3 Steps to Fix Encoding Problems in Ruby
Bài đăng này đã không được cập nhật trong 7 năm
Bạn chỉ thực sự quan tâm đến encode của một string khi string đó xảy ra lỗi. Hãy kiểm tra trong terminal, chúng ta sẽ thấy khi có lỗi liên quan đến encode thì Ruby sẽ có raise một ngoại lệ sau:
Encoding::InvalidByteSequenceError: "\xFE" on UTF-8
Trong ví dụ trên, thực tế chúng ta muốn hiển thị chuỗi là “they’re” nhưng lại gặp lỗi encode. Vậy làm thế nào để xử lý lỗi trên và hiển thị ra đúng chuỗi mình mong muốn.
Encoding là gì?
Thực tế nếu bạn có thể hiểu được encoding là gì thì việc xử lý lỗi này sẽ dễ dàng hơn.
Bạn có thể hình dung một chuỗi là một mảng bytes
như sau:
irb(main):001:0> "hello!".bytes
=> [104, 101, 108, 108, 111, 33]
Trong ví dụ trên, encoding nghĩa là 104
biểu diễn ký tự h
, 33
biểu diễn ký tự !
, v.v....
Trong trường hợp trong chuỗi của bạn có sử dụng các ký tự ít phổ biến trong tiếng Anh thì mảng bytes
biểu diễn sẽ khác. Ví dụ:
irb(main):002:0> "hellṏ!".bytes
=> [104, 101, 108, 108, 225, 185, 143, 33]
Khác với via dụ trước thì ở đấy riêng để biểu diễn ký tự ṏ
cần sử dụng đến 3 bytes là [225, 185, 143]
. Như vậy, ta có thể thấy sẽ có một quan hệ nào đó giữa bytes và ký tự, và encoding của một chuỗi chính là định nghĩa cho mối qua hệ này.
Hãy nhìn ví dụ bên dưới. Ta có thể thấy với cùng một mảng bytes nhưng khi bạn thử encode theo hai cách khác 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]
Qua ví dụ trên, ta cũng thấy rằng mảng bytes sẽ không thay đổi khi ta đổi encode mà chỉ có chuỗi hiển thị ra thay đổi mà thôi.
Và không phải tất cả các chuỗi đều có thể endcode theo tất cả các kiểu:
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 là nhỏ, nó không thể encode tất cả các ký tự. Vì vậy lỗi sẽ xảy ra khi có một ký tự không encode được hoặc Ruby không tìm ra cách chuyển giữa hai encode bất kỳ với nhau.
Bạn có thể xử lý lỗi encode với một vài tuỳ chọn có sẵn trong hàm encode
:
irb(main):064:0> "hi∑".encode("Windows-1252", invalid: :replace, undef: :replace)
=> "hi?"
Với việc sử dụng các tuỳ chọn invalid
và undef
trên thì những ký tự không encode được sẽ được thay thế bằng một ký tự mặc định là ?
. (Trong Unicode thì là �).
Tuy nhiên, với việc thay thế ký tự như vậy thì bạn sẽ bị mất thông tin của chuỗi. Do đó cách tốt nhất là xử lý lỗi encoding sao cho hiển thị đầy đủ chuỗi mà không bị mất thông tin.
Đến bay giờ, bạn đã thấy bao phương thức liên qua đến encode của một chuỗi:
encode
, chuyển đổi một chuỗi từ kiểu encode này sang kiểu encode khácbytes
, hiển thị các bytes của một chuỗiforce_encoding
, cũng giống nhưencode
là chuyển đổi một chuỗi từ kiểu encode này sang kiểu encode khác. Tuy nhiên điểm khác nhau cơ bản giữa hai phương thức này làencode
có thể thay đổibytes
cònforce_encoding
thì không.
Ba bước xử lý lỗi encode
Đa phần các lỗi encode đều xử lý được theo ba bước sau:
1. Tìm mã encoding thực sự của chuỗi đó.
Nghe có vẻ đơn giản, ta chỉ cần sử dụng hàm encoding
, Ruby sẽ trả ra kết quả. Vi dụ:
irb(main):078:0> "hi\x99!".encoding
=> #<Encoding:UTF-8>
Điều này không đúng, nếu đúng encode là UTF-8 thì sẽ không hiển thị những ký tự lạ như trên. Vậy làm sao biết được encoding của một chuỗi?.
Câu trả lờ là dự đoán theo những cách sau:
- Cách thứ nhất: Theo kinh nghiệm thì rất nhiều phần mềm cũ sẽ chỉ có một kiểu encoding. Ví dụ: trong Word? có thể là Windows-1252, hay những website cũ thường sử dụng ISO-8859-1.
- Cách thứ hai: Bạn có thể tìm kiếm trên Internet, bảng endcode, sau đó sử dụng nó để đối chiếu bytes và ký tự. Từ đó tìm ra encoding.
Ví dụ: Encoding theo Windows-1252 thì byte
99
biểu diễn ký tự “™” character. Byte99
không có trong ISO-8859-1. If ™ là ký tự bạn cần hiển thị ở đây thì như vậy bạn có thể đoán chuỗi hiện tại đang được encode bằng Windows-1252.
2. Quyết định encoding mà bạn muốn chuyển đổi sang.
Điều này rất đơn giản, ta nên sử dụng các kiểu encoding phổ biến như UTF-8
Ngoài ra cũng có các encding phổ biến khác trong Ruby: ASCII-8BIT. In ASCII-8BIT, mỗi ký tự được biểu diễn bằng 1 byte. Điều này có nghĩa là, str.chars.length == str.bytes.length
. Vì vậy nếu bạn muốn kiểm soát các bytes cụ thể của một chuỗi thì ASCII-8BIT là lựa chọn tốt.
3. Re-encode chuỗi của bạn theo encoding trong bước 1 tới encoding trong bước 2.
Bạn có thể thực hiện bằng phương thức encode
. Ví dụ, chuỗi của chúng ta đã được encode Windows-1252 và bây giờ ta muốn nó được encode UTF-8 thì thực hiện đơn giản như sau:
irb(main):088:0> "hi\x99!".encode("UTF-8", "Windows-1252")
=> "hi™!"
Kết quả được một chuỗi có ý nghĩa hơn rất nhiều.
Bạn cũng có thể thử qua phương thức force_encode
để xem các bytes thay đổi ra sao.
Kết luận
Trên đây là ba bước để xử lý hầu hết các lỗi encode trong Ruby. Cảm ơn các bạn đã theo dõi. (bow)
Tài liệu tham khảo: http://www.justinweiss.com/articles/3-steps-to-fix-encoding-problems-in-ruby/
All rights reserved