2016-08-11 42 views
1

有了这个输入文件为什么Perl XML :: LibXML将UTF8更改为8859-1?

<?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在内存中,这也意味着我可以用上面的正则表达式来验证。

回答

5

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


你家种的解析器不能解码文件

你可以通过改变/<title>(.*?)<//<title>(.)</观察这个bug。它不是按照预期得到第一个字形(ú),它只获得其编码的第一个字节(C3)。

为了解决这个问题,有

use Encode qw(decode_utf8); 

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

现在你要从这两种试验,即在同一垃圾输出相同的行为取代

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

。这给我们带来了第二个问题。


你不编码您的输出解码文本又名Unicode代码点

XML ::的libxml的回报。 ú因此返回为字符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)); 
} 
+0

谢谢!作为你的回答和一些实验的结果,一个缺失的perl字符拼图碎片适合我,我非常感激。 – milesb

0

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

+0

确实如此,但LibXML [link](http://search.cpan.org/dist/XML-LibXML/LibXML.pod)的窗格明确指出 '...与I/O操作相关的功能(即:解析和保存)使用二进制数据(在原始文档编码中)遵守XML文档的编码声明... ... 不应用任何与编码相关的PerlIO图层(:utf8或:encoding(...) )将文件句柄作为解析的输入或(完整)XML文档的序列化程序的输出。 ...' ' – milesb