perl utf8 encoding decoding HTML::Entities Perl中字串編碼的處理

zk1878發表於2011-04-07

轉自:http://hi.baidu.com/youzhch/blog/item/991ad4357baeb00491ef3965.html

#!/usr/bin/perl -w
use CGI;
use Encode;
use HTML::Entities;
use utf8;
$query = new CGI;

 

 

print $query->header(  -charset=>'utf-8' ); 

$a = $query->param('word');
my $a = decode("utf8", $a);   # make a string  utf

$b = encode_entities($a);

 


#$secretword = $query->param('word');
$remotehost = $query->remote_host();

$secretword = encode_entities($secretword);


#$a = "utf8 &#xHHHH สิ่งเล็กเล็กที่เรียกว่า•รัก   中文 行(ゆ)こう行こうみんなわくわく";
#$b = encode_entities($a);

#print $secretword."\n";    #輸出中文  在瀏覽器裡顯示為“中文”

print $a."\n";
print $b.$b."\n";    #輸出中文  在瀏覽器裡顯示為“中文”
#$b =~ s/&/&/g;
#print $b."\n";    #輸出中文  在瀏覽器裡顯示為中文

 

print<<"[HTML]";               
  

<form name="f1" method="post" action="utf8hhhh.pl">
  <p>word: <input name="word" type="text" value="$b"> 
 <input type="submit" name="Submit" value=" 查詢 "></p>

</form><br>
$b    #輸出&#x4E2D;&#x6587;  在瀏覽器裡顯示為“中文”
[HTML]

 

 


 

###########

 這種東西還真的少有人寫,其實是轉換成16進位制的,研究後把程式碼貼給你,希望給你有用,也給後人做參考(程式碼部分請儲存為UTF8格式,為便於顯示,控制檯輸出我指定為GB2312),如果格式為GB2312,需要載入Encode模組,使用語句encode("utf-8", decode("gb2312", $str))進行轉換。

#!/usr/bin/perl
$|=1;
use URI::Escape qw(uri_escape_utf8);
#  use encoding 'utf8', STDIN=>'utf8', STDOUT=>'gb2312';  #  original code
use encoding 'utf8', STDIN=>'utf8', STDOUT=>'utf8';
my $str="~~我有一所房子,面朝大海,春暖花開...";
for my $i(0..length($str)-1) {
 if (ord(substr($str,$i,1))<0xFF){
  print substr($str,$i,1);
 } else {
  print sprintf("&#x%1x;",ord(substr($str,$i,1)));
 }
}

 

#################Perl中字串編碼的處理

在Perl看來, 字串只有兩種形式. 一種是octets, 即8位序列, 也就是我們通常說的位元組陣列. 另一種utf8編碼的字串, perl管它叫string. 也就是說: Perl只熟悉兩種編碼: Ascii(octets)和utf8(string).

utf8 flag
在perl內部, 字串結構由兩部分組成: 資料和utf8 flag. 比如字串"中國"在perl內部的儲存是這樣:
utf8 flag 資料
On 中國
假如utf8 flag是On的話, perl就會把中國當成utf8字串來處理, 假如utf8 flag為Off, perl就會把他當成octets來處理. 所有字串相關的函式包括正規表示式都會受utf8 flag的影響. 讓我們來看個例子:
程式程式碼:
use Encode;
use strict;
my $str = "中國";
Encode::_utf8_on($str);
print length($str) . "\n";
Encode::_utf8_off($str);
print length($str) . "\n";
執行結果是:
程式程式碼:
2
6
這裡我們使用Encode模組的_utf8_on函式和_utf8_off函式來開關字串"中國"的utf8 flag. 可以看到, utf8 flag開啟的時候, "中國"被當成utf8字串處理, 所以其長度是2. utf8 flag關閉的時候, "中國"被當成octets(位元組陣列)處理, 出來的長度是6(我的編輯器用的是utf8編碼, 假如你的編輯器用的是gb2312編碼, 那麼長度應該是4).

再來看看正規表示式的例子:
程式程式碼:
use Encode;
use strict;
my $a = "china----中國";
my $b = "china----中國";
Encode::_utf8_on($a);
Encode::_utf8_off($b);
$a =~ s/\W //g;
$b =~ s/\W //g;
print $a, "\n";
print $b, "\n";
執行結果:
程式程式碼:
Wide character in print at unicode.pl line 10.
china中國
china
結果第一行是一條警告, 這個我們稍後再討論. 結果的第二行說明, utf8 flag開啟的情況下, 正規表示式中的\w能夠匹配中文, 反之則不能.
如何確定一個字串的utf8 flag是否已開啟? 使用Encode::is_utf8($str). 這個函式並不是用來檢測一個字串是不是utf8編碼, 而是僅僅看看它的utf8 flag是否開啟.

eq是一個字串比較操作符, 只有當字串的內容一致並且utf8 flag的狀態也是一致的時候, eq才會返回真.

unicode轉碼
假如你有一個字串"中國", 它是gb2312編碼的. 假如它的utf8 flag是關閉的, 它就會被當成octets來處理, length()會返回4, 這通常不是你想要的. 而假如你開啟它的utf8 flag, 則它會被當做utf8編碼的字串來處理. 由於它本來的編碼是gb2312的, 不是utf8的, 這就可能導致錯誤發生. 由於gb2312和utf8內碼範圍部分重疊, 所以很多時候, 不會有錯誤報出來, 但是perl可能已經錯誤地拆解了字元. 嚴重的時候, perl會報警, 說某個位元組不是合法的utf8內碼.
解決的方法很顯然, 假如你的字串本來不是utf8編碼的, 應該先把它轉成utf8編碼, 並且使它的utf8 flag處於開啟狀態. 對於一個gb2312編碼的字串, 你可以使用
程式程式碼:
$str = Encode::decode("gb2312", $str);
來將其轉化為utf8編碼並開啟utf8 flag. 假如你的字串編碼本來就是utf8, 只是utf8 flag沒有開啟, 那麼你可以使用以下三種方式中的任一種來開啟utf8 flag:
程式程式碼:
$str = Encode::decode_utf8($str);
$str = Encode::decode("utf8", $str);
Encode::_utf8_on($str);
最後一種方式效率最高, 但是官方不推薦. 以下劃線開頭的函式是內部函式, 出於禮貌, 一般不從外部呼叫.

字串連線
. 是字串連線操作符. 連線兩個字串時, 假如兩個字串的utf8 flag都是Off, 那麼結果字串也是Off. 假如其中任何一個字串的utf8 flag是On的話, 那麼結果字串的utf8 flag將是On. 連線字串並不會改變它們原來的編碼, 所以假如你把兩個不同編碼的字串連在一起, 那麼以後不管對這個字串怎麼轉碼, 都總會有一段是亂碼. 這種情況一定要避免, 連線兩個字串之前應該確保它們編碼一致. 如有必要, 先進行轉碼, 再連線字串.

perl unicode程式設計基本原則
對於任何要處理的unicode字串, 1)把它的編碼轉換成utf8; 2)開啟它的utf8 flag

字串來源
為了應用上面說到的基本原則, 我們首先要知道字串本來的編碼和utf8 flag開關情況, 這裡我們討論幾種情況.
1) 命令列引數和標準輸入. 從命令列引數或標準輸入(STDIN)來的字串, 它的編碼跟locale有關. 假如你的locale是zh_CN或zh_CN.gb2312, 那麼進來的字串就是gb2312編碼, 假如你的locale是zh_CN.gbk, 那麼進來的編碼就是gbk, 假如你的編碼是zh_CN.UTF8, 那進來的編碼就是utf8. 不管是什麼編碼, 進來的字串的utf8 flag都是關閉的狀態.
2) 你的原始碼裡的字串. 這要看你編寫原始碼時用的是什麼編碼. 在editplus裡, 你可以通過"檔案"->"另存為"檢視和更改編碼. 在linux下, 你可以cat一個原始碼檔案, 假如中文正常顯示, 說明原始碼的編碼跟locale是一致的. 原始碼裡的字串的utf8 flag同樣是關閉的狀態.
假如你的原始碼裡含有中文, 那麼你最好遵循這個原則: 1) 編寫程式碼時使用utf8編碼, 2)在檔案的開頭加上use utf8;語句. 這樣, 你原始碼裡的字串就都會是utf8編碼的, 並且utf8 flag也已經開啟.
3) 從檔案讀入. 這個毫無疑問, 你的檔案是什麼編碼, 讀進來就是什麼編碼了. 讀進來以後, utf8 flag是off狀態.
4) 抓取網頁. 網頁是什麼編碼就是什麼編碼, utf8 flag是off狀態. 網站的編碼可以從響應頭裡或者html的http://www.sina.com.cn";
eval {my $str2 = $str; Encode::decode("gbk", $str2, 1)};
print "not gbk: [url=]$@\n[/url]" if $@;
eval {my $str2 = $str; Encode::decode("utf8", $str2, 1)};
print "not utf8: [url=]$@\n[/url]" if $@;
eval {my $str2 = $str; Encode::decode("big5", $str2, 1)};
print "not big5: [url=]$@\n[/url]" if $@;
輸出:
程式程式碼:
not utf8: utf8 "\xD0" does not map to Unicode at /usr/local/lib/perl/5.8.8/Encode.pm line 162.
not big5: big5-eten "\xC8" does not map to Unicode at /usr/local/lib/perl/5.8.8/Encode.pm line 162.
我們給decode函式傳遞了第三個引數, 要求有異常字元的時候報錯. 我們用eval捕捉錯誤, 轉碼失敗說明字串本來不是這種編碼. 另外注重我們每次都把$str拷貝到$str2, 這是因為decode第三個引數為1時, decode以後, 傳給它的字串引數(第二個引數會被清空). 我們拷貝一下, 這樣每次被清空的都是$str2, $str不變.
來看結果, 既然不是utf8, 也不是big5, 那就應該是gbk了. 對於其他不知編碼的字串, 也可以使用這種方法來猜. 不過因為幾種編碼的內碼範圍都差不多, 所以假如字串比較短, 就可能出不了異常字元, 所以這個方法只適用於大段的文字.

輸出字串
在程式內被正確地處理後, 要展現給使用者. 這時我們需要把字串從perl internal form轉化成使用者能接受的形式. 簡單地說, 就是把字串從utf8編碼轉換成輸出的編碼或表現介面的編碼. 這時候, 我們使用$str = Encode::encode(charset, $str);. 同樣可以分為幾種情況.
1) 標準輸出. 標準輸出的編碼跟locale一致. 輸出的時候utf8 flag應該關閉, 不然就會出現我們前面看到的那行警告:
程式程式碼:
Wide character in print at unicode.pl line 10.
2) GUI程式. 這個應該是不用幹什麼, utf8編碼, utf8 flag開啟就行. 沒有實際測試過.
3) 做http post. 看網頁表單要求什麼編碼. utf8 flag開或關無所謂, 因為http post傳送出去的只是字串中的資料部分, 不管utf8 flag.
PerlIO
PerlIO為我們的輸入/輸出轉碼提供了便利. 它可以針對某個檔案控制程式碼, 輸入的時候自動幫你轉碼並開啟utf8 flag, 輸出的時候, 自動幫你轉碼並關閉utf8 flag. 假設你的終端locale是gb2312, 看下面的例子:
程式程式碼:
use strict;
binmode(STDIN, ":encoding(gb2312)");
binmode(STDOUT, ":encoding(gb2312)");
while (<>) {
chomp;
print $_, length, "\n";
}
執行後輸入"中國", 結果:
程式程式碼:
中國2
這樣我們就省去了輸入和輸出時轉碼的麻煩. PerlIO可以作用於任何檔案控制程式碼, 具體請參考perldoc PerlIO.

相關API
都是Encode模組的:
$octets = encode(ENCODING, $string [, CHECK]) 把字串從utf8編碼轉成指定的編碼, 並關閉utf8 flag.
$string = decode(ENCODING, $octets [, CHECK]) 把字串從其他編碼轉成utf8編碼, 並開啟utf8 flag, 不過有個例外就是, 假如字串是僅僅ascii編碼或EBCDIC編碼的話, 不開啟utf8 flag.
is_utf8(STRING [, CHECK]) 看看utf8 flag是否開啟. 假如第二個引數為真, 則同時檢查編碼是否符合utf8. 這個檢測不一定準確, 跟decode方式檢測效果一樣.
_utf8_on(STRING) 開啟字串的utf flag
_utf8_off(STRING) 關閉字串的utf flag
最後兩個是內部函式, 不推薦使用.
參考perldoc Encode.

utf8和utf-8
前面我們提到的一直都是utf8. 在perl中, utf8和utf-8是不一樣的. utf-8是指國際上標準的utf-8定義, 而utf8是perl在國際標準上做了一些擴充套件, 能相容的內碼要比國際標準的多一些. perl internal form使用的是utf8. 另外順便提一下, 字符集的名稱是不區分大小寫的並且"_"和"-"是等價的.

EBCDIC是一套遺留的寬字元解決方案, 不同於unicode, 它不是Ascii的超集. 上面介紹的方案並不完全適用於EBCDIC. 關於EBCDIC, 請參考perldoc perlebcdic.

 

相關文章