IBM®
跳转到主要内容
    中国 [选择]    使用条款
 
 
Select a scope: Search for:    
    首页    产品    服务与解决方案     支持与下载    个性化服务    
跳转到主要内容

developerWorks 中国  >  XML  >

XML Matters: RXP 解析器

一个特别快速的带 Python 绑定的验证解析器

developerWorks
文档选项

未显示需要 JavaScript 的文档选项


级别: 初级

David Mertz, Ph.D. (mertz@gnosis.cx), 评估师, Gnosis Software, Inc.

2003 年 10 月 01 日

RXP 是一个用 C 编写的验证解析器,它创建XML文档的一个非 DOM 树表示。虽然 RXP 本身没有很好地提供说明文档,因为它并不是为怯懦的人准备的,但是至少有两个非常棒的高层 API 是在 RXP 之上构建的: pyRXP 和 LT XML。pyRXP 是 RXP 的一个 Python 绑定,而 LT XML 是一组实用程序和库。在本文中,David 介绍了 RXP,将它与 expat 解析器进行了对比,并简要讨论了 pyRXP 和 LT XML,这两者利用了 RXP 速度上的优势,但又避免了 RXP 的复杂性。

本专栏的读者将会感受到这个事实,虽然我是在这里对 XML 作一般性描述,但是我对于 Python 工具有特别的偏爱。我曾想在本期中打破以往这种模式,而注重于在 C 应用程序中使用 RXP 。不过在更深入地察看了 RXP 库后,我发现利用它的最简单的方法是通过 Python 模块 pyRXP

尽管底层 RXP GPL 库几乎肯定是您能找到的最快的验证XML解析器,但是实际的解析器代码没有什么说明文档,只有一个简单的命令行工具示例。这个工具 rxp 类似于实用程序 xmlcat.py (我在技巧文章“ 命令行 XML 处理”中已介绍过它) 以及各种类似的实用程序——它读取 XML 文档、对它们进行验证、并输出一种规范的形式。可以分析 rxp.c 文件的源代码,以了解 RXP 解析生成紧凑文档树作为数据结构的方法。

RXP 之上,语言技术组(Language Technology Group)又构建了 LT XML ,它包含多种高层工具和 API。用 LT XML 构建了其他一些工具, 包括 XED(一个XML编辑器)。在本文中,我将简要概述 LT XML 中的工具,但是主要重点还是研究通过 pyRXP 绑定公开的 RXP 树 API。就我所知,其他本身就具有 RXP 绑定的高层语言——如Perl、TCL和Ruby——还没有加入它们。

谈论速度

RXP 快速的。一个使用(可选地)验证 RXP 解析器的 C 应用程序在速度上可能与使用非验证 expat 解析器(被认为是非常快的)没有什么差别。 RXP 通过为所要解析的XML 文档构建一个紧凑的内存树结构而工作。解析的失败就是构建树的失败,成功的解析给出一个比以 DOM 表示的 XML 文档资料有效得多的数据结构。

在需要从 XML 文档构建一个完整的数据结构时, RXP 可能稍微胜过 expat ,而如果需要验证,那么肯定不会选用 expat 。不过,如果只是顺序处理,或者提取 XML 文档中的一小部分信息,那么 expat 会更好一些,因为它不需要保存任何已经处理的 (或者已经跳过的)标记的表示。事实上,对于十分大的文档, expat 具有压倒性的优势——很少需要创建一个 1 GB XML 文档的内存表示(而使用 RXP 则 毫无选择地需要创建这样的内存表示)。一个使用 expat 构建的应用程序在读取这么大的 XML 时可以只提取少部分感兴趣的标记,使用的命令占用的内存比文档大小小得多。

RXP 的速度在 pyRXP 绑定的上下文中可以真正体现出来。本文的最后部分 Python中几个XML文档模型的比较 对这些模型: ElementTree gnosis.xml.objectify、 xml.minidom cDomlette 进行了一些非常详细的速度和内存使用 的比较。测试就是用第一种 API 创建一个最小的内存表示,并测量这种构建所用的时间和所需要的内存使用。用 pyRXP 做同样的事情更容易:


清单 1. time_rxp.py
        from pyRXP 
        import Parser
        import sys, time
start = time.clock()
tups = Parser().parse(sys.stdin.read())
        print
        "Time: %.3f" % (time.clock()-start)
      

pyRXP 解析 3MB 的 weblog.xml 文件只 需用 4 秒钟,在以前的测试中最好的性能是 cDomlette ,它在我的测试计算机上用了大约 25 秒。在内存使用方面, time_rxp.py 最多时使用大约 28 MB,与以前的竞争者中最节俭的一个 gnosis.xml.objectify 相似。换句话说, pyRXP 具有最好的内存使用,并且速度是以前最快的 6 倍!

pyRXP 比其他 XML 文档模型 API 快得多有一个非常特别的原因: RXP 用 C 构建一个完整的数据结构,而 pyRXP 所需要做的只不过是将这个完整的结构转换为一个非常类似的 Python 数据结构。相反,像 gnosis.xml.objectify ElementTree 这样的模块——虽然利用底层 expat 解析器进行实际的解析——仍然需要对每一个标记或者遇到的一部分内容在 Python 函数中进行回调。在 Python 中函数调用的开销是很大的,特别是与 C 调用的廉价相比。原则上,可以编写一个基于 expat 的、用 C 编码的 Python 扩展,它构建一个完整的数据结构,然后将它送回 Python 解释器(其速度将类似于 pyRXP )。但是创建一个扩展将需要比 pyRXP 包装器更多的编程工作,因为即使在 C expat 中,也需要对每一个标记和内容编写回调。相反, RXP 就在解析器中建立数据结构。

pyRXP 的元组树数据结构

pyRXP (以及 RXP 本身 )使用一个高效的、轻量级的树表示 XML 文档。树中的每一个节点是 form 的一个元组:

(tagname, attr_dict, child_list, reserved)

表示中没有使用专门的 Python 类——只使用了元组、字典、列表和字符串(以及保留位置上的 None )。也许令人意外的是,这个 form 足以表示 XML 文档中的所有信息。tagname 是直观的字符串 ;正如您所想到的,属性字典是一个映射属性与值的字典。子列表更为精巧:在列表中字符串可以与元组交错,表示混合的内容元素。而一个没有内容的元素由一个空的子列表表示,但是一个自封闭的标记由 None 表示 清单 2 显示了这种结构:


清单 2. pyRXP 元组树数据结构
>>> import pprint
>>> xml = '''<foo this="that" spam="eggs">
... <bar>1</bar><bar>2</bar>
... <baz></baz><baz/></foo>'''
>>> tree = Parser().parse(xml)
>>> pprint.pprint(tree)
('foo',
 {'this': 'that', 'spam': 'eggs'},
 ['\\n',
  ('bar', None, ['1'], None),
  ('bar', None, ['2'], None),
  '\\n',
  ('baz', None, [], None),
  ('baz', None, None, None)],
 None)

所有 XML 信息都在这里,但是浏览它可能不方便。





回页首


比较数据访问样式

回想一下,在上一期中,我比较了一个简单应用程序的几种实现,该应用程序筛选一个测试 weblog.xml 文档、并显示其中的一些信息。这个文件中的一个 <entry> 元素看起来可能像下面这样:


清单 3. 一个 weblog.xml 项记录
<entry>
  <host>64.172.22.154</host>
  <referer>-</referer>
  <userAgent>-</userAgent>
  <dateTime>19/Aug/2001:01:46:01</dateTime>
  <reqID>-0500</reqID>
  <reqType>GET</reqType>
  <resource>/</resource>
  <protocol>HTTP/1.1</protocol>
  <statusCode>200</statusCode>
  <byteCount>2131</byteCount>
</entry>

文件 weblog.xml 包含上千个这种项。一个使用 gnosis.xml.objectify 的筛选器看起来像下面这样:


清单 4. select_hits_xo.py
        from gnosis.xml.objectify 
        import XML_Objectify, EXPAT
weblog = XML_Objectify(
        'weblog.xml',EXPAT).make_instance()
interesting = [entry 
        for entry 
        in weblog.entry
                      if entry.host.PCDATA==
        '209.202.148.31'
        
and entry.statusCode.PCDATA== '200'] for e in interesting: print "%s (%s)" % (e.resource.PCDATA, e.byteCount.PCDATA)

如何对一个 pyRXP 元组树编写同样的应用程序呢?不幸的是,由于必须在嵌套的列表和数字元组位置中查找, 所以访问没有那么直观:


清单 5. select_hits_rxp1.py
        from pyRXP 
        import Parser
TAGNAME,ATTRS,CHILDREN = range(
        3)
weblog = Parser().parse(open(
        'weblog.xml').read())
interesting = []
        for child 
        in weblog[CHILDREN]:
            if child[TAGNAME]!=
        'entry': 
        continue
    gotHost, gotStatus = 
        0, 
        0
        
for fld in child[CHILDREN]: tag = fld[TAGNAME] if tag== 'host' and fld[CHILDREN]==[ '209.202.148.31']: gotHost = 1
elif tag== 'statusCode' and fld[CHILDREN]==[ '200']: gotStatus = 1
if gotHost and gotStatus: interesting.append(child[CHILDREN]) for e in interesting: resource, byteCount = '', ''
for fld in e: if fld[TAGNAME]== 'resource': resource = fld[CHILDREN][ 0] elif fld[TAGNAME]== 'byteCount': byteCount = fld[CHILDREN][ 0] print "%s (%s)" % (resource, byteCount)

即使用一些指定的常量代替元组位置,这个版本也无疑是难于阅读的(但是我认为对于直接处理元组树这已经是最好的了)。输出是一样的,只不过 pyRXP 的版本在 5 秒钟 内而不是 25 秒就得到了这个输出。

pyRXP 模块与一些其他文件混杂在一起,其中一个有意思的模块称为 xmlutils 。在一种聪明的策略中,类 xmlutils.TagWrapper 充当 pyRXP 元组树 的代理包装器。整体效果是可以以非常类似于 gnosis.xml.objectify 或者 ElementTree 所提供的自然 的 Python 样式访问元组树:


清单 6. select_hits_rxp2.py
        from pyRXP 
        import Parser
        import xmlutils
tree = Parser().parse(open(
        'weblog.xml').read())
weblog = xmlutils.TagWrapper(tree)
interesting = [child 
        for child 
        in weblog
                      if child.tagName==
        'entry'
        
if str(child.host)== '209.202.148.31'
if str(child.statusCode)== '200'] for e in interesting: print "%s (%s)" % (e.resource, e.byteCount)

至目前为止都不错。代码相当优雅。不过,代理增加了一些开销。这个版本的脚本运行了 7.5 秒而不是 5 秒,这比 gnosis.xml.objectify 的25 秒还是好得多。不过,筛选器在代理开销上所花费的这 2.5 秒相当于 select_hits_xo.py 在其筛选 过程中不用花费十分之一秒。分析步骤掩盖了这种差别,但是如果想像一个对XML文档进行一次解析、然后执行数百个不同筛选行动(例如对用户规则)的应用程序,那么代理包装器就开始变得不那么有吸引力了。不过, pyRXP 开发人员警告说 xmlutils 是试验性的,所以也许可以开发出更有效的包装器。





回页首


使用 LT XML

LT XML 集合建立在 RXP 之上,并包含多种处理 XML 的命令行工具,以及一些比 RXP 本身的 API 更高层的 API。 LT XML 中一个强大的工具称为 sggrep ,它是 XML 文件的一种 grep。 它的语法有些令人困惑,但是基本上它是将结合了常规表达式和 XPath 的表达式进行公式化的方法。

LT XML 中的一些其他工具包括:

  • textonly ,它去掉标记并输出 PCDATA 内容。
  • sgsort 用于 XML 元素排序。
  • sgcount 用于元素统计。
  • xmlnorm 用于规范化 XML 文档。

其中每一种工具都使用输入和输出管道,因而可以在命令行和 shell 脚本中结合。而且,将“sg”前缀从许多名字中去掉就可以看到与非 XML 版本的类似工具的关系。

一种有意思的技术是将几个 sggrep 查询在一个管道中传送。每一个 sggrep 命令可以同时指定主查询和子查询。例如:“我要 <foo> 元素,其中包含内容为 baz <bar> 元素 ”。主查询要求 <foo>, 子查询指定子元素 <bar> 的属性。工具允许用 -q-s -t 显式指定查询、子查询和样式的更详细的形式,也可以使用不带开关的紧凑形式 (用 -- 开关启用紧凑形式)。清单 7 是一个复杂的命令行,它与上面讨论的筛选工具所完成的工作几乎一样:


清单 7. 一个 webhost.xml 筛选复合查询
% cat weblog.xml |
  sggrep '.*/entry' '.*/entry/host' '209.202.148.31' -- |
  sggrep -q '.*/entry' -s '.*/entry/statusCode' -t '200' |
  sggrep '.*/resource|byteCount' -- |
  textonly -s '\\n'

其输出并不很正确,它分成了几行,如下所示:

/publish/programming/regular_expressions.html
45674

而不是像 Python 筛选器那样按行编排格式:

/publish/programming/regular_expressions.html (45674)

也许可以聪明地使用一些标准的 Unix shell 工具,如 awksed 或者 tr, 以准确得到所需要的输出。

在好的方面, sggrep 和其他 LT XML 工具是相当快的,与不使用 TagWrapper 开销的 pyRXP 一样快。而且,由绑定的工具所带来的潜在能力也可以被希望使用类似 API 的 C 程序员所利用。也许最好的是, LT XML 本身现在有了一个 Python 绑定(但是有意思的是,其他脚本语言没有)。



参考资料

  • 您可以参阅本文在 developerWorks 全球站点上的 英文原文.

  • 参与本文的 讨论组(也可以单击本文顶部和底部的 讨论以访问这些讨论组 )。



  • 访问 RXP 解析器的主页。



  • 找出有关 pyRXP 绑定的更多内容,它是由 ReportLab 所提供的,同时还提供了在 Python 中处理 PDF 文件的工具。



  • 查看 LT XML 工具,它基于 RXP ,并提供了对 XML 文档的不同命令行处理能力以及更高层的 API。



  • 参阅 David 关于 命令行 XML 处理 的 前一个提示,就在 developerWorks (2003年5月)。



  • 参阅 David Mertz 以前关于 XML 库的评论:
    • XML Matters #2 介绍了 gnosis.xml.objectify,那里简称为 xml_objectify( developerWorks, 2000年8月)。
    • XML Matters #11为读者介绍了对 gnosis.xml.objectify的一些早期改进。在这篇评论中没有包括一些新的功能,但是在模块的历史和其他文件中包括了这些新功能 (2001年1月)。
    • XML Matters #14讨论了 Haskell lazy pure-functional 编程语言的 HaXml模块 (2001年10月)。
    • XML Matters #18讨论了 Ruby 的 REXML库(2002年3月)。
    • XML Matters #28 讨论了 Fredrik Lundh 的 ElementTree XML API(2003年6月)。
    column summary page中您可以找到 David 的 XML Matters 专栏中所有以前的内容。


  • developerWorks XML 专区中有XML的更多资源。



  • 取得 IBM WebSphere Studio,这是用 Java 和其他语言自动化 XML 开发的一组工具。它与 WebSphere Application Server紧密集成,但是也可以与其他 J2EE 服务器一同使用。



  • 找到如何成为一个 XML及相关技术的IBM认证的开发人员的信息。




关于作者

Author photo

对于David Mertz,凡事从小事做起。可以通过 mertz@gnosis.cx 与他联系,可以在 http://gnosis.cx/publish/ 了解他的生活。欢迎对本文、以前或者以后的评论提出建议和意见。请在 Text Processing in Python 查看 David 的新书。




对本文的评价










回页首


IBM 公司保留在 developerWorks 网站上发表的内容的著作权。未经IBM公司或原始作者的书面明确许可,请勿转载。如果您希望转载,请通过 提交转载请求表单 联系我们的编辑团队。
    关于 IBM 隐私条约 联系 IBM 使用条款