如何在XML :: LibXML中避免双重UTF-8编码

时间:2014-01-13 16:54:58

标签: perl utf-8 libxml2

我的程序从数据源接收UTF-8编码的字符串。我需要篡改这些字符串,然后将它们作为XML结构的一部分输出。 当我序列化我的XML文档时,它将被双重编码并因此被破坏。当我仅序列化根元素时,它会很好,但当然缺少标题。

这是一段试图可视化问题的代码:

use strict; use diagnostics;    use feature 'unicode_strings';
use utf8;   use v5.14;      use encoding::warnings;
binmode(STDOUT, ":encoding(UTF-8)");    use open qw( :encoding(UTF-8) :std );
use XML::LibXML

# Simulate actual data source with a UTF-8 encoded file containing '¿Üßıçñíïì'
open( IN, "<", "./input" ); my $string = <IN>; close( IN ); chomp( $string );
$string = "Value of '" . $string . "' has no meaning";

# create example XML document as <response><result>$string</result></response>
my $xml = XML::LibXML::Document->new( "1.0", "UTF-8" );
my $rsp = $xml->createElement( "response" );    $xml->setDocumentElement( $rsp );
$rsp->appendTextChild( "result", $string );

# Try to forward the resulting XML to a receiver. Using STDOUT here, but files/sockets etc. yield the same results
# This will not warn and be encoded correctly but lack the XML header
print( "Just the root document looks good: '" . $xml->documentElement->serialize() . "'\n" );
# This will include the header but wide chars are mangled
print( $xml->serialize() );
# This will even issue a warning from encoding::warnings
print( "The full document looks mangled: '" . $xml->serialize() . "'\n" );

剧透1:好案例:

  

&lt; response&gt;&lt; result&gt;'¿Üßıçñíïì'的数值没有意义&lt; / result&gt;&lt; / response&gt;

剧透2:不好的案例:

  

&lt;?xml version =“1.0”encoding =“UTF-8”?&gt;&lt; response&gt;&lt; result&gt;'¿ÃñçñÃÃÃ'的价值没有意义&lt; /导致&GT;&LT; /响应&GT;

根元素及其内容已经是UTF-8编码的。 XML :: LibXML接受输入并能够处理它并再次输出为有效的UTF-8。一旦我尝试序列化整个XML文档,内部的宽字符就会被破坏。在十六进制转储中,看起来好像已经UTF-8编码的字符串再次通过UTF-8编码器。我已经搜索,尝试和阅读了很多内容,从Perl's own Unicode tutorial一直到tchrist'sWhy does modern Perl avoid UTF-8 by default?问题的极好回答。我不认为这是一般的Unicode问题,而是我和XML :: LibXML之间的特定问题。

如果能够输出包含标题的完整XML文档以使其内容保持正确编码,我需要做什么?是否有要设置的标志/属性/开关?

(我很乐意接受指向 TFM 的相应部分的链接,只要它们确实有用,我应该 R ;)

2 个答案:

答案 0 :(得分:4)

池上是正确的,但他并没有真正解释什么是错的。引用the docs for XML::LibXML::Document

  

重要提示:与其他节点的toString不同,在文档节点上,此函数将XML作为文档原始编码中的字节字符串返回(请参阅actualEncoding()方法)!

serialize只是toString)的别名

当您将字节字符串打印到标有:encoding图层的文件句柄时,它会被编码为ISO-8859-1。由于您有一个包含UTF-8字节的字符串,因此会进行双重编码。

正如ikegami所说,使用binmode(STDOUT)从STDOUT中删除编码层。在打印之前,您还可以decodeserialize的结果重新转换为字符,但这假定文档使用的是您在输出文件句柄上设置的相同编码。 (否则,您将发出一个XML文档,其实际编码与其标题声明的内容不匹配。)如果您要打印到文件而不是STDOUT,请使用'>:raw'打开它以避免双重编码。

答案 1 :(得分:3)

由于XML文档的解析不需要任何外部信息,因此它们是二进制文件而不是文本文件。

您告诉Perl编码发送到STDOUT [1] 的任何内容,但随后您继续输出XML文档。您不能将字符编码应用于二进制文件,因为它会破坏它。

替换

binmode(STDOUT, ":encoding(UTF-8)");

binmode(STDOUT);

注意:这假定您输出的其余文本只是临时调试信息。输出没有其他意义。


  1. 事实上,你这样做了两次!使用use open qw( :encoding(UTF-8) :std );后,再使用binmode(STDOUT, ":encoding(UTF-8)");