Wednesday, October 20, 2004

[Perl] Escape Full Big5 Character for R.E. in Perl under 5.8

由於 perl 5.8 以下(不包括 5.8),其字串是 byte 的組成單位是 byte 而非 character,所以碰上內含有 regular expression 特殊字元的 big5 字,就會造成 regular expression 判斷出錯。例如:

print (($s =~ m/會議/) ? 'match' : 'no match');

不管 $s 是否含有「會議」一詞,皆會印出 'match'。這是因為「會」字的第二個 byte,是個 '|',而 '|' 在 perl regular expression 裡,代表著特殊的意義。這個問題的通常解法,是將含有 regular expression 特殊字元的全形 Big5 字元,改寫成以 hex 數字表示的形式。例如,m/會議/ 就可以被改寫成 m/[\xB7][\x7C]議/。不過,如果是程式裡面的 regular expression 那還好,見一個改一個便是。可是,若要 match 的字串是 run-time 才能知道的,好比說 user input 的話,這招就沒效了。

這問題的終極且最好的解法,當然是換用 perl 5.8。然而,換用 perl 5.8,可能代表著整套程式都必須跟著升級,而這通常會是很麻煩的一件事,而且,也不一定每一台機器都有灌 perl 5.8。因此,我們需要另一個方法來迴避這個問題。

要迴避這個問題,其實策略也很簡單:「(只)檢查每一個全形 big5 字元,若其內含有 r.e. 特殊字元,便將之代換成以 hex 數字表示的形式」。因此,我們有了以下的 function:

my $__FULLBIG5CHARS = q{
# Table adopted from http://freebsd.sinica.edu.tw/zh-tut/big5.html
# (http://netlab.cse.yzu.edu.tw/~statue/freebsd/zh-tut/big5.html)
#
#    第一位元組      第二位元組          字區                                                  制訂
# ----------------------------------------------------------------------------------------------------
 ([\xA1-\xA2][\x40-\x7E\xA1-\xFE])| # 各種符號區                                            Big5_1984
 (     [\xA3][\x40-\x7E\xA1-\xBF])| # 各種符號區 (?]括標點符號、ASCII 全形符號、注音符號等) Big5_1984
 (     [\xA3]              [\xE1])| # 歐元符號                                              CP950
 ([\xA4-\xC5][\x40-\x7E\xA1-\xFE])| # 常用字區                                              Big5_1984
 (     [\xC6]         [\x40-\x7E])| # 常用字區                                              Big5_1984
 (     [\xC6]         [\xA1-\xFE])| # 罕用符號區                                            Big5_ETen
 (     [\xC7][\x40-\x7E\xA1-\xFE])| # 罕用符號區 (?]括日文、俄文等)                         Big5_ETen
 (     [\xC8][\x40-\x7E\xA1-\xD3])| # 罕用符號區 (?]括俄文、輸入法特殊符號等)               Big5_ETen
 ([\xC9-\xF8][\x40-\x7E\xA1-\xFE])| # 次常用字區                                            Big5_1984
 (     [\xF9][\x40-\x7E\xA1-\xD5])| # 次常用字區                                            Big5_1984
 (     [\xF9]         [\xD6-\xDC])| # 七個擴充字 (碁, 銹, 裏, 墻, 恒, 粧, 嫺)               Big5_ETen
 (     [\xF9]         [\xDD-\xFE])| # 表格符號區                                            Big5_ETen
};

sub mmi_escape_big5_re
{
 my ($mbcs) = @_;

 die if (!defined($mbcs));

 # One character per list element.
 my @chars = ();
 while ($mbcs =~ /\G($__BIG5CHARS)/gox) {
    push @chars, $&;
 }

 my $str = '';
 for my $char (@chars) {
    if (length($char) > 1) {
       if ($char =~ m/[\[|\^\$]/o) {
          my $c0 = sprintf('[\x%X]', ord(substr($char, 0, 1)));
          my $c1 = sprintf('[\x%X]', ord(substr($char, 1, 1)));
          $char = "$c0$c1";
       }
       else {
       }
    }
    $str .= $char;
 }

 return $str;
}

使用這個 function 很簡單,在用之前,把要用來 match 的變數,用這個 function 先「過濾」一遍就可以了。例如:

$content = 'blah~ blah~ blah~';
while ($input = <>) {
 chomp($input);
 $re = mmi_escape_big5_re($input);
 if ($content =~ m/$re/) {
    print "There are '$input' in \$content.\n";
 }
}

Comments: Post a Comment

Subscribe to Post Comments [Atom]





<< Home

This page is powered by Blogger. Isn't yours?

Subscribe to Comments [Atom]