如何让Perl检测到错误的UTF-8序列?

时间:2010-04-16 22:20:19

标签: perl unicode utf-8

我正在运行Perl 5.10.0和Postgres 8.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 个答案:

答案 0 :(得分:8)

首先,请按照文档进行操作 - 在{use utf8;'中utf8模块应使用表单,表示您的源代码是UTF-8而不是Latin-1。不要使用任何utf8函数。

Perl区分字节和UTF-8字符串。在字节模式下,Perl不知道或不关心您正在使用的编码,如果您打印它将使用Latin-1。以欧元符号(€)为例。在UTF-8中,这是3个字节,0xE2,0x82,0xAC。如果打印这些字节的长度,Perl将返回3.再次,它不关心编码。它可以是任何字节或任何编码,合法或非法。

如果您使用Encode模块并调用Encode::decode("UTF-8', $bytes),您将获得一个新字符串,其中设置了所谓的UTF8标志。 Perl现在知道你的字符串是UTF-8,并且返回长度为1。

utf8::valid仅适用于第二种字符串的问题。你的字符串可能是第一种形式,字节模式,而utf8::valid只是以字节形式返回true。这在perldoc中有记载。

解决方案是让Perl将您的字节字符串解码为UTF-8,并检测任何错误。这可以通过FB_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 REPLACEMENT CHARACTER(带有问号的菱形)。

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

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

还有一些在线正则表达式可用于在调用decode之前检查有效的UTF-8序列(检查其他Stack Overflow答案)。如果使用这些正则表达式,则无需进行任何编码或解码。

最后,请在拨打UTF-8时使用utf8而不是decode。后者更宽松,允许允许一些无效的UTF-8序列(例如Unicode范围之外的序列)。

答案 1 :(得分:8)

你是如何得到你的琴弦的?你确定Perl认为他们已经是UTF-8吗?如果它们尚未解码(即八位字节被解释为某种编码),则需要自己完成:

    use Encode;

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

更好的是,如果您知道您的字符串源已经是UTF-8,则需要将该源读取为UTF-8。查看您获得字符串的代码,看看您是否正确执行此操作。

答案 2 :(得分:2)

正如utf8::valid的文档指出的那样,如果字符串被标记为UTF-8并且它是有效的UTF-8,或者字符串根本不是UTF-8,则返回true / em>的。虽然如果没有在上下文中看到代码并且知道数据是什么就不可能分辨,但很可能你想要的不是“有效的utf8”检查;可能你只需要做

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