Unicode-processing issues in Perl and how to cope with it

http://ahinea.com/en/tech/perl-unicode-struggle.html

Các vấn đề về sử dụng mã Unicode trong Perl và làm thế nào để đối phó với chúng

Perl 5.8+ đã hỗ trợ toàn diện cho Unicode và một loạt các bảng mã văn bản khác nhau. Nhưng vẫn còn nhiều người gặp khó khăn khi xử lý văn bản đa ngôn ngữ. Ở đây tôi giải thích những vấn đề phổ biến nhất và cung cấp các giải pháp.

 

21 tháng 11 năm 2013. Một số phần chưa chính xác trong các văn bản của bài viết và trong các spript thử nghiệm đã được sửa chữa.

 

Bài viết này được dịch sang ngôn ngữ Serbo-Croatian bởi Anja Skrba từ trang Webhostinggeeks.com.

 

Phiên bản cũ của bài viết này ở đây. Nó có cấu trúc không tốt lắm, nhưng cung cấp bổ sung một số chi tiết liên quan đến unicode của phiên bản perl 5.6.1.

 

Một loạt các manpage perldoc tô đậm và giải thích mã unicode của Perl hỗ trợ. perluniintro, perlunicode, module Encode, chức năng binmode() .Và danh sách này chưa đầy đủ. Vấn đề quan trọng với các tài liệu này là dung lượng của nó. Hầu hết các lập trình viên thậm chí còn không đọc hết tất cả, bởi vì để bắt đầu làm việc với Unicode bạn chỉ cần biết một số thông tin cơ bản và quy tắc.
Tôi đã có nhiều kinh nghiệm loại rắc rối với mã Unicode trong Perl, trong một số dự án. Hai vấn đề chính tôi thấy được là:

  • Dữ liệu UTF-8 bị mã hóa gấp đôi hoặc các mã hóa khác bị đọc sai
  • Bị lỗi cảnh báo “ Wide character in print

Hai vấn đề này có liên quan chặt chẽ và thường giải quyết bằng các bước tương tự nhau.
Đọc hoặc ít nhất là duyệt qua các trang manpage liên quan vẫn là một cách tốt để hiểu và giải quyết vấn đề mã Unicode của bạn. Nếu bạn không có thời gian để làm điều đó, hãy đọc tiếp.

Bạn có thể đọc phần này và đi sâu vào các chi tiết kỹ thuật và đặc tính của perl và mã unicode. Hoặc bạn có thể thuê tôi để sửa chữa mã của bạn.

 

Giới thiệu vấn đề: ví dụ

 

Hãy tưởng tượng hai biến đơn giản với văn bản Unicode trong đó. Và bạn in các biến ra output chuẩn. Việc gì có thể dễ dàng hơn thế? ..

 

#!/usr/bin/perl

 

my $ustring1 = “Hello \x{263A}!\n”;  

my $ustring2 = <DATA>;

 

print “$ustring1$ustring2”;

__DATA__

Hello !

Nguồn

 

Cả hai biến ở đây có chứa cùng một dữ liệu: chuỗi “Hello “ tiếp theo là ký tự Unicode WHITE SMILING FACE U + 263A, một dấu chấm than và một ký tự xuống dòng mới. Phần __DATA__ ( $ustring2 ) là phần mã hóa UTF-8.

 

Nhưng khi chúng ta in nó ra, chuỗi đầu tiên đi ra tốt đẹp và chuỗi thứ hai thì bị cắt mất 1 phần. Điều này bởi vì Perl biết rằng chuỗi đầu tiên là một chuỗi Unicode và được lưu trữ nội bộ trong UTF-8. Nhưng nó không biết mã hóa chuỗi thứ hai. Khi nó xây dựng một chuỗi lớn hơn cho việc in ấn, nó tái mã hóa chuỗi thứ hai vào UTF-8, sai lầm.

 

Ngoài ra, nó in một cảnh báo: Wide character in print at unitest1.pl line 6, <DATA> line 1.. Chúng ta sẽ xem xét nó sau, khi chúng ta sửa chữa output trên của chúng ta.

 

Bạn có thể sửa chữa lỗi này bằng cách tránh các sự nối tiếp:

#!/usr/bin/perl

 

my $ustring1 = “Hello \x{263A}!\n”;  

my $ustring2 = <DATA>;

 

print $ustring1, $ustring2;

__DATA__

Hello !

Nguồn

 

Nhưng đây không phải là một giải pháp. Đôi khi bạn chỉ đơn giản là không thể tránh được sự nối tiếp; nó là một sự vận hành cơ bản. Ngoài ra, nó là dễ bị lỗi và không có chứng minh nào trong tương lai.

 

Tại sao vấn đề này xảy ra

 

Đầu tiên, một số thông tin cơ bản.

 

Có sự khác biệt giữa byte và ký tự. Các ký tự là các ký tự của mã Unicode. Một ký tự có thể được đại diện bởi một vài byte khi lưu trữ, in hoặc gửi qua mạng. Chính xác là làm thế nào là một ký tự được chuyển đổi thành byte phụ thuộc vào các phương thức mã hóa được sử dụng. UTF-8 chỉ là một trong những cách đại diện cho các ký tự Unicode.

 

Perl có một “utf8” dán cờ cho mỗi giá trị vô hướng, có thể là “on” hoặc “off”. Lệnh “On” của lá cờ này cho perl xử lý giá trị như là một chuỗi các ký tự Unicode. Nếu không, nó chỉ là một loạt các byte.

 

Nếu bạn lấy một chuỗi có dán cờ utf8 off và nối nó với một chuỗi có cờ utf8 on, perl sẽ chuyển đổi chuỗi đầu tiên thành mã Unicode.

 

Điều này nghe có vẻ ổn và rõ ràng. Nhưng sau đó bạn nghĩ: Làm thế nào? Perl sẽ cần phải biết mã hóa các chuỗi dữ liệu trước khi chuyển đổi nó. Và perl sẽ cố gắng phán đoán. Và điều này thường là lý do của các vấn đề.

 

Các thuật toán được sử dụng khi perl phán đoán là một tài liệu (sử dụng một số mặc định và có thể kiểm tra nội vùng của bạn), nhưng đề chắc chắn của tôi là: không bao giờ để perl làm điều đó. Nếu không, có cơ hội rất LỚN rằng bạn sẽ nhận được mã hóa chuỗi UTF-8 gấp đôi, hoặc dữ liệu khác sẽ bị đọc sai.

 

Giải pháp: luôn luôn làm cho dữ liệu mã hóa rõ ràng, cho cả input và output của bạn.

 

Giải pháp # 1: Chuyển đổi chuỗi thành mã Unicode

 

Một giải pháp có thể để nói với perl rằng $ustring2 chứa dữ liệu mã Unicode trong mã hóa UTF-8. Có một vài cách để làm điều đó; cách chính thống là thông qua chức năng mã hóa decode_utf8():

 

#!/usr/bin/perl

 

use Encode;

my $ustring1 = “Hello \x{263A}!\n”;  

my $ustring2 = <DATA>;

$ustring2 = decode_utf8( $ustring2 );

 

print “$ustring1$ustring2”;

__DATA__

Hello !

Nguồn

 

Trong trường hợp đơn giản này cả hai cách sẽ hoàn thành tốt công việc, nhưng có thể khá tẻ nhạt nếu input của bạn rất phong phú. Và nó vẫn còn hiển thị các cảnh báo ” Wide character “.

 

Nhưng đây là những gì bạn nên luôn luôn làm cho các dữ liệu quốc tế bạn nhận được từ các module Perl khác, như từ cơ sở dữ liệu.

 

Bạn không nên quên, mặc dù rằng không phải mọi chuỗi của byte là UTF-8 khả dụng. Vì vậy, các decode_utf8 () hoạt động có thể thất bại. Xem Encode perldoc để biết các chi tiết xử lý lỗi.

 

(Một cách khác để làm phép perl chấp nhận các dữ liệu UTF-8 như vậy là với một gói “U0C*”, giải nén gói “C*” hack. Nhưng có lẽ bạn không nên làm điều đó.)

 

Nếu bạn nhận được dữ liệu trong một bảng mã khác (không phải UTF-8), chuyển đổi nó sang Unicode một cách rõ ràng. Một lần nữa, module Encode, chức năng decode():

 

require Encode;

my $ustring = Encode::decode( ‘iso-8859-1’, $input );

Một ví dụ khác: dữ liệu UTF-8 từ CGI

 

Trong ACIS chúng tôi sản xuất các trang HTML trong mã UTF-8. Chúng tôi hy vọng các hình thức input HTML cũng ở dạng UTF-8. Để vận dụng nó, chúng tôi thiết lập perl về việc mã hóa như sau:

 

require Encode;

require CGI;

my $query = CGI ->new;

my $form_input = {};  

foreach my $name ( $query ->param ) {

 my @val = $query ->param( $name );

 foreach ( @val ) {

   $_ = Encode::decode_utf8( $_ );

 }

 $name = Encode::decode_utf8( $name );

 if ( scalar @val == 1 ) {   

   $form_input ->{$name} = $val[0];

 } else {                      

   $form_input ->{$name} = \@val;  # save value as an array ref

 }

}

 

Thiết lập này là sẵn sàng và an toàn sử dụng các thông số input.

 

Giải pháp # 2: Chỉ định lớp mã hóa IO cho filehandle của bạn

 

Bắt đầu với phiên bản 5.8 trong Perl một filehandle có thể có một mã hóa quy định cho nó. Perl sau đó sẽ chuyển đổi tất cả các input từ các tập tin nội bộ của mình thành bảng mã Unicode một cách tự động. Nó sẽ đánh dấu các giá trị đọc từ nó cho phù hợp với lá cờ utf8. Tương tự, perl có thể chuyển đổi output cho một mã hóa cụ thể cho một filehandle. Ngoài ra, perl còn kiểm tra dữ kiện bạn cho output là hợp lệ cho việc mã hóa filehandle.

 

Vì vậy, nếu bạn đọc dữ liệu từ một tập tin hay dòng input khác, và bạn mong đợi dữ liệu UTF-8 ở đó, hãy cảnh báo perl:

 

if ( open( FILE, “<:utf8”, $fname ) ) {

 . . .

}

hoặc, trong trường hợp thử nghiệm đơn giản của chúng tôi,

 

#!/usr/bin/perl

 

my $ustring1 = “Hello \x{263A}!\n”;  

binmode DATA, “:utf8”;

my $ustring2 = <DATA>;

 

print “$ustring1$ustring2”;

__DATA__

Hello !

Nguồn

 

Bạn nên in hai dòng bằng nhau, nhưng nó vẫn sẽ hiện các cảnh báo gây phiền toái. Đó là bởi vì chúng ta vẫn in giá trị chứa mã unicode cho filehandle mà không chuẩn bị sẵn sàng cho điều đó: các STDOUT. (Và nó xảy ra hiển nhiên, kể từ khi bản in print được mặc định.) đưa đến đó để xem các cách sửa lỗi cho các cảnh báo ngay bây giờ.

 

Tương tự như vậy, nếu bạn mở một tập tin như:

 

open FILE, “<:encoding(iso-8859-7)”, $filename;

 

Nội dung của nó sẽ được giả định là trong mã hóa iso-8859-7. Perl sẽ sử dụng nó để diễn giải dữ liệu của tập tin một cách chính xác, nghĩa là để chuyển đổi nó sang UTF-8 nội bộ.

(Ở đây và bên dưới, các bảng mã ISO-8859-7 chỉ là một ví dụ. Bất kỳ của các bảng mã perl-hỗ trợ có thể được sử dụng.)

 

Giải pháp # 3: Thiết lập Global Unicode trong Perl

 

Và đây là một cách khác để tiếp cận vấn đề mã hóa/giải mã của bạn. Đó là lệnh perl để xử lý các input và output tất cả các chương trình của bạn dưới dạng UTF-8 theo mặc định.-C là một chuyển đổi perl mà cho phép của bạn làm điều đó. Chỉ cần đặt -CS trên dòng lệnh perl.

Ngoài ra, sử dụng biến môi trường PERL_UNICODE. Nó phải được đặt trong môi trường nơi bạn thực hiện perl, ví dụ:

 

god@world:~$ PERL_UNICODE=S perl script.pl

 

Sẽ lệnh cho perl nhận định mã UTF-8 cho tất cả các input và output của các filehandle trong script và các module đã sử dụng, theo mặc định. (Thật không may và trái với mong đợi của tôi điều này không có tác động vào các DỮ LIỆU đặc biệt của filehandle. Vì vậy, đây không phải là một giải pháp cho vấn đề trưng bày script của chúng ta.)

 

Bạn cũng có thể chỉ định UTF-8-Ness chi cho stdin của bạn hoặc chỉ cho stdout hoặc chỉ cho stderr. Đọc phần -C trong perlrun để biết đầy đủ chi tiết.

 

Cảnh báo Wide character in print

 

Các cảnh báo sẽ hiển thị khi bạn xuất ra một chuỗi Unicode đến một filehandle không sử dụng unicode. một “filehandle không sử dụng unicode?” là gì, bạn hỏi. Đó là một filehandle không có sự tương thích giữa Unicode với lớp IO vào nó (xem Giải pháp # 2 ở trên).

 

Cách đúng để sửa lỗi này là xác định mã hóa output một cách rõ ràng, với chức năng binmode() hoặc trong lệnh open() của bạn. Ví dụ, mở tập tin của bạn theo cách này:

 

open FILE, “>:utf8”, $filename;

 

Để in UTF-8 ra output chuẩn (hay sai số chuẩn), như trong trường hợp của chúng ta, chúng ta làm như sau:

 

#!/usr/bin/perl

 

my $ustring1 = “Hello \x{263A}!\n”;  

binmode DATA, “:utf8”;

my $ustring2 = <DATA>;

binmode STDOUT, “:utf8”;

print “$ustring1$ustring2”;

__DATA__

Hello !

Nguồn

 

Bây giờ cuối cùng đã in được hai dòng bằng nhau (một cách chính xác) và xuất ra mà không còn cảnh báo hiển thị!

 

Cách sai lầm để tránh những cảnh báo là tắt cờ utf8 trên dữ liệu được-in của bạn. Sau đó, các ký tự sẽ biến thành byte, và perl sẽ đẩy chúng vào một byte-filehandle một cách trơn tru. Nhưng bạn không cần điều đó, thực sự là như vậy.

 

Mặt khác, nếu bạn mở một tập tin như sau:

 

open FILE, “>:encoding(iso-8859-7)”, $filename;

 

Những thứ bạn in sẽ được output trong bảng mã ISO-8859-7, chuyển mã tự động. ISO-8859-7 không phải là một bảng mã tương thích Unicode, vì vậy bạn sẽ không có khả năng output ký tự Unicode vào nó mà không bị cảnh báo.

 

Các chiến lược đúng đắn: tóm tắt

 

Nếu có thể, sử dụng một bảng mã Unicode (như UTF-8) để lưu trữ và xử lý dữ liệu của bạn. Luôn đảm bảo perl biết đâu là mã hóa dữ liệu của bạn đi vào và đi ra. Hãy chắc chắn rằng tất cả các giá trị vô hướng của bạn chứa mã Unicode, có cờ utf8 on. Sau đó, bạn có thể nối chuỗi một cách an toàn. Sau đó, bạn có thể sử dụng biểu thức thông thường liên quan đến Unicode, trong đó cung cấp cho bạn quyền hạn rất lớn cho xử lý văn bản quốc tế (đa ngôn ngữ).

 

Để đạt được điều đó, bạn có thể cần phải biết tất cả những cách dữ liệu được đưa vào chương trình của bạn. Ngay sau khi bạn nhận được một số input, đánh dấu nó như là Unicode hoặc chuyển đổi nó sang Unicode và ngủ một giấc.

 

Đôi khi dữ liệu đi vào chương trình của bạn đã là mã Unicode và bạn không nên lo lắng. Ví dụ, phân tích cú pháp XML trả lại cho bạn giá trị chuỗi với cờ utf8 “on”. (Trừ khi bạn làm điều gì đó khác lạ, giống như trả nó về ở dạng ban đầu từ các phân tích cú pháp, điều bạn không nên làm dù gì đi nữa.) Trong ví dụ trên một cách rõ ràng chúng ta đã bao gồm một ký tự unicode thành một chuỗi ($ustring1) và perl biết những mã hóa của nó.

 

Nhưng khi bạn đọc dữ liệu từ dòng input, từ một cơ sở dữ liệu hoặc từ các biến môi trường (như các thông số trong CGI), bạn cần phải cho perl biết về những mã hóa của nó.

 

Sử dụng biến môi trường PERL_UNICODE để buộc lớp UTF-8 IO trên input và/hoặc output của filehandles.

 

Những bài tìm hiểu thêm

 

Man pages (perldocs):

 

 

Các bài khác:

 

 

Ý kiến luôn được chào đón.

Advertisements