为什么 XML::Simple 气馁?

Why is XML::Simple Discouraged?

来自 XML::Simple 的文档:

The use of this module in new code is discouraged. Other modules are available which provide more straightforward and consistent interfaces. In particular, XML::LibXML is highly recommended.

The major problems with this module are the large number of options and the arbitrary ways in which these options interact - often with unexpected results.

谁能帮我解释一下这其中的主要原因是什么?

真正的问题是 XML::Simple 主要尝试做的是采用 XML,并将其表示为 perl 数据结构。

您无疑会从 perldata 中了解到,您可以使用的两个关键数据结构是 hasharray

  • 数组是有序标量。
  • 哈希是无序的 key-value 对。

而 XML 两者都没有。它的元素是:

  • 非唯一命名(这意味着散列不 "fit")。
  • ..... 但在文件中 'ordered'。
  • 可能有属性(您可以将其插入哈希)
  • 可能有内容(但也可能没有,但可能是一元标签)
  • 可能有 children(任意深度)

并且这些东西不会直接映射到可用的 perl 数据结构 - 在简单的层面上,散列的嵌套散列可能适合 - 但它无法处理具有重复名称的元素。您也不能轻易区分属性和 child 节点。

因此XML::Simple尝试根据XML内容进行猜测,并从各种选项设置中获取'hints',然后当您尝试并输出 内容,它(尝试)反向应用相同的过程。

因此,除了最 简单 XML 之外,它充其量变得笨拙,或者在最坏的情况下丢失数据。

考虑:

<xml>
   <parent>
       <child att="some_att">content</child>
   </parent>
   <another_node>
       <another_child some_att="a value" />
       <another_child different_att="different_value">more content</another_child>
   </another_node>
</xml>

这个 - 当通过 XML::Simple 解析时给你:

$VAR1 = {
          'parent' => {
                      'child' => {
                                 'att' => 'some_att',
                                 'content' => 'content'
                               }
                    },
          'another_node' => {
                            'another_child' => [
                                               {
                                                 'some_att' => 'a value'
                                               },
                                               {
                                                 'different_att' => 'different_value',
                                                 'content' => 'more content'
                                               }
                                             ]
                          }
        };

注意 - 现在您在 parent 下只有匿名散列,但在 another_node 下您有一个匿名散列数组。

所以为了访问child的内容:

my $child = $xml -> {parent} -> {child} -> {content};

注意你是如何得到一个 'child' 节点,在它下面有一个 'content' 节点,这不是因为它是......内容。

但是要访问第一个 another_child 元素下的内容:

 my $another_child = $xml -> {another_node} -> {another_child} -> [0] -> {content};

请注意 - 由于具有多个 <another_node> 元素,XML 已被解析为一个数组,而不是一个数组。 (如果你确实在它下面有一个名为 content 的元素,那么你最终会得到其他东西)。您可以使用 ForceArray 更改此设置,但最终会得到数组哈希数组的哈希数组的哈希值——尽管它至少在处理 child 元素时是一致的。编辑:注意,在讨论之后——这是一个错误的默认值,而不是 XML::Simple 的缺陷。

你应该设置:

ForceArray => 1, KeyAttr => [], ForceContent => 1

如果将此应用到上面的 XML,则会得到:

$VAR1 = {
          'another_node' => [
                            {
                              'another_child' => [
                                                 {
                                                   'some_att' => 'a value'
                                                 },
                                                 {
                                                   'different_att' => 'different_value',
                                                   'content' => 'more content'
                                                 }
                                               ]
                            }
                          ],
          'parent' => [
                      {
                        'child' => [
                                   {
                                     'att' => 'some_att',
                                     'content' => 'content'
                                   }
                                 ]
                      }
                    ]
        };

这会给您带来一致性,因为您将不再有与 multi-node 不同的单节点元素处理方式。

但你还是:

  • 有一个 5 参考深度树来获取一个值。

例如:

print $xml -> {parent} -> [0] -> {child} -> [0] -> {content};

您仍然将 contentchild 散列元素视为属性,并且由于散列是无序的,您根本无法重建输入。所以基本上,您必须解析它,然后 运行 通过 Dumper 找出您需要查看的位置。

但是通过 xpath 查询,您可以通过以下方式到达该节点:

findnodes("/xml/parent/child"); 

你在 XML::Simple 中没有得到的,你在 XML::Twig 中得到的(我推测 XML::LibXML 但我不太了解):

  • xpath支持。 xpath 是表达节点路径的 XML 方式。所以你可以'find'上面的一个节点用get_xpath('//child')。您甚至可以在 xpath 中使用属性 - 例如 get_xpath('//another_child[@different_att]'),这将 select 正是您想要的。 (您也可以迭代匹配项)。
  • cutpaste 移动元素
  • parsefile_inplace 允许您使用就地编辑修改 XML
  • pretty_print 选项,格式化 XML
  • twig_handlerspurge - 允许您处理非常大的 XML 而无需将其全部加载到内存中。
  • simplify 如果你真的必须让它向后兼容 XML::Simple
  • 代码通常比尝试遵循散列和数组引用的菊花链要简单得多,由于结构上的根本差异,这永远不可能一致地完成。

它也被广泛使用 - 很容易从 CPAN 下载,并在许多操作系统上作为可安装包分发。 (遗憾的是,它不是默认安装。然而)

参见:XML::Twig quick reference

为了便于比较:

my $xml = XMLin( \*DATA, ForceArray => 1, KeyAttr => [], ForceContent => 1 );

print Dumper $xml;
print $xml ->{parent}->[0]->{child}->[0]->{content};

比。

my $twig = XML::Twig->parse( \*DATA );
print $twig ->get_xpath( '/xml/parent/child', 0 )->text;
print $twig ->root->first_child('parent')->first_child_text('child');

XML::Simple 是最复杂的 XML 可用解析器

XML::Simple 的主要问题是生成的结构极难正确导航。 $ele->{ele_name} 可以 return 以下任何一项(即使对于遵循相同规范的元素):

[ { att => 'val', ..., content => [ 'content', 'content' ] }, ... ]
[ { att => 'val', ..., content => 'content' }, ... ]
[ { att => 'val', ..., }, ... ]
[ 'content', ... ]
{ 'id' => { att => 'val', ..., content => [ 'content', 'content' ] }, ... }
{ 'id' => { att => 'val', ..., content => 'content' }, ... }
{ 'id' => { att => 'val', ... }, ... }
{ 'id' => { content => [ 'content', 'content' ] }, ... }
{ 'id' => { content => 'content' }, ... }
{ att => 'val', ..., content => [ 'content', 'content' ] }
{ att => 'val', ..., content => 'content' }
{ att => 'val', ..., }
'content'

这意味着您必须执行各种检查以查看您实际得到了什么。但这纯粹的复杂性鼓励开发人员做出非常糟糕的假设。这会导致各种问题进入生产环境,导致实时代码在遇到极端情况时失败。

制作更规则树的选项不足

您可以使用以下选项来创建更规则的树:

ForceArray => 1, KeyAttr => [], ForceContent => 1

但即使有这些选项,仍然需要进行许多检查才能从树中提取信息。例如,从文档中获取 /root/eles/ele 节点是一个常见的操作,执行起来应该很简单,但在使用 XML::Simple 时需要执行以下操作:

# Requires: ForceArray => 1, KeyAttr => [], ForceContent => 1, KeepRoot => 0
# Assumes the format doesn't allow for more than one /root/eles.
# The format wouldn't be supported if it allowed /root to have an attr named eles.
# The format wouldn't be supported if it allowed /root/eles to have an attr named ele.
my @eles;
if ($doc->{eles} && $doc->{eles}[0]{ele}) {
    @eles = @{ $doc->{eles}[0]{ele} };
}

在另一个解析器中,将使用以下内容:

my @eles = $doc->findnodes('/root/eles/ele');

XML::Simple 强加 众多 限制,并且缺少共同特征

  • 生产XML完全没用。即使是ForceArray => 1, ForceContent => 1, KeyAttr => [], KeepRoot => 1,也有太多无法控制的细节

  • 不保留children不同名字的相对顺序

  • 它有限(使用 XML::SAX 后端)或不支持(使用 XML::Parser 后端)名称空间和名称空间前缀。

  • 一些后端(例如 XML::Parser)无法处理不基于 ASCII 的编码(例如 UTF-16le)。

  • 一个元素不能有一个 child 元素和一个同名的属性。

  • 无法创建 XML 个带评论的文档。

忽略前面提到的主要问题,XML::Simple在这些限制下仍然可以使用。但是,为什么还要麻烦检查 XML::Simple 是否可以处理您的文档格式并冒着以后不得不切换到另一个解析器的风险呢?您可以简单地从一开始就为所有文档使用更好的解析器。

其他一些解析器不仅不受这些限制的约束,而且还提供了大量其他有用的功能。以下是 XML::Simple 可能没有的一些功能:

  • 速度。 XML::Simple 非常慢,尤其是当您使用 XML::Parser 以外的后端时。我说的比其他解析器慢几个数量级。

  • XPath 选择器或类似的。

  • 支持超大文档。

  • 支持漂亮的打印。

XML::Simple有用吗?

唯一 XML::Simple 最简单的格式是没有可选元素的格式。我接触过无数XML格式,但从未遇到过这种格式。

仅这种脆弱性和复杂性就足以保证远离 XML::Simple,但还有其他原因。

备选方案

我用XML::LibXML。这是一个非常快的 full-featured 解析器。如果我需要处理不适合内存的文档,我会使用 XML::LibXML::Reader(及其 copyCurrentNode(1))或 XML::Twig(使用 twig_roots).

我不同意文档

我会反对并说 XML::Simple 就是这么..简单。而且,它对我来说一直很容易和愉快地使用。使用您收到的输入对其进行测试。只要输入不变,你就很好。抱怨使用 XML::Simple 的人抱怨使用 JSON::Syck 序列化 Moose。文档是错误的,因为它们考虑了正确性而不是效率。如果您只关心以下内容,那很好:

  • 不丢弃数据
  • 构建为提供的格式而非抽象模式

如果您正在制作一个不是由应用程序定义而是由规范定义的抽象解析器,我会使用其他东西。我曾经在一家公司工作过,我们不得不接受 300 种不同的 XML none 模式,其中有一个规范。 XML::Simple 轻松完成任务。其他选择将要求我们实际雇用某人来完成工作。每个人都认为 XML 是以一种严格的、包罗万象的规范格式发送的东西,这样如果你写一个解析器就很好了。如果是这种情况,请不要使用 XML::Simple。 XML,在 JSON 之前,只是一种从一种语言到另一种语言的 "dump this and walk" 格式。人们实际上使用了 XML::Dumper 这样的东西。没有人真正知道输出了什么。处理这种情况 XML::Simple 太棒了!理智的人仍然在没有规范的情况下转向 JSON 来完成同样的事情。世界就是这样运转的。

想读入数据而不用担心格式?想要遍历 Perl 结构而不是 XML 可能性?去 XML::Simple.

通过扩展...

同样,对于大多数 应用程序,JSON::Syck 足以丢弃它并行走。 虽然如果你要发送到很多人,我 高度 建议不要做一个冲洗喷嘴并制定一个你导出的规范。但是,你知道吗.. 有时你会接到一个你不想与之交谈的人打来的电话,他想要他的数据,而你通常不会导出这些数据。而且,您将通过 JSON::Syck's voodoo and let them worry about it. If they want XML? Charge them 0 more and fire up ye' ole XML::Dumper.

进行管道传输

带走

它可能不够完美,但 XML::Simple 非常高效。在这个竞技场上节省的每一个小时,您都可以花在更有用的竞技场上。这是现实世界的考虑。

其他答案

看起来 XPath 有一些优点。这里的每个答案都归结为更喜欢 XPath 而不是 Perl。没关系。如果您更愿意使用标准化的 XML 领域特定语言来访问您的 XML,那就试试吧!

Perl 没有提供一种简单的机制来访问深度嵌套的可选结构。

var $xml = [ { foo => 1 } ];  ## Always w/ ForceArray.

var $xml = { foo => 1 };

在这两个上下文中获取 foo 的值可能很棘手。 XML::Simple 知道这一点,这就是为什么你可以强制前者..然而,即使使用 ForceArray,如果元素不存在,你也会抛出错误..

var $xml = { bar => [ { foo => 1 } ] };

现在,如果 bar 是可选的,您仍然可以访问它 $xml->{bar}[0]{foo} 并且 @{$xml->{bar}}[0] 将引发错误。无论如何,这只是 perl。这与 XML::Simple imho 没有关系。而且,我承认 XML::Simple 不利于构建规范。显示数据,我可以使用 XML::Simple 访问它。