为啥 XML::Simple 不受欢迎?
Posted
技术标签:
【中文标题】为啥 XML::Simple 不受欢迎?【英文标题】:Why is XML::Simple Discouraged?为什么 XML::Simple 不受欢迎? 【发布时间】:2016-01-20 22:46:49 【问题描述】:来自XML::Simple
的文档:
不鼓励在新代码中使用此模块。其他模块也可以提供更直接和一致的接口。特别是,强烈推荐使用 XML::LibXML。
这个模块的主要问题是大量的选项以及这些选项交互的任意方式 - 通常会产生意想不到的结果。
谁能帮我解释一下造成这种情况的主要原因是什么?
【问题讨论】:
听听metacpan.org/pod/XML::Fast 的优缺点也可能很有趣 您是否正在创建一个知识库文章,您可以链接到该文章以杀死 XML::Simple? :D XML::Simple 不在 Perl 核心中,而且从来没有。实际上,Perl 核心中并没有 XML 解析模块。 作为 XML::Simple 的作者,我不鼓励使用它,因为有更好的解决方案实际上更易于使用。我个人使用并推荐 XML::LibXML 并写了一个教程来帮助人们入门 - XML::LibXML by example 刚回来读了cmets。如果您希望将某些内容包含在核心中,您可以随时在 p5p 邮件列表中提出建议。如果你有好的论据,他们可能会赞成。 【参考方案1】:真正的问题是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'
]
]
;
这将为您提供一致性,因为您将不再让单节点元素以不同于多节点的方式处理。
但你还是:
有一个 5 参考深度树来获得一个值。例如:
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
轻松下载,并作为可安装包分发到许多操作系统上。 (遗憾的是它不是默认安装。然而)
见: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');
【讨论】:
很遗憾,这不是默认安装。 如果“默认安装”是指核心模块,那么是的,我同意你的看法。但是,如果您的意思是与 Perl 发行版捆绑在一起,那么至少在 May 2014 之后,Strawberry Perl 已经包含了预安装的 XML 模块(XML::LibXML、XML::Parser、XML::Twig 等)。 IMO 它很大程度上归结为 ForceArray 应该默认为 1(并且在不破坏大多数现有用途的情况下无法更改)。如果 XML::Simple 满足您的需求,那么没有理由不使用它。 我同意,但将“满足我的需求”范围缩小到“如果我无法安装其他模块之一”,并且如果正则表达式破解不会这样做。因为老实说,出于同样的原因,我认为它与正则表达式非常相似。只要您对输入 XML 的范围有一个非常可控的范围,它就会起作用。它可能会在没有明显原因的情况下中断。它确实解决了一个问题,它是一个核心模块。但是当存在更好的选择时,这是一个糟糕的解决方案 @Sobrique:我开始编辑您的解决方案,但是当我到达最后一段和列表时,我不得不放弃。你声明的目的是解释为什么XML::Simple
是一个如此糟糕的选择,但你最终为XML::Twig
写了粉丝邮件。如果你想超越解释XML::Simple
的问题,那么你需要考虑的不仅仅是XML::Twig
和XML::LibXML
,我不认为这是进行这种扩展分析的地方
因为我不喜欢在没有提供合适替代方案的情况下提供“不要做 X”,所以我试图提供一些积极的理由来转换。理想情况下,有助于商业案例。我是 XML::Twig 的粉丝。我认为如果他们“简单地”从核心中删除 XML::simple,那将是一个很好的替代品。尤其是因为“简化”允许您保持向后兼容性。这有点偏离了我所知道的观点——还有很多其他不错的选择。【参考方案2】:
XML::Simple 是可用的最复杂的 XML 解析器
XML::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::Simple 有许多限制,并且缺乏共同的特点
它对于生成 XML 完全没用。即使是ForceArray => 1, ForceContent => 1, KeyAttr => [], KeepRoot => 1
,也有太多无法控制的细节。
它不保留不同名字的孩子的相对顺序。
它对命名空间和命名空间前缀的支持有限(使用 XML::SAX 后端)或不支持(使用 XML::Parser 后端)。
某些后端(例如 XML::Parser)无法处理不基于 ASCII 的编码(例如 UTF-16le)。
一个元素不能有同名的子元素和属性。
无法使用 cmets 创建 XML 文档。
忽略前面提到的主要问题,XML::Simple 在这些限制下仍然可以使用。但是,为什么还要麻烦检查 XML::Simple 是否可以处理您的文档格式并冒着以后不得不切换到另一个解析器的风险呢?您可以从一开始就对所有文档使用更好的解析器。
其他一些解析器不仅不会让您受到这些限制,它们还提供了大量其他有用的功能。以下是 XML::Simple 可能没有的一些特性:
速度。 XML::Simple 非常慢,尤其是当您使用 XML::Parser 以外的后端时。我说的是比其他解析器慢几个数量级。
XPath 选择器或类似的。
支持超大文档。
支持漂亮的打印。
XML::Simple 有用吗?
XML::Simple 最简单的唯一格式是没有可选元素的格式。我有无数 XML 格式的经验,我从来没有遇到过这样的格式。
仅凭这种脆弱性和复杂性就足以保证远离 XML::Simple,但还有其他原因。
替代方案
我使用 XML::LibXML。它是一个速度极快、功能齐全的解析器。如果我需要处理无法放入内存的文档,我会使用 XML::LibXML::Reader(及其 copyCurrentNode(1)
)或 XML::Twig(使用 twig_roots
)。
【讨论】:
XML::TreePP 在我看来似乎没有神奇的猜测 XML::Simple 有。但是你可以告诉它如何准确地表现。它也比 XML::LibXML 及其家族更容易处理。对于创建 XML,我会使用 XML::TreePP,如果你有巨大的 XML 并且速度是一个问题,我会使用 XML::LibXML 来解析外部 XML 内容。 @nicomen,假设您使用$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 => [ '*' ]
,情况会更糟。)
@nicomen,您说您会使用 XML::TreePP 而不是 XML::LibXML 来处理大型文档。为什么????这对我来说听起来很可笑,但我可能会遗漏一些东西。我没有对 XML::TreePP 进行基准测试,但我怀疑它没有接近 XML::LibXML、大文档或其他。大型文档的问题是内存,而不是速度。 XML::LibXML 确实为大型文档(拉式解析器)提供了一个选项,而 XML::TreePP 没有。也就是说,XML::Twig 在这方面要好得多。
我可能不清楚,我的意思是 XML::LibXML 适用于重型和大型文档。为了便于书写和阅读,我更喜欢 XML::TreePP,但是是的,您需要设置一些合理的默认值。
对于 XML::LibXML 用户,XML::LibXML::Reader 可能比 XML::Twig 更易于使用。【参考方案3】:
我不同意文档
我会反对并说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
imho 有 0 关系。而且,我承认XML::Simple
不适合按规范构建。显示数据,我可以使用 XML::Simple 访问它。
【讨论】:
评论不用于扩展讨论;这个对话是moved to chat。 让我们continue this discussion in chat. 我已经删除了针对其他用户的不必要的元评论。这并不需要成为答案的一部分,如果你想解决这个问题,就拿去聊天吧。以上是关于为啥 XML::Simple 不受欢迎?的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 XML::Simple 根据 Perl 中的 XML 属性内容修改 XML 元素?
在 Perl 中使用 XML::Simple 将哈希转换为 XML 后内容丢失