ฉันเพิ่งเผยแพร่บทความที่ฉันทดสอบวิธีสตริงของ Ruby ส่วนใหญ่ด้วยอักขระ Unicode บางตัวเพื่อดูว่าจะทำงานโดยไม่คาดคิดหรือไม่ หลายคนทำ
การวิจารณ์อย่างหนึ่งที่บางคนมีเกี่ยวกับบทความนี้คือฉันใช้สตริงที่ไม่ปกติสำหรับการทดสอบ พูดตามตรง ฉันรู้สึกคลุมเครือเล็กน้อยเกี่ยวกับการทำให้ Unicode เป็นมาตรฐาน ฉันสงสัยว่า Rubyists หลายคนเป็น
เมื่อใช้การทำให้เป็นมาตรฐาน คุณสามารถใช้สตริง Unicode จำนวนมากที่ทำงานโดยไม่คาดคิดในการทดสอบของฉัน และแปลงเป็นสตริงที่เล่นได้ดีกับวิธีสตริงของ Ruby อย่างไรก็ตาม:
- การแปลงอาจไม่สมบูรณ์แบบเสมอไป ลำดับ Unicode บางอย่างจะทำให้วิธีสตริงของ Ruby ทำงานผิดปกติเสมอ
- เป็นสิ่งที่คุณต้องทำด้วยตนเอง ทั้ง Ruby, Rails หรือ DB จะไม่ทำให้เป็นมาตรฐานโดยอัตโนมัติตามค่าเริ่มต้น
บทความนี้จะเป็นการแนะนำสั้นๆ เกี่ยวกับการปรับมาตรฐาน Unicode ใน Ruby หวังว่าจะเป็นจุดเริ่มต้นสำหรับการสำรวจของคุณเอง
มาทำให้สตริงเป็นมาตรฐานกันเถอะ
String#unicode_normalize
วิธีการถูกนำมาใช้ใน Ruby 2.2 การเขียนด้วย Ruby นั้นไม่เร็วเท่ากับการทำให้ไลบรารีการทำให้เป็นมาตรฐาน เช่น utf8_proc และ unicode gems ที่ใช้ประโยชน์จาก C
เหตุผลที่เราต้องทำให้เป็นมาตรฐานคือใน Unicode มีหลายวิธีในการเขียนอักขระ ตัวอักษร "Å"
สามารถแสดงเป็นจุดรหัส "\u00c5"
หรือเป็นองค์ประกอบของตัวอักษร "A" และสำเนียง:"A\u030A"
.
Normalization แปลงรูปแบบหนึ่งเป็นอีกรูปแบบหนึ่ง:
"A\u030A".unicode_normalize #=> 'Å' (same as "\u00C5")
แน่นอนว่า ไม่ใช่แค่วิธีเดียวในการทำให้ Unicode เป็นมาตรฐาน นั่นจะง่ายเกินไป! มีสี่วิธีในการทำให้เป็นมาตรฐาน เรียกว่า "รูปแบบการทำให้เป็นมาตรฐาน" มีการตั้งชื่อโดยใช้ตัวย่อที่เป็นความลับ:NFD, NFC, NFKD และ NFKC
String#unicode_normalize
ใช้ NFC เป็นค่าเริ่มต้น แต่เราสามารถบอกให้ใช้รูปแบบอื่นได้ดังนี้:
"a\u0300".unicode_normalize(:nfkc) #=> 'à' (same as "\u00E0")
แต่สิ่งนี้หมายความว่าอย่างไร? แบบฟอร์มการทำให้เป็นมาตรฐานทั้งสี่แบบทำอะไรได้บ้าง? มาดูกันเลย
รูปแบบการทำให้เป็นมาตรฐาน
การดำเนินการทำให้เป็นมาตรฐานมีสองประเภท:
- องค์ประกอบ: แปลงอักขระหลายรหัสจุดเป็นจุดรหัสเดียว ตัวอย่างเช่น
"a\u0300"
กลายเป็น"\u00E0"
ซึ่งทั้งสองวิธีเป็นวิธีการเข้ารหัสอักขระà
. - การสลายตัว: ตรงข้ามขององค์ประกอบ แปลงอักขระรหัสจุดเดียวเป็นจุดรหัสหลายจุด ตัวอย่างเช่น
"\u00E0"
กลายเป็น"a\u0300"
.
องค์ประกอบและการสลายตัวสามารถทำได้สองวิธี:
- Canonical: รักษาร่ายมนตร์ ตัวอย่างเช่น
"2⁵"
ยังคง"2⁵"
แม้ว่าบางระบบอาจไม่รองรับอักขระ superscript-5 ก็ตาม - ความเข้ากันได้: สามารถแทนที่ร่ายมนตร์ด้วยอักขระที่เข้ากันได้ ตัวอย่างเช่น
"2⁵"
จะถูกแปลงเป็น"2 5"
.
การดำเนินการทั้งสองและสองตัวเลือกจะรวมกันในรูปแบบต่างๆ เพื่อสร้าง "แบบฟอร์มการทำให้เป็นมาตรฐาน" สี่รูปแบบ ฉันได้แสดงรายการทั้งหมดไว้ในตารางด้านล่าง พร้อมด้วยคำอธิบายและตัวอย่างอินพุตและเอาต์พุต:
ชื่อ | คำอธิบาย | อินพุต | ผลลัพธ์ |
---|---|---|---|
NFD | การสลายตัวตามรูปแบบบัญญัติ | Å "\u00c5" | Å "A\u030A" |
NFC | การสลายตัวตามรูปแบบบัญญัติตามด้วยองค์ประกอบตามรูปแบบบัญญัติ | Å "A\u030A" | Å "\u00c5" |
NFKD | การสลายตัวที่เข้ากันได้ | ẛ̣ "\u1e9b\u0323" | ṩ "\u0073\u0323\u0307" |
NFKC | การสลายตัวที่เข้ากันได้ตามด้วยองค์ประกอบที่เป็นที่ยอมรับ | ẛ̣ "\u1e9b\u0323" | ṩ "\u1e69" |
หากคุณดูตารางนี้สักครู่ คุณอาจเริ่มสังเกตว่าคำย่อมีความหมาย:
- "NF" ย่อมาจาก "normalization form"
- "D" ย่อมาจาก "decomposition"
- "C" ย่อมาจาก "composition"
- "K" ย่อมาจาก "kompatibility" :)
สำหรับตัวอย่างเพิ่มเติมและคำอธิบายทางเทคนิคที่ละเอียดยิ่งขึ้น โปรดดูที่ Unicode Standard Annex #15
การเลือกรูปแบบการทำให้เป็นมาตรฐาน
แบบฟอร์มการทำให้เป็นมาตรฐานที่คุณควรใช้นั้นขึ้นอยู่กับงานที่ทำอยู่ คำแนะนำของฉันด้านล่างนี้อ้างอิงจากคำถามที่พบบ่อยเกี่ยวกับ Unicode Normalization
ใช้ NFC สำหรับความเข้ากันได้ของสตริง
หากเป้าหมายของคุณคือการทำให้วิธีการสตริงของ Ruby เล่นได้ดีกับ Unicode ส่วนใหญ่ คุณน่าจะต้องการใช้ NFC มากที่สุด มีเหตุผลที่เป็นค่าเริ่มต้นสำหรับ String#unicode_normalize
.
- ประกอบด้วยอักขระหลายโค้ด-พอยต์เป็นจุดโค้ดเดียวที่เป็นไปได้ อักขระหลายจุดเป็นสาเหตุของปัญหาส่วนใหญ่เกี่ยวกับวิธีสตริง
- ไม่เปลี่ยนแปลงร่ายมนตร์ ดังนั้นผู้ใช้ปลายทางของคุณจะไม่สังเกตเห็นการเปลี่ยนแปลงใดๆ ในข้อความที่พวกเขาป้อน
ที่กล่าวว่า อักขระที่มีหลายโค้ดหลายจุดไม่สามารถประกอบเป็นโค้ดพอยต์เดียวได้ ในกรณีเหล่านั้นเมธอด String ของ Ruby จะทำงานได้ไม่ดี:
s = "\u01B5\u0327\u0308" # => "Ƶ̧̈", an un-composable character
s.unicode_normalize(:nfc).size # => 3, even though there's only one character
ใช้ NFKC เพื่อความปลอดภัยและความเข้ากันได้ของฐานข้อมูล
หากคุณกำลังทำงานกับข้อความที่เกี่ยวข้องกับความปลอดภัย เช่น ชื่อผู้ใช้ หรือสนใจที่จะให้ข้อความเล่นได้ดีกับฐานข้อมูลของคุณเป็นหลัก NFKC อาจเป็นทางเลือกที่ดี
- แปลงอักขระที่อาจเป็นปัญหาเป็นอักขระที่เข้ากันได้
- จากนั้นจะประกอบอักขระทั้งหมดเป็นจุดโค้ดเดียว
เพื่อดูว่าเหตุใดจึงมีประโยชน์สำหรับการรักษาความปลอดภัย ลองจินตนาการว่าคุณมีผู้ใช้ที่มีชื่อผู้ใช้ "HenryIV" ผู้มุ่งร้ายอาจพยายามแอบอ้างเป็นผู้ใช้รายนี้โดยการลงทะเบียนชื่อผู้ใช้ใหม่:"HenryⅣ"
ฉันรู้ว่าพวกเขาดูเหมือนกัน นั่นคือประเด็น แต่จริงๆ แล้วมันเป็นสองสายที่ต่างกัน อดีตใช้อักขระ ascii "IV"
ในขณะที่ตัวหลังใช้อักขระ Unicode สำหรับเลขโรมัน 4:"Ⅳ"
.
คุณสามารถป้องกันสิ่งนี้ได้โดยใช้ NFKC เพื่อทำให้สตริงเป็นปกติก่อนที่จะตรวจสอบความถูกต้อง ในกรณีนี้ NFKC จะแปลงยูนิโค้ด "\u2163"
ไปที่ตัวอักษร ASCII "IV"
a = "Henry\u2163"
b = "HenryIV"
a.unicode_normalize(:nfc) == b.unicode_normalize(:nfc) # => false, because NFC preserves glyphs
a.unicode_normalize(:nfkc) == b.unicode_normalize(:nfkc) # => true, because NFKC evaluates both to the ascii "IV"
การแยกคำ
เมื่อฉันได้ตรวจสอบเพิ่มเติมแล้ว ฉันรู้สึกประหลาดใจเล็กน้อยที่การทำให้ Unicode เป็นปกตินั้นไม่ใช่หัวข้อที่ใหญ่กว่าในชุมชน Ruby and Rails คุณอาจคาดหวังว่า Rails จะทำเพื่อคุณ แต่เท่าที่ฉันบอกได้ มันไม่ใช่ และการไม่ได้ทำให้ข้อมูลที่ผู้ใช้ของคุณให้เป็นมาตรฐานหมายความว่าวิธีสตริงของ Ruby จำนวนมากไม่น่าเชื่อถือ
หากผู้อ่านที่รักท่านใดรู้บางอย่างที่ฉันไม่รู้ โปรดติดต่อทาง twitter @StarrHorne หรืออีเมลที่ [email protected] Unicode เป็นหัวข้อใหญ่และฉันได้พิสูจน์แล้วว่าฉันไม่รู้ทุกอย่างเกี่ยวกับมัน :)