为什么Perl XML :: LibXML将UTF8更改为8859-1?

时间:2016-08-11 13:41:25

标签: xml perl utf-8

使用此输入文件

<?xml version="1.0" encoding="UTF-8"?>
<entry>
   <title>ú</title>
</entry>

和这段代码,

my $raw_xml = read_file("test.xml", binmode => 'raw');
print "$raw_xml\n";
$raw_xml =~ /<title>(.*?)</;
print "Regex finds [$1]\n";      # prints u+accent to UTF8 terminal

my $dom  = XML::LibXML->load_xml(string => $raw_xml);
my $xpc = XML::LibXML::XPathContext->new($dom);
my ($entry) = $xpc->findnodes('entry');
my $title = $xpc->findvalue('title', $entry) || '';

print "title is now [$title]\n"; # prints garbage character to UTF8 terminal, u+accent to ISO-8859-1 terminal

在哪里/为什么非常好的utf8被翻译成8位字符集之一(我假设它是8859-1,可能是cp1252等)?

我通过Google发现的所有内容都表明它应该从头到尾都是utf8。但显然不是。

注意:如果我使用binmode在文件句柄上打开文件并将其传递给load_xml,则行为完全相同;我碰巧在实际代码中将xml存储在内存中 - 这也意味着我可以使用上面的正则表达式进行验证。

2 个答案:

答案 0 :(得分:5)

你有两个错误可以在第一次测试中取消产生正确的输出。

您的本土解析器无法解码文档

您可以通过将/<title>(.*?)</更改为/<title>(.)</来发现此错误。它不是按预期获得第一个字形(ú),而只获取其编码的第一个字节(C3)。

要解决此问题,请替换

$raw_xml =~ /<title>(.*?)</;
print "Regex finds [$1]\n";

use Encode qw( decode_utf8 );

my $decoded_xml = decode_utf8($raw_xml);
$decoded_xml =~ /<title>(.*?)</;
print "Regex finds [$1]\n";

现在,您从两个测试中获得相同的行为,即相同的垃圾输出。这给我们带来了第二个问题。

您不对输出进行编码

XML :: LibXML返回已解码的文本,即Unicode代码点。因此,ú作为字符FA返回,因为ú是U + 000FA。这是正确的,因为您不必关心编码,除非在进行I / O时。

执行I / O时会出现问题。 print期望它收到的每个字符代表一个字节,所以当你告诉它打印字符FA时,它会打印字节FA,你的终端会显示“wtf?”。

您的终端需要UTF-8,因此您需要先使用UTF-8对字符串进行编码,然后再将其传递给print,或者告诉print为您执行此操作。

# Decode STDIN (UTF-8).
# Decode STDOUT and STDERR (UTF-8).
# The default encoding for files opened in scope is UTF-8.
use open ':std', ':encoding(UTF-8)';

完整的解决方案:

use open ':std', ':encoding(UTF-8)';

use Encode qw( decode_utf8 );

my $raw_xml = read_file("test.xml", binmode => 'raw');

{
   my $decoded_xml = decode_utf8($raw_xml);
   my ($title) = $decoded_xml =~ /<title>(.*?)</;
   printf("%s: [%s] [%s]\n", "Home-grown", $title, substr($title, 0, 1));
}

{
   my $doc = XML::LibXML->load_xml(string => $raw_xml );
   my ($entry_node) = $doc->findnodes('entry');
   my $title = $entry->findvalue('title');
   printf("%s: [%s] [%s]\n", "LibXML", $title, substr($title, 0, 1));
}

答案 1 :(得分:0)

Latin-1是Perl的默认编码,特别是对于源代码中的字符串。 raw编码用于二进制数据,如图像或视频。如果您将数据作为原始数据读取,则它没有编码。如果将具有编码的字符串与没有编码的原始数据连接在一起,Perl必须猜测原始数据的编码。不要将字符串视为原始数据。如果您仍然想要,请在将原始数据附加到字符串之前告诉Perl编码。