代码之家  ›  专栏  ›  技术社区  ›  gorilla

如何让Perl检测坏的UTF-8序列?

  •  8
  • gorilla  · 技术社区  · 14 年前

    我正在运行Perl5.10.0和Postgres8.4.3,并将字符串放入数据库中,该数据库位于 DBIx::Class .

    这些字符串应该是UTF-8格式,因此我的数据库是以UTF-8格式运行的。不幸的是,其中一些字符串是坏的,包含格式错误的UTF-8,所以当我运行它时,我得到一个异常

    DBI Exception: DBD::Pg::st execute failed: ERROR: invalid byte sequence for encoding "UTF8": 0xb5

    我认为我可以简单地忽略无效的,稍后再担心格式不正确的UTF-8,所以使用这个代码,它应该标记并忽略不正确的标题。

    if(not utf8::valid($title)){
       $title="Invalid UTF-8";
    }
    $data->title($title);
    $data->update();
    

    然而,Perl似乎认为字符串是有效的,但它仍然抛出异常。

    如何让Perl检测出坏的UTF-8?

    3 回复  |  直到 8 年前
        1
  •  8
  •   rjh    8 年前

    首先,请遵循文档-The utf8 模块应该 只有 在“use utf8;”表单中使用,以指示源代码是utf-8而不是拉丁语1。不要使用任何utf8函数。

    Perl区分字节和UTF-8字符串。在字节模式下,Perl不知道或不关心您使用的是什么编码,如果您打印它,它将使用拉丁语1。以欧元符号(_在UTF-8中,这是3个字节,0xe2、0x82、0xac。如果打印这些字节的长度,Perl将返回3。同样,它不关心编码。它可以是任何字节或任何编码,合法或非法。

    如果你使用 Encode 模块和调用 Encode::decode("UTF-8', $bytes) 您将得到一个新的字符串,它具有所谓的utf8标志集。Perl现在知道您的字符串是UTF-8格式的,将返回1的长度。

    问题是 utf8::valid 仅适用于第二种类型的字符串。您的字符串可能是第一种形式、字节模式和 utf8::有效 只对字节形式的任何内容返回true。这在Perldoc中有记录。

    解决方案是让Perl将字节字符串解码为UTF-8,并检测任何错误。这可以通过FB ou Croak完成,正如Brian D Foy解释的那样:

    my $ustring =
        eval { decode( 'UTF-8', $byte_string, FB_CROAK ) }
        or die "Could not decode string: $@";
    

    然后您可以捕获该错误并跳过那些无效的字符串。

    或者,如果您知道您的代码主要是带有一些无效序列的UTF-8,那么您可以使用:

    my $ustring = decode( 'UTF-8', $byte_string );
    

    使用默认模式 FB_DEFAULT ,将无效字符替换为u+fffd,即Unicode替换字符(带有问号的菱形)。

    在大多数情况下,您可以直接将字符串传递给数据库驱动程序。某些驱动程序可能要求您先将字符串重新编码回字节格式:

    my $byte_string = encode('UTF-8', $ustring);
    

    在调用之前,还可以使用联机的regex检查有效的utf-8序列 decode (检查其他堆栈溢出答案)。如果使用这些正则表达式,则不需要进行任何编码或解码。

    最后,请使用 UTF-8 而不是 UTF8 在你的电话里 译码 . 后者更宽松,允许一些无效的UTF-8序列(如Unicode范围之外的序列)通过。

        2
  •  8
  •   brian d foy JRFerguson    14 年前

    你的弦怎么拉?您确定Perl认为它们已经是UTF-8了吗?如果还没有解码(也就是说,八位字节被解释为某种编码),您需要自己进行解码:

        use Encode;
    
        my $ustring =
          eval { decode( 'utf8', $byte_string, FB_CROAK ) }
          or die "Could not decode string: $@";
    

    更好的是,如果您知道您的字符串源已经是utf-8,那么您需要将该源读取为utf-8。看看您拥有的代码,这些代码获取字符串,看看您是否正确地执行了这些操作。

        3
  •  2
  •   hobbs    14 年前

    作为 utf8::valid 指出,如果字符串标记为utf-8并且它是有效的utf-8,则返回true, 或者如果字符串根本不是UTF-8 . 虽然在没有看到上下文中的代码和知道数据是什么的情况下是不可能分辨出来的,但最有可能的情况是根本不需要“有效的utf8”检查;也许您只需要做

    $data->title( Encode::encode("UTF-8", $title) )