重温Python的XML工具

发表于:2007-07-04来源:作者:点击数: 标签:
David Mertz 创作的可爱的 Python 的第一、第二部分概述了在 Python 中使用 XML。然而,在那些最初的文章出现后,Python 中的 XML 工具有了很大的发展。不幸的是,这些改进中的大多数并不向后兼容。在这个特别部分中,重温了作者先前对 XML 工具的讨论,并提

David Mertz 创作的可爱的 Python 的第一、第二部分概述了在 Python 中使用 XML。然而,在那些最初的文章出现后,Python 中的 XML 工具有了很大的发展。不幸的是,这些改进中的大多数并不向后兼容。在这个特别部分中,重温了作者先前对 XML 工具的讨论,并提供最新的代码示例。

在许多情况下,Python 是使用 XML 文档的理想语言。像 Perl、REBOL、REXX 和 TCL 一样,它是一种灵活的脚本语言,并且有强大的文本操作能力。而且,除了对多数类型的文本文件(或流文件)编码外,XML 文档还编码大量复杂的数据结构。

继续在 Python 2.0 中对 XML 的支持
文本处理中常见的“读取几行,并将它们与一些规则表达式比较”样式通常不能很好地适合对 XML 进行彻底语法分析和处理。幸好,Python(与大多数其它语言相比)不仅有处理复杂数据结构的直接方法(通常使用类和属性),还有一系列 XML 相关的模块可以帮助语法分析、处理和生成 XML。

XML-SIG (专门兴趣组)的成员为维护 Python 一系列 XML 工具做了许多工作。与其它 Python 专门兴趣组一样,XML-SIG 要维护邮件发送列表、列表档案、有用的参考大全、文档、标准包和其它资源(请参阅本文后的参考资料)。

从 Python 2.0 开始,Python 在其标准发行版中包括大多数 XML-SIG 项目。最新的 XML-SIG 包可能包含一些 Python 标准发行版中没有的“极端先进”特性,但出于面向绝大多数人的目的 -- 包括本文中的讨论 -- Python 2.0 的 XML 支持将是您感兴趣的。幸运的是,早期 Python 版本对 xmllib 的基本支持在 Python 2.0+ 下有了很大进步。目前,Python 用户能正常的选择 DOM、SAX 和 expat 技术来处理 XML (使用其他编程语言的 XML 开发人员将会意识到这些)。

模块:xmllib
xmllib 是一个非验证的低级语法分析器。应用程序员使用的 xmllib 可以覆盖 XMLParser 类,并提供处理文档元素(如特定或类属标记,或字符实体)的方法。从 Python 1.5x 到 Python 2.0+ 以来,xmllib 的使用方法并没变化;在绝大多数情况下更好的选择是使用 SAX 技术,它也是种面向流的技术,对语言和开发者来说更为标准。

本文中的示例与原来专栏中的相同:包括一个叫做 quotations.dtd 的 DTD 以及这个 DTD 的文档 sample.xml (请参阅参考资料,以获取本文中提到的文件的档案)。以下的代码显示了 sample.xml 中每段引言的前几行,并生成了非常简单的未知标记和实体的 ASCII 指示符。经过分析的文本作为连续流来处理,所使用的任何累加器都由程序员负责(如标记中的字符串 (#PCDATA),或所遇到的标记的列表或词典)。

清单 1: try_xmllib.py

import xmllib, string

classQuotationParser(xmllib.XMLParser):
    """Crude xmllib extractor for quotations.dtd document"""
    def__init__(self):
        xmllib.XMLParser.__init__(self)
        self.thisquote = ''             # quotation aclearcase/" target="_blank" >ccumulator
    defhandle_data(self, data):
        self.thisquote = self.thisquote + data
    defsyntax_error(self, message):
        pass
    defstart_quotations(self, attrs):  # top level tag
        print '--- Begin Document ---'
    defstart_quotation(self, attrs):
        print 'QUOTATION:'
    defend_quotation(self):
        print string.join(string.split(self.thisquote[:230]))+'...',
        print '('+str(len(self.thisquote))+' bytes)'
        self.thisquote = ''
    defunknown_starttag(self, tag, attrs):
        self.thisquote = self.thisquote + '{'
    defunknown_endtag(self, tag):
        self.thisquote = self.thisquote + '}'
    defunknown_charref(self, ref):
        self.thisquote = self.thisquote + '?'
    defunknown_entityref(self, ref):
        self.thisquote = self.thisquote + '#'

if __name__ == '__main__':
    parser = QuotationParser()
    for c in open("sample.xml").read():
        parser.feed(c)
    parser.close()

 

 

验证
您可能需要展望标准 XML 支持的未来的原因是,在进行语法分析的同时需要进行验证。不幸的是,标准 Python 2.0 XML 包并不包括验证型语法分析器。

xmlproc 是 python 原有的语法分析器,它执行几乎完整的验证。如果需要验证型语法分析器, xmlproc 是 Python 当前唯一的选择。而且,xmlproc 提供其它语法分析器所不具备的各种高级和测试接口。

选择一种语法分析器
如果决定使用 XML 的简单 API (SAX) -- 它应该用于复杂的事物,因为其它大部分工具都是在它的基础上建立的 -- 将为您完成许多语法分析器的分类工作。xml.sax 模块包含一个自动选择“最佳”语法分析器的设施。在标准 Python 2.0 安装中,唯一能选择的语法分析器是 expat,它是种 C 语言编写的快速扩展。然而,也可以在 $PYTHONLIB/xml/parsers 下安装另一个语法分析器,以备选择。设置语法分析器很简单:

清单 2: Python 选择最佳语法分析器的语句

import xml.sax
parser = xml.sax.make_parser()

 

 

您还可以通过传递参数来选择特定的语法分析器;但考虑到可移植性 -- 也为了对今后更好的语法分析器的向上兼容性 -- 最佳方法是使用 make_parser() 来完成工作。

您可以直接导入 xml.parsers.expat。如果这样做,您就能获得 SAX 界面并不提供的一些特殊技巧。这样,xml.parsers.expat 与 SAX 相比有些“低级”。但 SAX 技术非常标准,对面向流的处理也非常好;大多数情况下 SAX 的级别正合适。通常情况下,由于 make_parser() 函数已经能获得 expat 提供的性能,因此纯速度的差异很小。

什么是 SAX
考虑到背景因素,回答什么是 SAX 的较好答案是:

SAX (XML 的简单 API)是 XML 语法分析器的公用语法分析器接口。它允许应用程序作者编写使用 XML 语法分析器的应用程序,但是它却独立于所使用的语法分析器。(将它看作 XML 的 JDBC。)(Lars Marius Garshol,SAX for Python)
SAX -- 如同它提供的语法分析器模块的 API -- 基本上是一个 XML 文档的顺序处理器。使用它的方法与 xmllib 示例极其相似,但更加抽象。应用程序员将定义一个 handler 类,而不是语法分析器类,该 handler 类能注册到任何所使用的语法分析器中。必须定义 4 个 SAX 接口(每个接口都有几个方法):DocumentHandler、DTDHandler、EntityResolver 和 ErrorHandler。创建语法分析器除非被覆盖,否则它还连接默认接口。这些代码执行与 xmllib 示例相同的任务:

清单 3: try_sax.py

"Simple SAX example, updated for Python 2.0+"
import string
import xml.sax
from xml.sax.handler import *

classQuotationHandler(ContentHandler):
    """Crude extractor for quotations.dtd compliant XML document"""
    def__init__(self):
        self.in_quote = 0
        self.thisquote = ''
    defstartDocument(self):
        print '--- Begin Document ---'
    defstartElement(self, name, attrs):
        if name == 'quotation':
            print 'QUOTATION:'
            self.in_quote = 1
        else:
            self.thisquote = self.thisquote + '{'
    defendElement(self, name):
        if name == 'quotation':
            print string.join(string.split(self.thisquote[:230]))+'...',
            print '('+str(len(self.thisquote))+' bytes)'
            self.thisquote = ''
            self.in_quote = 0
        else:
            self.thisquote = self.thisquote + '}'
    defcharacters(self, ch):
        if self.in_quote:
            self.thisquote = self.thisquote + ch

if __name__ == '__main__':
    parser = xml.sax.make_parser()
    handler = QuotationHandler()
    parser.setContentHandler(handler)
    parser.parse("sample.xml")

 

 

与 xmllib 相比,上述示例中要注意两件小事:.parse() 方法处理整个流或字符串,所以不必为语法分析器创建循环;.parse() 同样能灵活地接收一个文件名、一个文件对象,或是众多的类文件对象(一些具有 .read() 方式)。

包:DOM
DOM 是一种 XML 文档的高级树型表示。该模型并非只针对 Python,而是一种普通 XML 模型(请参阅参考资料以获取进一步信息)。Python 的 DOM 包是基于 SAX 构建的,并且包括在 Python 2.0 的标准 XML 支持里。由于篇幅所限,没有将代码示例加到本文中,但在 XML-SIG 的 "Python/XML HOWTO" 中给出了一个极好的总体描述:

文档对象模型为 XML 文档指定了树型表示。顶级文档实例是树的根,它只有一个子代,即顶级元素实例;这个元素有表示内容和子元素的子节点,他们也可以有子代,以此类推。定义的函数允许随意遍历结果树,访问元素和属性值,插入和删除节点,以及将树转换回 XML。

 

DOM 可以用于修改 XML 文档,因为可以创建一棵 DOM 树,通过添加新节点和来回移动子树来修改这棵树,然后生成一个新的 XML 文档作为输出。您也可以自己构造一棵 DOM 树,然后将它转换成 XML;用这种方法生成 XML 输出比仅将 <tag1>...</tag1> 写入文件的方法更灵活。

 

使用 xml.dom 模块的语法与早期的文章相比有了一些变动。Python 2.0 中自带的 DOM 实现被称为 xml.dom.minidom,并提供轻量级和小型版本的 DOM。显然,完整的 XML-SIG 的 DOM 中有些试验性的特性并未被放入 xml.dom.minidom 中,但大家并不会注意到这一点。

生成 DOM 对象很简单;只需:

清单 4: 在 XML 文件中创建 Python DOM 对象

from xml.dom.minidom import parse, parseString
dom1 = parse('mydata.xml') # parse an XML file by name

 

 

使用 DOM 对象是种非常直接的 OOP 模式的工作。然而,经常在无法立刻简单区分的层级(除了循环列举)中碰到许多类似清单的属性。例如,以下是一段普通的 DOM Python 代码片断:

清单 5: 通过 Python DOM 节点对象的迭代

for node in dom_node.childNodes:
    if node.nodeName == '#text':      # PCDATA is a kind of node,
        PCDATA = node.nodeValue       # but not a new subtag
    elif node.nodeName == 'spam':
        spam_node_list.append(node) # Create list of <spam> nodes

 

 

Python 标准说明文档中有一些更详细的 DOM 示例。我的早期文章中有关使用 DOM 对象的示例(请参阅参考资料)指出的方向仍然是正确的,但是文章发布后至今,一些方法和属性名称以更改,因此请查阅一下 Python 的说明文档。

模块: pyxie
pyxie 模块是在 Python 标准 XML 支持之上构建的,它为 XML 文档提供了附加的高级接口。pyxie 将完成两项基本操作:它将 XML 文档转换成一种更易于进行语法分析的基于行的格式;并且它提供了将 XML 文档当作可操作树处理的方法。pyxie 所使用的基于行的 PYX 格式是不受语言限制的,其工具适用于几种语言。总之,文档的 PYX 表示与其 XML 表示相比,更易于使用常见的基于行的文本处理工具进行处理,如 grep、sed、awk、bash、perl,或标准 python 模块,如 string 和 re。根据结果,从 XML 转换到 PYX 可能节省许多工作。

pyxie 将 XML 文档当作树处理的概念与 DOM 中的思路相似。由于 DOM 标准得到许多编程语言的广泛支持,那么如果 XML 文档的树型表示是必需的,大多数程序员会使用 DOM 标准而非 pyxie。

更多模块:xml_pickle 和 xml_objectify
我自行开发了处理 XML 的高级模块,称为 xml_pickle 和 xml_objectify。我还在其它地方写过许多类似模块(请参阅参考资料),在此不必做过多的介绍。当你“用 Python 思考”而不是“用 XML 思考”时,这些模块非常有用。特别是 xml_objectify 自身对程序员隐藏了几乎所有的 XML 线索,使您在程序中充分使用 Python “原始”对象。实际的 XML 数据格式几乎被抽象得不可见。同样,xml_pickle 使 Python 程序员以“原始” Python 对象开始,该对象的数据可以来源于任何源代码,然后把它们(连续地)放入其他用户以后可能需要的 XML 格式。

参考资料

如想获得处理 XML 的 Python 2.0+ 模块的详细说明文档,最佳起点是 Python 库参考大全中的结构化标记处理工具部分。请搜索所有文件名以 xml 开头的包。
Python XML 专门兴趣组是个提供讨论的论坛,同时提供使用 Python 处理 XML 的工具实现。而且,Python Software Foundation 维护着一系列专门兴趣组 (SIGs),目标为“集中合作力量开发、提高或维护专门 Python 资源”。
在 Vaults of Parnassus XML 页面上有一个出色的 Python 代码和工具库。
请访问 Pyxie 主页,下载 Pyxie 和相关实用工具,并获得关于 Pyxie 和 PYX 的常见问题答案。
从 gnosis.cx/download/charming_python_1r.zip 获得文中提及和使用的文件 (quotations.dtd, sample.xml, try_sax.py, try_sax.pyc, try_xmllib.py)。
作者 David Mertz 文中提到的可爱的 Python 中两部分原文在 将 XML 和 Python 结合起来 (2000 年 6 月)和 DOM 的动态性(2000 年 7 月)。请参阅作者的可爱的 Python 专栏中的其他文章:

在 Python 下开发全文 indexer(2001 年 4 月)
Python 中的函数型编程,第 1 部分(2001 年 3 月)第 2 部分(2001 年 4 月)
获得版本 2.0(2001 年 2 月)
更新您的 Python 阅读清单(2001 年 2 月)
针对 .NET 的 JPython 和 Python 内部资源(2000 年 12 月)
Python 中的 TK 编程(2000 年 12 月)
直接快速的重新装载(2000 年 11 月)
Python 实施的内部资源(2000 年 10 月)
诅咒编程(2000 年 9 月)
Python 中的文本处理(2000 年 9 月)
使用状态机(2000 年 8 月)
我的第一个基于 Web 的过滤代理服务器(2000 年 7 月)
另见
XML 问题 #1: 将 XML 文档作为对象的“Python 化”处理 (2000 年 8 月)
XML 问题 #2: 将 XML 文档作为对象的“Python 化”处理 (II)(2000 年 8 月)


有关作者
David 认为愚蠢的结合是没脑子的妖怪所为,并在其写作中努力追求。可通过 mertz@gnosis.cx与 David 联系;他的精力完全投入在 http://gnosis.cx/publish/ 上。非常欢迎对过去的、这一篇或将来的专栏文章提出意见和建议。

原文转自:http://www.ltesting.net