正则表达式和索引不匹配unicode字符

时间:2013-08-21 04:21:06

标签: regex string perl character-encoding

我正在编写的库中的一个函数返回一个字符串,当尝试通过正则表达式或index函数定位unicode字符时会出现问题。字符串打印正常(使用Sublime文本的控制台进行unicode打印),如下所示:

<xml>V日한ế</xml>

我正试图像这样匹配它:$string =~ m/V日한ế/。我正在使用utf8

我道歉,我无法重现最小的破坏性示例,因为当我自己构建字符串并尝试匹配它时,一切正常。我尝试使用this站点中的hexdump函数,但它为库返回的字符串中的unicode字符和我构造的字符串($string2 = 'V日한ế')打印相同的十六进制序列: 56 e6 97 a5 ed 95 9c e1 ba bf。来自图书馆的那个关闭了utf标志而构建的那个没有,但是另一个测试向我展示了那不是问题。

我对问题的根源只有一个线索:输出use re 'debug';。它给出了以下信息:

Matching REx "V%x{65e5}%x{d55c}%x{1ebf}" against "%n<xml>V%x{e6}%x{97}%x{a5}%x{ed}%x{95}%x{9c}%x{e1}%x{ba}"...

正在将正则表达式中的字符“日”打印为%x{65e5},将有问题的字符串中的字符打印为%x{e6}%x{97}。其他unicode字符的打印方式也不同。

任何有调试字符串和编码经验的人都可以告诉我为什么正则表达式和index找不到我知道的字符串中出现的unicode字符,以及如何让这些函数找到它们?

1 个答案:

答案 0 :(得分:3)

让我们制作一个可重复的测试用例:

  1. 生成输入文件:

    $ perl -E'say "<xml>V\xe6\x97\xa5\xed\x95\x9c\xe1\xba\xbf</xml>"' >test.xml
    $ cat test.xml
    <xml>V日한ế</xml>
    

    这会将一些字节写入文件。请注意,我的终端模拟器使用UTF-8。

  2. 尝试天真地匹配输入:

    $ cat test.pl
    use strict; use warnings; use utf8; use autodie; use feature 'say';
    open my $fh, "<", shift @ARGV;
    
    my $s = <$fh>;
    say "$s ", $s =~ m/V日한ế/ ? "matches" : "doesn't match";
    say "string = ", map { sprintf "\\x{%x}", ord } split //, $s;
    $ perl test.pl test.xml
    <xml>V日한ế</xml>
     doesn't match
    string = \x{3c}\x{78}\x{6d}\x{6c}\x{3e}\x{56}\x{e6}\x{97}\x{a5}\x{ed}\x{95}\x{9c}\x{e1}\x{ba}\x{bf}\x{3c}\x{2f}\x{78}\x{6d}\x{6c}\x{3e}\x{a}
    

    哦,所以文件中的字符串被视为一串字节,而不是正确解码的代码点。谁会猜到?

  3. 让我们添加:utf8 PerlIO层:

    $ cat test-utf8.pl
    use strict; use warnings; use utf8; use autodie; use feature 'say';
    open my $fh, "<:utf8", shift @ARGV;
    
    my $s = <$fh>;
    say "$s ", $s =~ m/V日한ế/ ? "matches" : "doesn't match";
    say "string = ", map { sprintf "\\x{%x}", ord } split //, $s;
    $ perl test-utf8.pl test.xml
    Wide character in say at test-utf8.pl line 5, <$_[...]> line 1.
    <xml>V日한ế</xml>
     matches
    string = \x{3c}\x{78}\x{6d}\x{6c}\x{3e}\x{56}\x{65e5}\x{d55c}\x{1ebf}\x{3c}\x{2f}\x{78}\x{6d}\x{6c}\x{3e}\x{a}
    

    现在它匹配,因为我们已经从文件中读取了正确解码的代码点。

  4. 你得到相同的输出?如果你没有得到可比较的输出,你使用的是perl / OS组合(这是Ubuntu GNU / Linux上的perl 5.18.1)。

    此代码还存在一些问题:有多种方法可以表示ế。因此,您应该规范化正则表达式和输入中的字符串:

    use Unicode::Normalize 'NFC';
    my $regex_body = NFC "V日한ế";
    my $s          = NFC scalar <$fh>;
    
    ... m/\Q$regex_body/ ...