คุณคิดถึง encoding ของ string ก็ต่อเมื่อมันพัง เวลาดู exception tracker แล้วเห็น
Encoding::InvalidByteSequenceError: "\xFE" on UTF-8
จ้องอยู่หน้าตา หรือ "they’re" กลายเป็น "they’re"
ตอน encoding พัง จะหาสาเหตุและแก้ได้อย่างไร?
Encoding คืออะไร?
ถ้าคุณจินตนาการได้ว่า encoding ทำอะไรกับ string แบกนี้จะง่ายขึ้น
คิดว่าสตริงคืออาเรย์ของ bytes หรือตัวเลขเล็กๆ:
irb(main):001:0> "hello!".bytes => [104, 101, 108, 108, 111, 33]
ใน encoding นี้ 104 คือ h, 33 คือ ! ไปเรื่อยๆ
ซับซ้อนขึ้นกับตัวอักษรที่น้อยในภาษาอังกฤษ:
irb(main):002:0> "hellṏ!".bytes => [104, 101, 108, 108, 225, 185, 143, 33]
ตอนนี้ยากกว่าจะบอกว่าเลขไหนแทนตัวอักษรไหน แทนที่จะเป็น 1 byte ṏ แทนด้วยกลุ่ม bytes [225, 185, 143] แต่มันยังมีความสัมพันธ์ระหว่าง bytes และตัวอักษร และ encoding ของสตริงคือสิ่งที่กำหนดความสัมพันธ์นั้น
ดูชุด bytes เดียวกันตอนตีความด้วย encoding ต่างๆ:
# ลอง ISO-8859-1 string พร้อมตัวอักษรพิเศษ! irb(main):003:0> str = "hellÔ!".encode("ISO-8859-1"); str.encode("UTF-8") => "hellÔ!" irb(main):004:0> str.bytes => [104, 101, 108, 108, 212, 33] # ถ้าตีความเป็น ISO-8859-5 จะเป็นยังไง? irb(main):005:0> str.force_encoding("ISO-8859-5"); str.encode("UTF-8") => "hellд!" irb(main):006:0> str.bytes => [104, 101, 108, 108, 212, 33]
Bytes ไม่เปลี่ยน แตการเปลี่ยน encoding เปลี่ยนวิธีแสดงผลโดยไม่เปลี่ยน bytes
และไม่ใช่ทุกสตริงแทนได้ในทุก encoding:
irb(main):006:0> "hi∑".encode("Windows-1252") Encoding::UndefinedConversionError: U+2211 to WINDOWS-1252 in conversion from UTF-8 to WINDOWS-1252 from (irb):61:in `encode' from (irb):61 from /usr/local/bin/irb:11:in `<main>'
Encoding ส่วนใหญ่เล็ก ไม่รองรับทุกตัวอักษร จะเจอ error ตอนตัวอักษรใน encoding หนึ่งไม่มีในอีกอัน หรือ Ruby แปลงไม่ได้
หลีกเลี่ยง error นี้ได้ด้วย options ใน encode:
irb(main):064:0> "hi∑".encode("Windows-1252", invalid: :replace, undef: :replace) => "hi?"
invalid และ undef แทนที่ตัวอักษรที่แปลงไม่ได้ด้วยตัวอักษรอื่น ค่าเริ่มต้นคือ ? (ตอนแปลงเป็น Unicode คือ )
แตตอนแทนที่ด้วย encode อาจสูญเสียข้อมูล ไม่รู้ว่า bytes ไหนถูกแทนที่ด้วย ? แตถ้าจำเป็นต้องอยู่ใน encoding ใหม่ การสูญเสียข้อมูลดีกว่าการพัง
จนตอนนี้เห็น 3 เมธอดสำคัญ:
encode- แปลงสตริงเป็น encoding อื่น (แปลงตัวอักษรเป็นเทียบเท่าใน encoding ใหม่)bytes- แสดง bytes ที่ประกอบสตริงforce_encoding- แสดงว่า bytes นั้นจะเห็นยังไงถ้าตีความด้วย encoding อื่น
ความต่างหลัก: encode อาจเปลี่ยน bytes แต่ force_encoding ไม่เปลี่ยน
กระบวนการ 3 ขั้นตอนแก้ Encoding Bugs
แก้ปัญหา encoding ส่วนใหญ่ได้ด้วย 3 ขั้นตอน:
1. ค้นหา Encoding จริงของสตริง
ดูง่าย แตแค่สตริง บอก ว่าเป็น encoding ไหน ไม่ได้หมายความว่ามันจริง:
irb(main):078:0> "hi\x99!".encoding => #<Encoding:UTF-8>
ไม่ถูกต้อง - ถ้าเป็น UTF-8 จริงๆ จะไม่มี backslashed number แปลกๆ อยู่ ดังนั้นหา encoding ที่ถูกต้องอย่างไร?
ซอฟต์แวร์เก่ามักใช้ default encoding เดียว จึงค้นหาได้จากแหล่งที่มา มาจาก Word? อาจเป็น Windows-1252 มาจากไฟล์หรือเว็บเก่า? อาจเป็น ISO-8859-1
การค้นหาตาราง encoding เช่นใน Wikipedia ก็ช่วย ดูตัวอักษรที่ bytes ไม่รู้จักแทน ดูว่าเข้าใจในบริบทไหม
ตัวอย่างนี้ Windows-1252 chart บอกว่า byte 99 คือ "™" Byte 99 ไม่มีใน ISO-8859-1 ถ้า ™ เข้าใจได้ในบริบท สมมติว่า input เป็น Windows-1252 ได้ ไม่เช่นนั้นค้นต่อจนเจอตัวอักษรที่สมเหตุสมผลกว่า
2. ตัดสินใจ Encoding ที่ต้องการ
เรื่องนี้ง่าย ถ้าไม่มีเหตุผลพิเศษ คุณต้องการ string เป็น UTF-8
อีก encoding หนึ่งที่พบบ่อยใน Ruby: ASCII-8BIT ใน ASCII-8BIT ทุกตัวอักษรแทนด้วย 1 byte คือ str.chars.length == str.bytes.length ถ้าอยากควบคุม bytes โดยตรง ASCII-8BIT อาจดี
3. แปลง Encoding ใหม่จากขั้นตอน 1 ไปขั้นตอน 2
ใช้ encode ตัวอย่างนี้ string เป็น Windows-1252 และเรา อยากให้ เป็น UTF-8:
irb(main):088:0> "hi\x99!".encode("UTF-8", "Windows-1252") => "hi™!"
ดีขึ้นเยอะ (แม้ลำดับ encoding ในการเรียกจะดูถอยหลัง)
การจินตนาการการตีความ bytes เดียวกันหลายแบบทำปวดหัว โดยเฉพาะ ตอนหนึ่งในนั้นพัง แตมีวิธีทำให้คล่องกับ encodings: เล่นกับมัน
เปิด irb ลอง encode, bytes, force_encoding ดูว่า encode เปลี่ยน bytes ยังไง สร้าง直觉เกี่ยวกับ encoding ต่างๆ เมื่อคล่องขึ้นและใช้ขั้นตอนนี้ คุณจะแก้ในนาทีสิ่งที่เคยใช้ชั่วโมง
สุดท้าย ถ้าอยากเรียนรู้วิธีทำให้การเรียนรู้แบบนี้กลายเป็นนิสัย ดาวน์โหลดบทตัวอย่างฟรีของหนังสือผม การทำลายสิ่งต่างๆ ในคอนโซลเป็นวิธีเรียนรู้ที่สนุกมาก