Trong bài viết tuần trước, bạn đã học một quy trình ngắn giải quyết hầu hết vấn đề mã hóa. Nhưng có một vấn đề mã hóa khó giải quyết hơn nhiều.
Tôi biết bạn đã thấy nó. (Hoặc có thể bạn’ve đã thấy nó?) Đó là khi một dấu ngoặc đơn cong biến thành ’, hoặc một gạch ngang dài biến thành —. Nó sẽ khiến bạn nghĩ mình đã điên. Nó se chỉ hoạt động!
Bạn có thể tạo một bảng khổng lồ, để tìm ký tự xấu và thay thế bằng ký tự tốt:
[{broken: '–', fixed: "—"} {broken: "—", fixed: "–"} {broken: "‘", fixed: "‘"} {broken: "’", fixed: "’"} {broken: "“", fixed: "“"} {broken: "â€", fixed: "”"}, ...]
Nhưng có một cách dễ dàng, tin cậy hơn để sửa những ký tự hỏng đó.
Tại sao typography tốt luôn bị hỏng?
Tuần trước, bạn đã học rằng một encoding chỉ là cách để biến các nhóm byte vô nghĩa thành ký tự hiển thị. Không phải ký tự nào cũng có thể được biểu diễn trong một byte đơn lẻ, vì có hơn 256 ký tự có thể có. Vì vậy một số ký tự, như dấu ngoặc đơn cong ’, được biểu diễn bằng nhiều hơn một byte:
irb(main):001:0> "they’re".bytes => [116, 104, 101, 121, 226, 128, 153, 114, 101]
Mặc dù chuỗi chỉ có 7 ký tự, chúng được biểu diễn bằng 9 byte!
Khi bạn tập trung chỉ vào dấu ngoặc đơn cong:
irb(main):002:0> "’".bytes => [226, 128, 153]
Bạn sẽ thấy nó dùng 3 byte. Và chuỗi bị hỏng they’re có ba ký tự ở chỗ nó nên chỉ có một. Đó có vẻ như không chỉ là sự trùng hợp, phải không?
Có vẻ như ba byte đó nên được đọc như UTF-8, nơi chúng sẽ biểu diễn một dấu ngoặc đơn cong. Thay vào đó, mỗi byte đang hiển thị như một ký tự khác. Vậy encoding nào sẽ biểu diễn [226, 128, 153] thành ’? Nếu bạn nhìn vài bảng encoding phổ biến, bạn sẽ thấy đó là Windows-1252.
Bạn có thể kiểm tra trong irb:
irb(main):003:0> "they’re".force_encoding("Windows-1252").encode("UTF-8") => "they’re"
(Chúng ta cần .encode("UTF-8") cuối cùng để hiển thị chuỗi trong console.)
Đúng! Đó là vấn đề. Nhưng nó tệ hơn.
Dữ liệu se là UTF-8, nhưng bị đọc nhầm thành Windows-1252. Nhưng bạn có thể sẽ lưu dữ liệu đó vào cơ sở dữ liệu, hoặc file, như UTF-8. Ruby sẽ hữu ích chuyển đổi nó thành UTF-8 cho bạn, vậy bạn sẽ kết thúc với:
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]
Chuỗi của bạn đã bị mã hóa sai hai lần. Những ký tự hỏng đó bây giờ trông như thể chúng nên ở đó. Và nếu bạn không biết nó xảy ra như thế nào, sẽ gần như không thể giải quyết được.
Làm sao bạn sửa nó?
Làm sao để đưa mọi thứ về bình thường? Hãy suy nghĩ về vấn đề ngược lại:
- Bạn có một chuỗi UTF-8, (they’re)
- được chuyển đổi từ một chuỗi Windows-1252, (they’re)
- mà các byte của nó se đã được đọc như UTF-8 (they’re)
Để sửa nó, bạn chỉ cần làm theo các bước ngược lại đó. Dùng encode để chuyển chuỗi UTF-8 ngược lại thành chuỗi Windows-1252. Sau đó, dùng force_encoding để ép chuỗi Windows-1252 bị mã hóa sai đó được đọc như UTF-8:
irb(main):006:0> "they’re".encode("Windows-1252").force_encoding("UTF-8") => "they’re"
Đã sửa!
Có một vấn đề nhỏ…
Thật không may, bạn có thể tìm thấy vấn đề này vì một đống file hoặc bản ghi cơ sở dữ liệu có dữ liệu bị mã hóa sai. Và không phải file hoặc bản ghi nào cũng bị mã hóa sai – bạn có thể có hỗn hợp dữ liệu tốt và xấu. Đặc biệt nếu dữ liệu đó đến từ người truy cập trang web của bạn.
Nếu đó là trường hợp, bạn không thể chạy mã đó 맹 trên mọi chuỗi:
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"
Nếu bạn chạy nó trên dữ liệu tốt, bạn sẽ chỉ biến nó thành dữ liệu xấu. Vậy bạn có thể làm gì?
Bạn có thể dùng heuristic: chỉ thay đổi chuỗi có chứa một trong các ký tự xấu, như â. Điều này hoạt động tốt nếu một ký tự như â không bao giờ xuất hiện trong chuỗi hợp lệ.
Lần trước tôi sửa loại lỗi này, tuy nhiên, tôi muốn chơi an toàn. Tôi dùng một công cụ hữu ích khác để giúp: mắt của tôi.
Bất cứ khi nào tôi tìm thấy một chuỗi bị mã hóa sai, tôi in nó ra, cùng với bản thay thế của nó:
Changing title with ID 6 from "They’re over there!" to "They’re over there!"
Bằng cách đó, tôi có thể kiểm tra nhanh số lượng nhỏ chuỗi đã thay đổi, và đảm bảo chúng không bị hỏng thêm.
Tôi nghĩ tôi bị đau đầu
Như tôi nói tuần trước, giữ các giải thích khác nhau của cùng một dữ liệu ngay trong đầu bạn là khó! Nhưng nếu bạn bị bối rối, khám phá trong console irb sẽ giúp ích. Vậy hãy thử! Mở một cái lên, và xem nếu bạn có thể đi qua lại giữa — và —, hoặc “ và “.
Thực hành các ý tưởng phức tạp như thế này là cách nhanh nhất để cảm thấy tự tin khi bạn cần chúng. Và trong chương mẫu miễn phí của Practicing Rails, bạn sẽ học các kỹ thuật và quy trình tốt nhất để làm chính điều đó.