XML::Simple
文档的说明:
请问有人能为我澄清其中关键原因吗?不建议在新的代码中使用此模块。还有其他提供更直接和一致接口的模块可用。特别是强烈推荐使用XML::LibXML。
使用此模块的主要问题是选项太多以及这些选项相互作用时的任意性 - 通常具有令人意外的结果。
XML::Simple
文档的说明:
请问有人能为我澄清其中关键原因吗?不建议在新的代码中使用此模块。还有其他提供更直接和一致接口的模块可用。特别是强烈推荐使用XML::LibXML。
使用此模块的主要问题是选项太多以及这些选项相互作用时的任意性 - 通常具有令人意外的结果。
实际问题在于XML::Simple
主要尝试将XML表示为Perl数据结构。
正如您从perldata
中所知道的那样,两个主要的可用数据结构是哈希表(hash)
和数组(array)
。
然而XML实际上不做这两件事。它具有以下特点:
这些内容不能直接映射到可用的Perl数据结构——在简单层面上,嵌套的哈希表可能是适合的——但它无法处理具有重复名称的元素。并且您无法轻松区分属性和子节点。
因此,XML::Simple
尝试根据XML内容猜测,并从各种选项设置中获取“提示”,然后在尝试输出内容时,(尝试)应用相同的逆向过程。
因此,对于除了最简单的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};
请注意,您有一个“子”节点,并且该节点下方有一个“内容”节点,这并不是因为它是...内容。
但是要访问第一个another_child
元素下方的内容:
my $another_child = $xml -> {another_node} -> {another_child} -> [0] -> {content};
请注意 - 由于存在多个<another_node>
元素,XML 已被解析为数组,而单个元素时没有这种情况。(如果你在其中加入一个名为content
的元素,你将得到另一种结果)。你可以通过使用ForceArray
来更改此行为,但这可能会导致多层嵌套的哈希和数组,虽然它处理子元素时至少是一致的。编辑:请注意,经过讨论,这是一个不好的默认设置,而不是 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'
}
]
}
]
};
通过这样做,您将获得一致性,因为您不再需要单节点元素与多节点处理方式不同。
但是,您仍然:
例如:
print $xml -> {parent} -> [0] -> {child} -> [0] -> {content};
你仍然有content
和child
散列元素被视为属性处理,由于散列是无序的,所以你无法重建输入。因此,基本上你必须先解析它,然后通过Dumper
运行它来确定需要查找的位置。
但是,使用xpath
查询,您可以轻松获取该节点:
findnodes("/xml/parent/child");
XML::Simple
缺少的功能,而在XML::Twig
中(我认为XML::LibXML
也有,但我不太熟悉):
xpath
支持。 xpath
是表示到节点路径的一种 XML 方式。因此,您可以使用 get_xpath('//child')
在上述内容中“查找”节点。甚至可以在 xpath
中使用属性,例如 get_xpath('//another_child[@different_att]')
,这将选择所需的节点。(您还可以迭代匹配项)。cut
和 paste
以移动元素。parsefile_inplace
允许您对 XML
进行原地编辑修改。pretty_print
选项,用于格式化 XML
。twig_handlers
和 purge
- 允许您处理非常大的 XML,而无需将其全部加载到内存中。simplify
,如果您确实需要使其向后兼容 XML::Simple
。它也广泛可用-可以从 CPAN
轻松下载,并作为可安装软件包分发在许多操作系统中。(遗憾的是它不是默认安装。)
供比较:
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::Twig
的赞信。如果你想超越解释XML::Simple
的问题,那么你需要考虑远比XML::Twig
和XML::LibXML
更多的东西,而且我不认为这是进行这种扩展分析的地方。 - BorodinXML::Simple的主要问题在于其生成的结构非常难以正确地导航。即使对于遵循相同规范的元素,$ele->{ele_name}
也可以返回以下任何一种:
[ { 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。即使使用ForceArray => 1, ForceContent => 1, KeyAttr => [], KeepRoot => 1
,也有太多无法控制的细节。
它不保留具有不同名称的子项的相对顺序。
它的命名空间和命名空间前缀支持有限(使用XML::SAX后端)或没有(使用XML::Parser后端)。
一些后端(例如XML::Parser)无法处理基于ASCII之外的编码(例如UTF-16le)。
元素不能同时具有与其名称相同的子元素和属性。
它无法创建带有注释的XML文档。
尽管忽略了先前提到的主要问题,XML::Simple仍然可能在这些限制下可用。但是为什么要费力地检查XML::Simple是否可以处理您的文档格式并冒险以后必须切换到另一个解析器呢?您可以从一开始就为所有文档使用更好的解析器。
一些其他解析器不仅不会受到这些限制的限制,而且还提供了许多其他有用的功能。以下是一些它们可能具有但XML::Simple不具备的功能:
速度。XML::Simple非常慢,特别是如果您使用的是XML::Parser之外的后端。我说的是比其他解析器慢几个数量级。
XPath选择器或类似内容。
支持极大的文档。
支持漂亮的打印。
唯一适用于XML::Simple的格式是没有元素是可选的格式。我已经使用了无数种XML格式,并且从未遇到过这样的格式。
这种脆弱性和复杂性足以理由让我们远离XML::Simple,但还有其他原因。
我使用XML::LibXML。它是一个非常快速、功能齐全的解析器。如果我需要处理无法适合内存的文档,我将使用XML::LibXML::Reader(及其copyCurrentNode(1)
)或XML::Twig(使用twig_roots
)。
$tpp->set( force_array => [ '*' ] );
,则需要至少my @eles; if ($doc->{root} && $doc->{root}[0]{eles} && $doc->{root}[0]{eles}[0]{ele}) { @eles = @{ $doc->{root}[0]{eles}[0]{ele} } }
来获取/root/eles/ele
节点,这是假设没有多个eles
节点的情况。这与最佳配置的XML::Simple没有区别。(如果没有force_array => [ '*' ]
,情况会更糟。) - ikegamitwig_handlers
返回匹配元素的子树,而XML::LibXML::Reader几乎只是一个标记生成器。XML::LibXML::Reader更接近于XML::Parser(XML::Twig使用的底层解析器)而不是XML::Twig。 - ikegami我持异议,认为XML::Simple
就是简单易用的。在我看来,它一直都很容易且令人愉快地使用。只要输入数据没有发生变化,你就可以放心使用。那些抱怨使用XML::Simple
的人也会抱怨使用JSON::Syck
来序列化Moose。这些文档是错误的,因为它们考虑的是正确性而非效率。只要你关心以下内容,你就可以使用它:
如果你正在创建一个抽象解析器,其定义不是由应用程序而是由规格说明书确定的,我会使用其他工具。有一次我曾在一家公司工作,我们必须接受300种不同的XML模式,没有一个模式是规范的。使用XML::Simple
轻松完成了这项工作。其他选项将要求我们雇佣某个人来完成这项工作。每个人都认为XML是以一种严格的、全面的、规范的格式发送的东西,这样,如果你编写了一个解析器,你就可以做到了。如果是这样的话,就不要使用XML::Simple
。在JSON出现之前,XML只是从一种语言到另一种语言的“转储和行走”格式。人们实际上使用诸如XML::Dumper
之类的工具。没有人真正知道输出了什么。在处理这种情况时,XML::Simple
非常好用!理智的人仍然会将其转换为JSON而不需要规范来完成同样的事情。这就是现实中的运作方式。
想读取数据并不担心格式?想遍历Perl结构而不是XML可能性?那就使用XML::Simple
。
同样地,对于大多数应用程序,JSON::Syck
足以转储并行走。但是如果你要向很多人发送数据,我强烈建议制定一个规范导出。但是,你知道吗,有时候你会接到某个你不想和他说话却要求提供数据的人的电话。然后,你就可以通过JSON::Syck
的巫术让他们去烦恼。如果他们想要XML?再收取500美元的费用,启动老式的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
无关。同时,我承认XML::Simple
不适合用于按规范构建XML数据。如果给我数据,我可以使用XML::Simple
来访问它。