【问题标题】:How do I avoid double UTF-8 encoding in XML::LibXML如何避免 XML::LibXML 中的双重 UTF-8 编码
【发布时间】:2014-02-01 12:43:18
【问题描述】:

我的程序从数据源接收 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:好案例:

'¿Üßıçñíïì' 的值没有意义

剧透 2:坏情况:

'¿ÃÃıçñÃïì' 的值没有意义

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

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

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

【问题讨论】:

  • 注意,use open qw( :encoding(UTF-8) :std ); 已经做到了binmode(STDOUT, ":encoding(UTF-8)");

标签: perl utf-8 libxml2


【解决方案1】:

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

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

serialize 只是toString 的别名)

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

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

【讨论】:

  • 这是我错过的文档中的确切点,谢谢。所以我通过我的输出通道混合了二进制和字符串对象,这当然是行不通的。我会尝试相应地修复我的代码。
  • 感谢您提示多重编码不会有损,因此我可以编码两次,只需解码多余的编码运行而不会损坏我的输出。
  • 他没有建议你双重编码或者这样做不会有损。 (取决于编码是否。)他只是说发生了双重编码。他确实建议您可以编码-解码-编码,这可能会导致损坏的 XML 文档。 (取决于编码是否会。)
【解决方案2】:

由于 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)");

【讨论】:

  • @cjm,添加了解释。
  • 这似乎是正确的,是的。实际上,知道这一点并不会让我的生活变得更轻松,因为现在我需要准确找出何时将输出通道切换到原始模式以及何时再次反转。但特此回答了所提出的问题,对此我非常感谢。
  • 为什么要混合文本和二进制文件?不要期望奇怪的事情比正常的事情更容易。
  • 你确定输出一个可能错误的标题比不输出更好吗?
  • 整个问题只是关于同一应用程序的各个模块之间的通信。传入的数据被转换为 UTF-8,传出的数据被编码为任何请求的内容,但在内部,它对每个人来说都是 UTF-8,无一例外。使用代码后,我发现立即再次解码 XML 比摆弄输出编码更容易。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-11-07
  • 2016-10-10
  • 2011-05-09
  • 1970-01-01
  • 2010-10-26
相关资源
最近更新 更多