Bạn chỉ thực sự nghĩ về encoding của một chuỗi khi nó hỏng. Khi bạn kiểm tra exception tracker và thấy
Encoding::InvalidByteSequenceError: "\xFE" on UTF-8
nhìn thẳng vào mặt bạn. Hoặc có thể "they’re" bắt đầu hiển thị thành "they’re".
Vậy, khi bạn có encoding xấu, làm sao bạn tìm ra gì bị hỏng? Và làm sao bạn sửa nó?
Encoding là gì?
Nếu bạn có thể tưởng tượng encoding làm gì với một chuỗi, những bug này dễ sửa hơn.
Bạn có thể nghĩ một chuỗi như một mảng byte, hoặc các con số nhỏ:
irb(main):001:0> "hello!".bytes => [104, 101, 108, 108, 111, 33]
Trong encoding này, 104 nghĩa là h, 33 nghĩa là !, v.v.
Nó trở nên phức tạp hơn khi bạn dùng các ký tự ít phổ biến trong tiếng Anh:
irb(main):002:0> "hellṏ!".bytes => [104, 101, 108, 108, 225, 185, 143, 33]
Bây giờ khó hơn để biết số nào biểu diễn ký tự nào. Thay vì một byte, ṏ được biểu diễn bởi nhóm byte [225, 185, 143]. Nhưng vẫn có mối quan hệ giữa byte và ký tự. Và encoding của một chuỗi định nghĩa mối quan hệ đó.
Hãy xem một tập byte đơn trông như thế nào khi bạn thử các encoding khác nhau:
# Thử chuỗi ISO-8859-1 với ký tự đặc biệt! 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] # Chuỗi đó trông như thế nào nếu được hiểu như 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]
Các byte không thay đổi. Nhưng trông nó không đúng chút nào. Thay đổi encoding đã thay đổi cách chuỗi in, mà không thay đổi byte.
Và không phải tất cả chuỗi đều có thể được biểu diễn trong tất cả 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>'
Hầu hết encoding đều nhỏ, và không thể xử lý mọi ký tự có thể. Bạn sẽ thấy lỗi đó khi một ký tự trong encoding này không tồn tại trong encoding kia, hoặc khi Ruby không thể tìm ra cách dịch ký tự giữa hai encoding.
Bạn có thể khắc phục lỗi này nếu bạn truyền thêm tùy chọn vào encode:
irb(main):064:0> "hi∑".encode("Windows-1252", invalid: :replace, undef: :replace) => "hi?"
Các tùy chọn invalid và undef thay thế các ký tự không thể dịch bằng một ký tự khác. Mặc định, ký tự thay thế đó là ?. (Khi bạn chuyển sang Unicode, nó là ).
Thật không may, khi bạn thay thế ký tự bằng encode, bạn có thể mất thông tin. Bạn không biết byte nào được thay thế bằng ?. Nhưng nếu bạn cần dữ liệu của bạn ở encoding mới đó, mất dữ liệu có thể tốt hơn là mọi thứ bị hỏng.
Cho đến nay, bạn đã thấy ba phương thức chuỗi chính để giúp bạn hiểu encoding:
encode, dịch chuỗi sang encoding khác (chuyển đổi ký tự thành đối ứng trong encoding mới)bytes, cho thấy các byte tạo nên chuỗiforce_encoding, cho thấy những byte đó trông như thế nào nếu được hiểu bởi một encoding khác
Khác biệt chính giữa encode và force_encoding là encode có thể thay đổi bytes, và force_encoding thì không.
Quy trình ba bước để sửa lỗi encoding
Bạn có thể sửa hầu hết vấn đề encoding với ba bước:
1. Xác định encoding chuỗi của bạn thực sự đang ở.
Điều này nghe dễ dàng. Nhưng chỉ vì một chuỗi nói nó là một encoding nào đó, không có nghĩa là nó thực sự là vậy:
irb(main):078:0> "hi\x99!".encoding => #<Encoding:UTF-8>
Đó không đúng – nếu nó thực sự là UTF-8, nó sẽ không có số lạ có gạch chéo ngược đó. Vậy làm sao bạn tìm ra encoding đúng cho chuỗi của bạn?
Nhiều phần mềm cũ sẽ giữ một encoding mặc định đơn lẻ, nên bạn có thể tìm hiểu nguồn đầu vào đến từ đâu. Ai đó dán nó từ Word? Có thể là Windows-1252. Nó đến từ file hoặc bạn kéo từ trang web cũ? Có thể là ISO-8859-1.
Tôi cũng thấy hữu ích khi tìm kiếm các bảng encoding, như những trang Wikipedia được liên kết. Trên những bảng đó, bạn có thể tra cứu các ký tự được tham chiếu bởi các số không xác định, và xem chúng có hợp lý trong ngữ cảnh không.
Trong ví dụ này, biểu đồ Windows-1252 cho thấy byte 99 biểu diễn ký tự "™". Byte 99 không tồn tại dưới ISO-8859-1. Nếu ™ hợp lý ở đây, bạn có thể giả định đầu vào là Windows-1252 và tiếp tục. Ngược lại, bạn có thể tiếp tục tìm kiếm cho đến khi tìm thấy ký tự dường như hợp lý hơn.
2. Quyết định encoding bạn muốn chuỗi trở thành.
Cái này dễ. Trừ khi bạn có lý do thực sự tốt, bạn muốn chuỗi của bạn được encoding UTF-8.
Có một encoding phổ biến khác bạn có thể dùng trong Ruby: ASCII-8BIT. Trong ASCII-8BIT, mỗi ký tự được biểu diễn bằng một byte đơn. Nghĩa là, str.chars.length == str.bytes.length. Vì vậy, nếu bạn muốn nhiều kiểm soát hơn các byte cụ thể trong chuỗi, ASCII-8BIT có thể là lựa chọn tốt.
3. Tái mã hóa chuỗi từ encoding ở bước 1 sang encoding ở bước 2.
Bạn có thể làm điều này với phương thức encode. Trong ví dụ này, chuỗi của chúng ta đang ở encoding Windows-1252, và chúng ta muốn nó trở thành UTF-8. Rất đơn giản:
irb(main):088:0> "hi\x99!".encode("UTF-8", "Windows-1252") => "hi™!"
Tốt hơn nhiều. (Mặc dù thứ tự các encoding trong cuộc gọi đó luôn ngược với trực giác của tôi).
Có thể gây choáng váng khi tưởng tượng các giải thích khác nhau của cùng một mảng byte. Đặc biệt khi một trong những giải thích đó bị hỏng. Nhưng có một cách tuyệt vời để trở nên thoải mái hơn với encoding: Chơi với chúng.
Mở console irb, và thử nghiệm với encode, bytes, và force_encoding. Quan sát cách encode thay đổi các byte tạo nên chuỗi. Xây dựng trực giác về trông các encoding khác nhau như thế nào. Khi bạn đã thoải mái hơn với encoding và dùng các bước này, bạn sẽ sửa trong vài phút thứ gì đó sẽ mất bạn hàng giờ trước đây.
Cuối cùng, nếu bạn muốn học cách làm thói quen việc học những loại điều này bằng thực hành, hãy lấy chương mẫu miễn phí cuốn sách của tôi. Phá vỡ thứ gì trong console là một cách thực sự thú vị để nghiên cứu các ý tưởng như thế này.