Perl XML :: Twig字符编码

时间:2015-06-04 18:49:17

标签: xml perl encoding utf-8

我有一组XML文件,其中包含非简单ASCII字符和编码字符的组合,例如:

... many 8-bit characters such as é, ⪚, and ñ.

(第二个字符是amp的&符号分号。第一个和第三个是非转义字符。)

文件采用UTF-8格式。

当我使用XML :: Twig运行我的Perl脚本时,实体(上面的第二个字符)变成一个未知字符(我在打印文件时得到'宽字符'消息)

这是我的代码。所有处理程序正在读取XML,而不是进行任何更改:

 my $twig= XML::Twig->new( 
   comments => 'keep',
   output_encoding => 'UTF-8',
#   keep_encoding => 1,
   twig_handlers => { topicref => \&topicref_processing,
            xref => \&topicref_processing,
            link => \&topicref_processing},
      pretty_print => 'indented',

 );

 $twig->parsefile($file);
 my($outfile) = $file;
 $outfile =~ s/([.]dita)/.out$1/i;

open(NEW,">$outfile");
$twig->flush( \*NEW);
close(NEW);

如果我添加keep_encoding => 1(上面已注释),实体被保留,但第一个和第三个字符被破坏:

...such as é, ⪚, and ñ.

如果我将UTF-8编码添加到flush:

open(NEW,'>:encoding(UTF-8)', $outfile);
它变得更奇怪了:

...such as Ã?©, ⪚, and Ã?±. 

关于如何通过未受损害的角色和实体传递任何想法? 谢谢, 斯科特

2 个答案:

答案 0 :(得分:2)

除了确保输入和输出IO通道设置为UTF-8编码外,您无需做任何特殊操作。 Wide character in print警告表示您正在尝试将宽字符(大于255的代码点)打印到仅具有字节语义的通道

如果我使用此数据

<?xml version="1.0" encoding="UTF-8"?>
<root>
  <text>... many 8-bit characters such as é, &#10906;, and ñ.</text>
</root>

下面的代码一切正常。关键是use open qw/ :std :encoding(utf-8) /设置STDIN,STDOUT和STDERR,以及任何其他新打开的文件句柄,以使用UTF-8编码

不幸的是,keep_encoding选项似乎控制了实体扩展和输出编码,我看不到一种方法来说服XML::Twig在启用时返回一个简单的字符串,而且你所有的can get是一个编码的字节序列,在将其传递给编码输出通道之前,必须先调用decode_utf8来获取字符。如果有人知道更好的方法来处理这个,那么我将不胜感激。当然,可以将模块中的编码数据发送到:raw输出通道,但这不是事情应该起作用的方式

请注意,要查看输出中的字符,您必须使用具有该代码点字形的字体。大多数字体都没有该字符

use strict;
use warnings;

use open qw/ :std :encoding(utf-8) /;

use XML::Twig ();
use Encode qw/ decode_utf8 /;

my $twig = XML::Twig->new( keep_encoding => 1 );
$twig->parsefile('utf-8.xml');

my ($text) = $twig->findnodes('/root/text');
$text = decode_utf8($text->trimmed_text);

print $text, "\n";

<强>输出

... many 8-bit characters such as é, &#10906;, and ñ.

<强>更新

这是为了解释你得到的输出

  

如果我添加keep_encoding =&gt; 1(上面已注释),实体被保留,&gt;但是第一个和第三个字符被破坏了:

     

...例如Ã,,⪚和Ã。。

这些字符不是损坏的,文本输出为UTF-8,但无论你使用什么来查看它都需要字节编码,类似于ISO-8859-1。编码为UTF-8时的e-acute字符U+00E9是一个双字节字符0xC3 0xA9。当解释为ISO-8859-1时,0xC3是A-tilde,而0xA9是版权符号,这正是您所看到的。如果您使用的是期望UTF-8编码的数据,那么您将看到单个字符e-acute而不是

  

如果我将UTF-8编码添加到flush:

     

open(NEW,'&gt;:encoding(UTF-8)',$ outfile);

     它变得更奇怪了:

     

...例如é,⪚和Ã?±。

这里发生的是,虽然来自XML::Twig的字符串已编码为UTF-8,但数据未标记为如此。这意味着形成UTF-8编码字符的两个字节被视为单个字符,并且它们再次编码共给出四个字符

答案 1 :(得分:2)

首先:在你的情况下,keep_encoding应该。这是一个古老的选择,可以追溯到远古时代,当latin1是一种常用的编码时,perl对于unicode来说并不是那么好。我在这里说5.8前。该选项为生活在全拉丁世界的人们提供了一种处理XML的方法,而无需处理unicode。使用utf-8数据会导致疯狂(以及您发现的编码问题)。

如其他答案中所述,输出文件需要在utf8模式下打开,可以在openuse utf8::all;中打开。这消除了wide character警告,并避免更糟糕的情况,如果输出只包含ascii和扩展的ascii字符,输出将转换为latin1(perl这样做是为了保持向后兼容性,如果你删除了它,你可以看到它来自您输入的&#10906;

完成此操作后,输出文件将处于正确的utf-8状态,未转义。如果显示不正确,可能是您的终端不支持utf-8。

如果您需要转义所有非ascii字符,可以使用output_filter => 'safe'选项,如下面的代码所示。

#!/usr/bin/perl

use strict;
use warnings;

use XML::Twig;
use utf8::all; # either this or open the output file with '>:utf8'

my $file= 'test_enc.dita';

 my $twig= XML::Twig->new( 
   comments => 'keep',
   # escapes all non-ascii characters (including accented ones)
   output_filter => 'safe', 
   twig_handlers => { topicref => \&topicref_processing,
            xref => \&topicref_processing,
            link => \&topicref_processing},
      pretty_print => 'indented',

 );

 $twig->parsefile( $file);
 my($outfile) = $file;
 $outfile =~ s/([.]dita)/.out$1/i;

# current best practices recommend the  use the 3 args form of 
# open and lexical filehandles
open( my $out,'>', $outfile);
$twig->flush( $out);
close( $out);

没有真正的方法来忠实地保存编码/非编码形式的字符,除了keep_encoding这是一个黑客。如果你真的需要将extended-ascii字符保存为字符并将其他字符编码为数字字符实体,那么你将为output_filter提供自定义函数,它应该接收字符串(所有utf-8字符),并返回要输出的字符串(某些字符编码为数字实体)

那说我不确定你是否需要忠实于原始格式。 XML处理器不应该关心它。事实上,这就是为什么很难保持编码:调用解析器的代码只将文本视为utf-8字符串,所有实体都已经解码。