解析XML的技术


11

我总是发现XML有点麻烦处理。我不是在谈论实现XML解析器:我是在谈论使用现有的基于流的解析器,例如SAX解析器,该解析器逐节点处理XML。

是的,为这些解析器学习各种API确实很容易,但是每当我查看处理XML的代码时,我总是发现它有些复杂。根本的问题似乎是XML文档在逻辑上被分离为各个节点,但是数据类型和属性通常与实际数据分离,有时通过多层嵌套。因此,当单独处理任何特定节点时,需要维护许多额外的状态以确定我们在哪里以及下一步需要做什么。

例如,给出一个典型XML文档的片段:

<book>
  <title>Blah blah</title>
  <author>Blah blah</author>
  <price>15 USD</price>
</book>

...如何确定何时遇到包含书名的文本节点?假设我们有一个简单的XML解析器,它就像一个迭代器,每次调用时,都会为我们提供XML文档中的下一个节点XMLParser.getNextNode()。我不可避免地发现自己正在编写如下代码:

boolean insideBookNode = false;
boolean insideTitleNode = false;

while (!XMLParser.finished())
{
    ....
    XMLNode n = XMLParser.getNextNode();

    if (n.type() == XMLTextNode)
    {
        if (insideBookNode && insideTitleNode)
        {
            // We have a book title, so do something with it
        }
    }
    else
    {
        if (n.type() == XMLStartTag)
        {
            if (n.name().equals("book")) insideBookNode = true
            else if (n.name().equals("title")) insideTitleNode = true;
        }
        else if (n.type() == XMLEndTag)
        {
            if (n.name().equals("book")) insideBookNode = false;
            else if (n.name().equals("title")) insideTitleNode = false;
        }
    }
}

基本上,XML处理很快就会变成一个巨大的,由状态机驱动的循环,其中包含许多状态变量,用于指示我们之前发现的父节点。否则,需要维护堆栈对象以跟踪所有嵌套标签。这很快变得容易出错并且难以维护。

同样,问题似乎是我们感兴趣的数据没有直接与单个节点关联。当然,如果我们将XML编写为:

<book title="Blah blah" author="blah blah" price="15 USD" />

...但是实际上很少使用XML。通常,我们将文本节点作为父节点的子节点,并且需要跟踪父节点以确定文本节点指的是什么。

所以...我做错什么了吗?有没有更好的办法?在什么时候使用基于XML流的解析器变得太麻烦了,以至于需要成熟的DOM解析器?我想听听其他程序员在使用基于流的解析器处理XML时会使用哪种惯用法。基于流的XML解析必须总是变成一个巨大的状态机吗?


2
如果您使用的是.net语言,则应查看linq to xml aka XLinq。
Muad'Dib 2011年

谢谢,我以为我是唯一遇到此问题的人。坦白说,我经常发现整个XML格式更多的是障碍而不是帮助。是的,它允许将大量结构化数据存储在一个小的文本文件中。但是,如果您那时需要20多个类来解开包装并弄清事物的含义,则不能保证您不会忽略或多或少重要的事物。就像Monty Python的《圣杯》中的兔子。
Elise van Looij

Answers:


9

对我来说,问题是相反的。什么时候XML文档变得如此繁琐,以至于您必须开始使用SAX而不是DOM?

我只会将SAX用于非常大且不确定大小的数据流。或者XML打算调用的行为实际上是事件驱动的,因此类似于SAX。

您提供的示例在我看来非常像DOM。

  1. 载入XML
  2. 提取标题节点并“对其进行操作”。

编辑:我也将SAX用于可能格式不正确的流,但是我想在猜测数据的位置上做到最好。


2
我认为这是一个好点。如果您要分析的文档对于DOM太大,那么您需要考虑是否正在分析对于XML的
Dean Harding

1
+1:如果有选择,我将始终使用DOM。不幸的是,我们的设计要求似乎总是包括“能够处理任何大小的文档的能力”和“必须高效”,这几乎排除了基于DOM的解决方案。
TMN

3
@TMN,在理想情况下,需求将首先排除XML。
SK-logic

1
@TMN,这听起来像是幻像的要求之一:“当然,我们所有的文档只有大约100KB,而我们看到的最大文档是1MB,但是您永远都不知道未来会怎样,因此我们应该保持选择的开放性并构建无限大的文档”
Paul Butcher

@Paul Butcher,你永远不知道。我的意思是,转储维基百科就像30GB的XML。
2011年

7

我认为,我对XML的使用并不太多,可能是使用XPath来解析带有库的XML的最佳方法之一。

您无需遍历树来找到某些特定节点,而是为其提供了路径。在您的示例(使用伪代码)的情况下,它将类似于:

books = parent.xpath(“ / book”)//这将为您提供所有book节点
书中各书
    标题= book.xpath(“ / title / text()”)
    作者= book.xpath(“ / author / text()”)
    价格= book.xpath(“ / price / text()”)

    //处理数据

XPath的功能要强大得多,您可以使用条件(在值和属性上)进行搜索,在列表中选择特定节点,在树中移动级别。我建议您查找有关如何使用它的信息,它在许多解析库中都已实现(我将其用于.Net Framework版本和适用于Python的lxml)


如果您可以事先知道并信任xml的构造方式,那就很好。例如,如果您不知道元素的宽度是指定为节点的属性,还是指定为元素的大小节点内的属性节点,那么XPath不会有太大帮助。
Elise van Looij

5

基于流的XML解析必须总是变成一个巨大的状态机吗?

通常是的,是的。

对我来说,使用成熟的DOM解析器是在我需要模拟内存中文件层次结构的某些部分时,例如能够解析文档中的交叉引用。


+1:从DOM开始。避免SAX。
S.Lott

或使用vtd-xml
vtd-xml-author

4

通常,解析只是驱动状态机,而XML解析也不例外。基于流的解析总是很麻烦,我总是结束构建某种堆栈来跟踪祖先节点,并定义许多事件以及某种事件分派器,以检查标记或路径注册表并触发事件如果匹配。核心代码相当紧凑,但是最后我遇到了大量的事件处理程序,这些事件处理程序主要包括将以下文本节点的值分配给某个结构中某个字段的值。如果您还需要在其中混合业务逻辑,那么它可能会变得很毛茸茸。

除非另有规定,否则我将始终使用DOM,除非出现大小或性能问题。


1

并非完全与语言无关,但是我通常将XML反序列化为对象,甚至不考虑解析。如果您遇到速度问题,只有时间担心解析策略本身。


那属于解析。除非所讨论的XML是对象序列化的输出,否则您将拥有一个现成的反序列化库。但是然后这个问题没有出现。

许多语言/堆栈确实有​​现成的反序列化库。
Wyatt Barnett

是的,那又如何呢?我的观点仍然成立-并非所有的XML文件都以这种格式出现,如果有的话,您就不会问这个问题,因为您只是使用该反序列化库并且不会自行解析任何内容,从流或其他方式。

0

如果可以使用XPath,它将变得不那么麻烦。在.Net领域中,LINQ to XML也提取了许多不太吸引人的内容。(编辑 -这些当然需要DOM方法)

从根本上讲,如果您采用的是基于流的方法(因此您不能使用需要DOM的更好的抽象方法),那么我认为它将总是很麻烦,并且我不确定是否有解决方法。


如果您使用的是XPath,那么您将使用DOM(除非您将其与本地的XPath评估程序一起使用)。
TMN

是的,因此,我对需要DOM的抽象的评论...但是我会澄清,谢谢!
史蒂夫

0

如果可以找到一个为您提供迭代器的解析器,您是否考虑过将其视为词法分析器,并使用状态机生成器?

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.