Tìm hiểu về Encoding trong Ruby
I. Vấn đề Bạn chỉ thực sự nghĩ về encoding của một chuỗi khi mà lỗi xảy ra. Khi bạn kiểm tra ngoại lệ của bạn sẽ thấy tracker báo lỗi như sau: Encoding::InvalidByteSequenceError: "xFE" on UTF-8 Hay khi bạn muốn hiển thị text "they're" nhưng lại nhận được kết quả "they’re". Chỉ khi đó ...
I. Vấn đề
Bạn chỉ thực sự nghĩ về encoding của một chuỗi khi mà lỗi xảy ra. Khi bạn kiểm tra ngoại lệ của bạn sẽ thấy tracker báo lỗi như sau:
Encoding::InvalidByteSequenceError: "xFE" on UTF-8
Hay khi bạn muốn hiển thị text "they're" nhưng lại nhận được kết quả "they’re". Chỉ khi đó bạn mới tự hỏi encoding là gì và tìm cách làm sao để fix được lỗi kia. Bài viết này được dịch từ trang nguồn http://www.justinweiss.com/articles/3-steps-to-fix-encoding-problems-in-ruby/
II. Encoding là gì?
Nếu bạn có thể hình dung ra được những gì mà encoding đã làm với chuỗi của bạn thì những vấn đề này sẽ dễ dàng để fix hơn. Bạn có thể nghĩ rằng chuỗi của bạn như là một mảng các bytes, hoặc là những số nhỏ hơn:
irb(main):001:0> "hello!".bytes => [104, 101, 108, 108, 111, 33]
Trong encoding này, 104 đại diện cho ký tự h, 33 là ! và vv... Như vậy, sẽ phức tạp hơn khi bạn sử dụng những 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ờ rất khó để nói chính xác số nào đại diện cho ký tự nào. Thay vì một byte "ṏ" được đại diện bởi nhóm các bytes [255, 185, 143]. Nhưng vẫn có một mối quan hệ giữa bytes và những ký tự. Và encoding của một chuỗi sẽ định nghĩa quan hệ đó.
Bây giờ, hãy cùng xem ví dụ sau khi ta thử mã hóa một string bằng các encoding khách nhau:
# Try an ISO-8859-1 string with a special character! 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] # What would that string look like interpreted as ISO-8859-5 instead? 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]
Những bytes ở 2 kiểu encoding trên không thay đổi nhưng điều đó có vẻ không đúng. Encoding thay đổi string được hiển thị ra trong khi bytes không thay đổi.
Và không phải mọi string đều có thể được diễn tả bởi bất kỳ encoding nào:
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 các encoding đều có giới hạn và không thể xử lý được mọi ký tự. Bạn có thể sẽ thấy lỗi của một ký tự trong encoding này nhưng khi sử dụng bộ encoding khác thì lại không, hoặc khi Ruby không thể tìm ra cách để "dịch" một ký tự nằm giữa 2 bộ encoding. Trong trường hợp này, cách xử lý là tạm thời xem như không có những ký tự này bằng cách thêm một số option vào hàm encode:
irb(main):064:0> "hi∑".encode("Windows-1252", invalid: :replace, undef: :replace) => "hi?"
Hai option invalid và undef sẽ thay thế toàn bộ những ký tự không thể dịch được bởi một ký tự khác, ký tự thay thế mặc định sẽ là "?" (Khi bạn convert trong Unicode sẽ là �).
Thật không may, khi bạn thay thế những ký tự với hàm encode, bạn có thể mất một số thông tin. Bạn không thể làm gì với những byte đã bị thay thế bởi ?. So sánh thiệt hơn thì việc đánh mất dữ liệu vẫn còn hơn là làm cho dữ liệu bị phá vỡ.
Tiếp theo, để hiểu rõ hơn về encoding, chúng ta sẽ tìm hiểu về ba 3 method chính:
- encode: dịch một chuỗi sang một chuỗi với encoding mới (convert các ký tự sang các ký tự tương ứng trong bộ encoding mới)
"hellÔ!".encode("ISO-8859-1") => "hellxD4!"
- bytes: show ra mảng các bytes tương ứng với mỗi ký tự trong chuỗi input.
"Hello".bytes => [72, 101, 108, 108, 111]
- force_encoding : cho bạn thấy những bytes này sẽ được giải mã như thế nào bởi một encoding khác.
"Ü".force_encoding("BINARY") => "xC3x9C"
Tóm lại: Điểm khác biệt cơ bản giữa encode và force_encoding là encode có thể thay đổi byte còn force_encoding thì không.
III. 3 bước để xử lý lỗi encoding
Bạn có thể fix hầu hết các lỗi encoding với 3 bước sau đây:
1. Khám phá ra encoding thực sự của một chuỗiĐiều này nghe có vẻ dễ. Nhưng khi bạn dùng hàm encoding để biết một chuỗi thuộc encoding nào thì thực sự chưa hẳn đã là vậy:
irb(main):078:0> "hix99!".encoding => #<Encoding:UTF-8>
Điều này là không đúng - nếu nó thực sự là UTF-8 thì làm sao lại có cả ký tự lạ trong đó. Vậy làm thế nào để bạn biết được encoding thực sự của 1 chuỗi được? Tốt nhất là bạn hãy search trên Internet các encoding và thử với từng loại đó xem nó có phù hợp với chuỗi đó không.
2. Quyết định encoding để mã hóa string của bạnHiểu một cách đơn giản, nếu không có lý do gì thực sự đặc biệt, hãy sử dụng UTF-8. Một số trường hợp hiếm gặp, có thể sẽ phải dùng tới ASCII-8BIT trong Ruby. Với bộ mã ASCII-8BIT, mỗi ký tự được đại diện bởi duy nhất một byte, kể cả những ký tự đặc biệt đi nữa. Do đó, nó sẽ là lựa chọn tốt khi bạn muốn xử lý từng byte một trong string của mình.
3. Re-encode cho đến khi nào thỏa mãnirb(main):088:0> "hix99!".encode("UTF-8", "Windows-1252") => "hi™!"
Ở ví dụ trên chuỗi đang ở encoding Window-1252 nhưng tôi muốn nó ở UTF-8 và chúng ta có thể dễ dàng thực hiện điều đó trong encode.