Computer >> คอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Ruby

การทำให้เป็นมาตรฐาน Unicode ใน Ruby

ฉันเพิ่งเผยแพร่บทความที่ฉันทดสอบวิธีสตริงของ Ruby ส่วนใหญ่ด้วยอักขระ Unicode บางตัวเพื่อดูว่าจะทำงานโดยไม่คาดคิดหรือไม่ หลายคนทำ

การวิจารณ์อย่างหนึ่งที่บางคนมีเกี่ยวกับบทความนี้คือฉันใช้สตริงที่ไม่ปกติสำหรับการทดสอบ พูดตามตรง ฉันรู้สึกคลุมเครือเล็กน้อยเกี่ยวกับการทำให้ Unicode เป็นมาตรฐาน ฉันสงสัยว่า Rubyists หลายคนเป็น

เมื่อใช้การทำให้เป็นมาตรฐาน คุณสามารถใช้สตริง Unicode จำนวนมากที่ทำงานโดยไม่คาดคิดในการทดสอบของฉัน และแปลงเป็นสตริงที่เล่นได้ดีกับวิธีสตริงของ Ruby อย่างไรก็ตาม:

  1. การแปลงอาจไม่สมบูรณ์แบบเสมอไป ลำดับ Unicode บางอย่างจะทำให้วิธีสตริงของ Ruby ทำงานผิดปกติเสมอ
  2. เป็นสิ่งที่คุณต้องทำด้วยตนเอง ทั้ง Ruby, Rails หรือ DB จะไม่ทำให้เป็นมาตรฐานโดยอัตโนมัติตามค่าเริ่มต้น

บทความนี้จะเป็นการแนะนำสั้นๆ เกี่ยวกับการปรับมาตรฐาน Unicode ใน Ruby หวังว่าจะเป็นจุดเริ่มต้นสำหรับการสำรวจของคุณเอง

มาทำให้สตริงเป็นมาตรฐานกันเถอะ

String#unicode_normalize วิธีการถูกนำมาใช้ใน Ruby 2.2 การเขียนด้วย Ruby นั้นไม่เร็วเท่ากับการทำให้ไลบรารีการทำให้เป็นมาตรฐาน เช่น utf8_proc และ unicode gems ที่ใช้ประโยชน์จาก C

เหตุผลที่เราต้องทำให้เป็นมาตรฐานคือใน Unicode มีหลายวิธีในการเขียนอักขระ ตัวอักษร "Å" สามารถแสดงเป็นจุดรหัส "\u00c5" หรือเป็นองค์ประกอบของตัวอักษร "A" และสำเนียง:"A\u030A" .

การทำให้เป็นมาตรฐาน Unicode ใน Ruby

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 หรืออีเมลที่ starr@honeybadger.io Unicode เป็นหัวข้อใหญ่และฉันได้พิสูจน์แล้วว่าฉันไม่รู้ทุกอย่างเกี่ยวกับมัน :)