Có hai giai đoạn để xử lý văn bản Unicode. Đầu tiên là "làm thế nào tôi có thể nhập nó và xuất nó mà không mất thông tin". Thứ hai là "làm thế nào để tôi đối xử với văn bản theo quy ước ngôn ngữ địa phương".
bài đăng của tchrist bao gồm cả hai, nhưng phần thứ hai là nơi 99% văn bản trong bài viết của ông đến từ. Hầu hết các chương trình thậm chí không xử lý I / O chính xác, vì vậy điều quan trọng là phải hiểu điều đó trước khi bạn bắt đầu lo lắng về việc chuẩn hóa và đối chiếu.
Bài này nhằm giải quyết vấn đề đầu tiên đó
Khi bạn đọc dữ liệu vào Perl, nó không quan tâm mã hóa nó là gì. Nó phân bổ một số bộ nhớ và bỏ các byte ở đó. Nếu bạn nóiprint $str
, nó chỉ làm mờ các byte đó ra thiết bị đầu cuối của bạn, có thể được đặt để giả sử mọi thứ được ghi vào nó là UTF-8 và văn bản của bạn hiển thị.
Kỳ diệu.
Ngoại trừ, không phải vậy. Nếu bạn cố gắng coi dữ liệu là văn bản, bạn sẽ thấy có gì đó không hay đang xảy ra. Bạn không cần phải đi xa hơn length
để thấy rằng những gì Perl nghĩ về chuỗi của bạn và những gì bạn nghĩ về chuỗi của bạn không đồng ý. Viết một lớp lót như: perl -E 'while(<>){ chomp; say length }'
và gõ vào文字化け
và bạn nhận được 12 ... không phải là câu trả lời đúng, 4.
Đó là bởi vì Perl giả định chuỗi của bạn không phải là văn bản. Bạn phải nói với nó rằng đó là văn bản trước khi nó đưa ra câu trả lời đúng.
Điều đó đủ dễ dàng; mô-đun Encode có các chức năng để làm điều đó. Điểm vào chung là Encode::decode
(hoặcuse Encode qw(decode)
, tất nhiên). Hàm đó lấy một số chuỗi từ thế giới bên ngoài (cái mà chúng ta gọi là "octet", một cách nói lạ mắt là "byte 8 bit") và biến nó thành một số văn bản mà Perl sẽ hiểu. Đối số đầu tiên là tên mã hóa ký tự, như "UTF-8" hoặc "ASCII" hoặc "EUC-JP". Đối số thứ hai là chuỗi. Giá trị trả về là vô hướng Perl chứa văn bản.
(Ngoài ra Encode::decode_utf8
, giả sử UTF-8 cho mã hóa.)
Nếu chúng ta viết lại một lớp lót của chúng tôi:
perl -MEncode=decode -E 'while(<>){ chomp; say length decode("UTF-8", $_) }'
Chúng tôi gõ vào 字 và nhận được "4" là kết quả. Sự thành công.
Điều đó, ngay tại đó, là giải pháp cho 99% các vấn đề về Unicode trong Perl.
Điều quan trọng là, bất cứ khi nào bất kỳ văn bản nào đi vào chương trình của bạn, bạn phải giải mã nó. Internet không thể truyền ký tự. Tập tin không thể lưu trữ các ký tự. Không có ký tự trong cơ sở dữ liệu của bạn. Chỉ có các octet và bạn không thể coi các octet là các ký tự trong Perl. Bạn phải giải mã các octet được mã hóa thành các ký tự Perl bằng mô-đun Encode.
Nửa còn lại của vấn đề là lấy dữ liệu ra khỏi chương trình của bạn. Điều đó thật dễ dàng; bạn chỉ cần nói use Encode qw(encode)
, quyết định mã hóa dữ liệu của bạn sẽ ở đâu (UTF-8 đến các thiết bị đầu cuối hiểu UTF-8, UTF-16 cho các tệp trên Windows, v.v.), sau đó xuất kết quả encode($encoding, $data)
thay vì chỉ xuất ra $data
.
Hoạt động này chuyển đổi các ký tự của Perl, đó là những gì chương trình của bạn hoạt động, thành các octet có thể được sử dụng bởi thế giới bên ngoài. Sẽ dễ dàng hơn rất nhiều nếu chúng ta chỉ có thể gửi các ký tự qua Internet hoặc đến các thiết bị đầu cuối của mình, nhưng chúng ta không thể: chỉ các octet. Vì vậy, chúng tôi phải chuyển đổi các ký tự thành octet, nếu không kết quả là không xác định.
Để tóm tắt: mã hóa tất cả các đầu ra và giải mã tất cả các đầu vào.
Bây giờ chúng ta sẽ nói về ba vấn đề khiến điều này trở nên khó khăn. Đầu tiên là thư viện. Họ có xử lý văn bản chính xác? Câu trả lời là ... họ cố gắng. Nếu bạn tải xuống một trang web, LWP sẽ cung cấp cho bạn kết quả của bạn dưới dạng văn bản. Nếu bạn gọi đúng phương thức trên kết quả, nghĩa là (và điều đó xảy ra decoded_content
, không phải content
, đó chỉ là luồng octet mà nó nhận được từ máy chủ.) Trình điều khiển cơ sở dữ liệu có thể bị rung; nếu bạn sử dụng DBD :: SQLite chỉ với Perl, nó sẽ hoạt động, nhưng nếu một số công cụ khác đã đặt văn bản được lưu trữ dưới dạng mã hóa khác với UTF-8 trong cơ sở dữ liệu của bạn ... thì ... nó sẽ không được xử lý chính xác cho đến khi bạn viết mã để xử lý nó một cách chính xác
Xuất dữ liệu thường dễ dàng hơn, nhưng nếu bạn thấy "ký tự rộng in", thì bạn biết bạn đang làm rối mã hóa ở đâu đó. Cảnh báo đó có nghĩa là "này, bạn đang cố gắng rò rỉ các nhân vật Perl ra thế giới bên ngoài và điều đó không có ý nghĩa gì cả". Chương trình của bạn có vẻ hoạt động (vì đầu kia thường xử lý chính xác các ký tự Perl), nhưng nó rất bị hỏng và có thể ngừng hoạt động bất cứ lúc nào. Sửa chữa nó với một rõ ràng Encode::encode
!
Vấn đề thứ hai là mã nguồn được mã hóa UTF-8. Trừ khi bạn nói use utf8
ở đầu mỗi tệp, Perl sẽ không cho rằng mã nguồn của bạn là UTF-8. Điều này có nghĩa là mỗi lần bạn nói điều gì đó như my $var = 'ほげ'
, bạn đang bơm rác vào chương trình của mình, điều đó sẽ phá vỡ mọi thứ một cách khủng khiếp. Bạn không cần phải "sử dụng utf8", nhưng nếu bạn không, bạn phải không sử dụng bất kỳ ký tự khác ASCII trong chương trình của bạn.
Vấn đề thứ ba là làm thế nào Perl xử lý Quá khứ. Cách đây rất lâu, không có thứ gì như Unicode và Perl cho rằng mọi thứ đều là văn bản hoặc nhị phân Latin-1. Vì vậy, khi dữ liệu đi vào chương trình của bạn và bạn bắt đầu coi nó là văn bản, Perl coi mỗi octet là một ký tự Latin-1. Đó là lý do tại sao, khi chúng tôi hỏi về độ dài của "", chúng tôi đã nhận được 12. Perl cho rằng chúng tôi đang hoạt động trên chuỗi Latin-1 "æååã" (gồm 12 ký tự, một số ký tự không in).
Đây được gọi là "nâng cấp ngầm" và đó là một điều hoàn toàn hợp lý để làm, nhưng đó không phải là điều bạn muốn nếu văn bản của bạn không phải là tiếng Latin-1. Đó là lý do tại sao việc giải mã một cách rõ ràng đầu vào: nếu bạn không làm điều đó, Perl sẽ làm và nó có thể làm sai.
Mọi người gặp rắc rối trong đó một nửa dữ liệu của họ là một chuỗi ký tự phù hợp và một số vẫn là nhị phân. Perl sẽ diễn giải phần vẫn là nhị phân như thể văn bản Latin-1 và sau đó kết hợp nó với dữ liệu ký tự chính xác. Điều này sẽ làm cho việc xử lý các nhân vật của bạn phá vỡ chương trình của bạn một cách chính xác, nhưng thực tế, bạn chỉ chưa sửa nó đủ.
Đây là một ví dụ: bạn có một chương trình đọc tệp văn bản được mã hóa UTF-8, bạn xử lý Unicode PILE OF POO
trên mỗi dòng và bạn in nó ra. Bạn viết nó như sau:
while(<>){
chomp;
say "$_ 💩";
}
Và sau đó chạy trên một số dữ liệu được mã hóa UTF-8, như:
perl poo.pl input-data.txt
Nó in dữ liệu UTF-8 với một poo ở cuối mỗi dòng. Hoàn hảo, chương trình của tôi hoạt động!
Nhưng không, bạn chỉ đang thực hiện nối nhị phân. Bạn đang đọc octet từ tệp, xóa a \n
bằng chomp và sau đó xử lý các byte trong biểu diễn UTF-8 của PILE OF POO
ký tự. Khi bạn sửa đổi chương trình của mình để giải mã dữ liệu từ tệp và mã hóa đầu ra, bạn sẽ nhận thấy rằng bạn nhận được rác ("ð ©") thay vì poo. Điều này sẽ khiến bạn tin rằng giải mã tệp đầu vào là điều sai. Nó không thể.
Vấn đề là poo đang được nâng cấp ngầm thành latin-1. Nếu bạn use utf8
làm văn bản bằng chữ thay vì nhị phân, thì nó sẽ hoạt động trở lại!
. nó bị hỏng. Đừng lo lắng, nếu bạn đang thêm các câu lệnh mã hóa / giải mã vào chương trình của mình và nó bị hỏng, điều đó chỉ có nghĩa là bạn còn nhiều việc phải làm. dễ dàng hơn nhiều!)
Đó thực sự là tất cả những gì bạn cần biết về Perl và Unicode. Nếu bạn nói với Perl dữ liệu của bạn là gì, nó có hỗ trợ Unicode tốt nhất trong số tất cả các ngôn ngữ lập trình phổ biến. Tuy nhiên, nếu bạn cho rằng nó sẽ kỳ diệu biết loại văn bản bạn đang cho nó ăn, thì bạn sẽ bỏ rác dữ liệu của mình. Chỉ vì chương trình của bạn hoạt động hôm nay trên thiết bị đầu cuối UTF-8 của bạn không có nghĩa là chương trình sẽ hoạt động vào ngày mai trên tệp được mã hóa UTF-16. Vì vậy, hãy làm cho nó an toàn ngay bây giờ và tự cứu mình khỏi việc làm hỏng dữ liệu của người dùng!
Phần dễ dàng của việc xử lý Unicode là mã hóa đầu ra và giải mã đầu vào. Phần khó là tìm tất cả đầu vào và đầu ra của bạn và xác định mã hóa đó là gì. Nhưng đó là lý do tại sao bạn nhận được số tiền lớn :)