สัปดาห์ที่แล้ว คุณเรียนกระบวนการสั้นๆ ที่แก้ปัญหา encoding ส่วนใหญ่ แต่มีปัญหา encoding หนึ่งที่ยากกว่านั้นมาก
คุณเคยเห็นมันแล้ว (หรือคุณ’ve seen it?) คือเมื่อ curly quote กลายเป็น ’ หรือ em-dash กลายเป็น — มันทำให้คุณคิดว่าคุณบ้าลงมา มันควรทำงานได้แค่ไหน!
คุณอาจสร้างตารางใหญ่เพื่อหาตัวอักษรเสียและแทนที่:
[{broken: '–', fixed: "—"} {broken: "—", fixed: "–"} {broken: "‘", fixed: "‘"} {broken: "’", fixed: "’"} {broken: "“", fixed: "“"} {broken: "â€", fixed: "”"}, ...]
แต่มีวิธีง่ายและน่าเชื่อถือกว่านั้น
ทำไม Typography ที่ดีถึงแตกตลอด?
สัปดาห์ที่แล้ว คุณเรียนรู้ว่า encoding คือแค่วิธีแปลงกลุ่ม bytes ไร้ความหมายให้เป็นตัวอักษรที่แสดงได้ ไม่ใช่ทุกตัวอักษรแทนได้ใน byte เดียว เพราะมีตัวอักษรมากกว่า 256 ตัว ดังนั้นบางตัว เช่น curly quote ’ ใช้มากกว่า 1 byte:
irb(main):001:0> "they’re".bytes => [116, 104, 101, 121, 226, 128, 153, 114, 101]
แม้สตริงมี 7 ตัวอักษร แทนด้วย 9 bytes!
โฟกัสที่ curly quote:
irb(main):002:0> "’".bytes => [226, 128, 153]
เห็นว่าใช้ 3 bytes สตริงที่เสีย they’re มี 3 ตัวอักษรแทนที่ควรเป็น 1 ตัว ดูเหมือนไม่ใช่योगมากไปไหม?
เหมือนว่า 3 bytes นี้ควรอ่านเป็น UTF-8 ที่จะแทน curly quote แต่ แต่ละ byte กลายเป็นตัวอักษรต่างกันแทน ดังนั้น encoding ไหนที่แทน [226, 128, 153] เป็น ’? ดูตาราง encoding ยอดนิยม จะพบว่าเป็น Windows-1252
ตรวจสอบใน irb:
irb(main):003:0> "they’re".force_encoding("Windows-1252").encode("UTF-8") => "they’re"
(ต้องมี .encode("UTF-8") ต่อท้ายเพื่อแสดงในคอนโซล)
ถูกต้อง! นั่นคือปัญหา แต่มันแย่กว่านี้
ข้อมูลควรเป็น UTF-8 แต่ถูกอ่านผิดเป็น Windows-1252 แล้วคุณน่าจะบันทึกลงฐานข้อมูลหรือไฟล์เป็น UTF-8 Ruby จะแปลงให้เป็น UTF-8 อัตโนมัติ ทำให้ได้:
irb(main):004:0> "they’re".force_encoding("Windows-1252").encode("UTF-8") => "they’re" irb(main):005:0> "they’re".force_encoding("Windows-1252").encode("UTF-8").bytes => [116, 104, 101, 121, 195, 162, 226, 130, 172, 226, 132, 162, 114, 101]
สตริงถูกเข้ารหัสผิด สองครั้ง ตัวอักษรเสียดูเหมือนควรอยู่ שם ถ้าไม่รู้ว่ามันเกิดอย่างไร จะเกือบไม่สามารถแก้ได้
แก้ไขอย่างไร?
คิดย้อนกลับ:
- คุณมี UTF-8 string (they’re)
- แปลงมาจาก Windows-1252 string (they’re)
- bytes ของมัน ควร อ่านเป็น UTF-8 (they’re)
เพื่อแก้ไฟล์ ต้องทำย้อนกลับ ใช้ encode แปลง UTF-8 string กลับเป็น Windows-1252 string แล้ว force_encoding บังคับให้ string Windows-1252 ที่ผิดนั้นอ่านเป็น UTF-8:
irb(main):006:0> "they’re".encode("Windows-1252").force_encoding("UTF-8") => "they’re"
แก้ได้!
แต่มีปัญหาเล็กน้อย…
เสียดายที่คุณเจอปัญหานี้เพราะหลายไฟล์หรือเรคอร์ดมีข้อมูลเข้ารหัสผิด และไม่ทุกไฟล์/เรคอร์ดเสียหมด - อาจมีข้อมูลดีและเสียปนกัน โดยเฉพาะ ถ้าข้อมูลมาจากผู้เยี่ยมชม
ถ้าเป็นเช่นนั้น ไม่สามารถรันโค้ดนี้กับทุกสตริงแบบعمياء:
irb(main):007:0> "they’re".encode("Windows-1252").force_encoding("UTF-8") => "they’re" irb(main):008:0> "they’re".encode("Windows-1252").force_encoding("UTF-8") => "they\x92re"
ถ้ารันกับข้อมูลดี จะกลายเป็นข้อมูลเสีย แล้วทำไง?
ใช้ heuristic: แก้เฉพาะสตริงที่มีตัวอักษรเสีย เช่น â ใช้ได้ดีถ้า â ไม่เคยปรากฏในสตริงที่ถูกต้อง
ครั้งล่าสุดที่ผมแก้ ผมอยากปลอดภัย ใช้เครื่องมืออื่นช่วย: ตาของตัวเอง
ทุกครั้งที่เจอสตริงเสีย ผมพิมพ์มันออกมาพร้อม replacement:
Changing title with ID 6 from "They’re over there!" to "They’re over there!"
เพื่อตรวจสอบสตริงที่เปลี่ยนไปจำนวนน้อย และมั่นใจว่าไม่ได้ทำลายเพิ่มเติม
คิดว่าผมปวดหัว
เหมือนที่บอกสัปดาห์ที่แล้ว การเก็บการตีความข้อมูลเดียวกันหลายแบบในหัวยาก! แตถ้งง ลองเล่นใน irb จะช่วย ลองเปิดดูว่าสลับระหว่าง — และ —, หรือ “ และ “ ได้ไหม
การฝึกฝนแนวคิดซับซ้อนแบบนี้คือวิธีเร็วที่สุดให้มั่นใจตอนต้องใช้จริง และในบทตัวอย่างฟรีของ Practicing Rails คุณจะเรียนเทคนิคและกระบวนการที่ดีที่สุดสำหรับการฝึกแบบนี้