级别: 初级 David Mertz,博士 (mertz@gnosis.cx), 转换专家, Gnosis Software, Inc.
2000 年 8 月 01 日 在 David Mertz 新的有关“XML 问题”专栏的第二部分 --也是他对在 XML 和 Python之间创建更加无缝的集成而不断进行探求的一部分 -- 中介绍了
xml_objectify 模块。 David 描述了如何使用
xml_objectify ,以及将该“Python 化”模块用于作为对象的XML 文档使用的好处。
项目介绍
XML 问题 #1介绍了在 XML 和 Python之间创建更加无缝和自然集成的项目。
参考资料部分提供了到其它
developerWorks文章的链接,在这些文章中,我讨论了一些常规Python 编程技术和其它有关 XML/Python 的主题。
 |
获得兼容的 XML-SIG 更新
XML-SIG 发行版在 beta 版本中更改相当频繁。这些更改往往影响到
xml_objectify 的功能。因此,可以从
参考资料中下载已知与
xml_objectify 兼容的 XML-SIG 版本。
当 XML-SIG 发行版正式发行并且/或者当 XML 软件包作为正式 Python发行版的一部分时,当前的
xml_objectify 将进行更新以对正式发行版使用。有关当前
xml_objectify 的详细信息,请参阅
参考资料。
|
|
因为在 XML 和 Python 之间存在不对称性,所以该项目 --至少在最初时 -- 包含两个单独的模块:
xml_pickle 和
xml_objectify ,前者用于以 XML 表示任意的 Python对象,后者用于将 XML 文档本机表示为 Python 对象。本文主要讨论
xml_objectify 。
在 Python 中,例如
xmllib 、
xml.sax 、
pyxie 和
xml.dom 这样的模块和软件包提供了处理 XML社区中一些公共 XML文档的方法。您可能熟悉应用于其它编程语言的类似模块和库。实际上,许多模块都基于语言中性的XML 标准,它们通常实现以 XML 为中心的处理文档和对象的方法。
常规 XML 协议的 Python实现提供了以不同方法进行编程的灵活性。例如,可以使用如 DOM这样的可移植标准,这样,使用一种语言的程序员可以方便地对以另一种语言编写的面向DOM 的代码进行操作。不过, Python程序员有时可能宁愿以更类似于“正常”Python的方法进行编码。在许多情况下,XML 概念性框架看起来似乎更接近于Python,而不是 Python 的一个组成部分。因此,我开发了一系列用于 XML文档的“Python 化”模块。
向前一步:如何使用xml_objectify
使用
xml_objectify 很简单,而且在模块 docstring注释中有详细记载。让我们快速浏览一下一些样本代码:
从 XML 文档创建 Python对象
from
xml_objectify
import
XML_Objectify
xml_obj = XML_Objectify(
'address.xml')
py_obj = xml_obj.make_instance()
|
如您所见,从常规 XML 文档创建本机 Python对象有两个步骤。首先创建一个类似于 DOM的中间工厂对象(即用于创建其它对象的对象)。然后,从
XML_Objectify 实例中生成一个或多个 Python对象实例。请注意,应该使用
xml_pickler 来处理特殊的
PyObjects.dtd 格式文档。(请参阅
XML 问题 #1了解有关
xml_pickle 的信息。)
也可以在同一行上执行这两步。例如:
在一行中创建 XML/Python 对象
py_obj = XML_Objectify(
'address.xml').make_instance()
|
当然,在后一种情况中,不保留工厂对象来产生更多本机对象,而且也将清除包含它的完整DOM 实例的
._dom 数据成员。
为进行比较,下例显示了使用 Python 创建 DOM 对象有多简单:
从 XML 文档创建 DOM 对象
from xml.dom.utils import FileReader
dom_obj =
FileReader().readXml(open('address.xml'))
|
FileReader().readXml() 需要实际的文件对象,而
XML_Objectify() 可以接受文件对象或者普通文件名。在这两种情况下,创建对象是一个两行的操作。
使用
xml_objectify 模块和
xml.dom 软件包的不同之处在于最后得到的对象类型。Python DOM 对象是真正的Python 对象,但它的属性和方法与原始 XML文档的数据和结构对应程度并不象
XML_Objectify 对象那样接近。Python DOM 对象的属性通常嵌套了
.children 列表,这些列表在语义上并没有什么太大的帮助。要访问样本文档中同一个XML 属性,可以使用
xml_objectify 的第一行,也可以使用DOM 的以下四行。下面显示了这四行:
使用 [xml.dom] 和 [xml_objectify] Python对象
print
py_obj.person[1].address.city
print
dom_obj.get_childNodes()[1].get_childNodes()[3].\
get_childNodes()[3].get_attributes()[
'city'].value
print
dom_obj._node.children[1].children[3].children[3].\
attributes[
'city'].children[0].value
|
DOM树是按严格定序的节点树组织起来的。枚举这些节点并不困难,但要引用其中特定的节点就非常麻烦了。如果有些节点是空白文本和处理指令节点(您几乎不太关心它们),情况就更糟糕了,因此在节点列表中查找子标记多半要反复试验。在上例中,访问本机属性(例如
.children )和 DOM 样式的方法(例如
.get_childNodes() )用在不同的
print 语句中。使用这两种方法时,要知道引用了 XML文档中哪些数据都很不容易。
相反,上例中第一个
print 语句非常好地文档化了自己。唯一需要注意的一个小问题是必须使用 Python的基于 0 的列表索引。除此以外,这行只说: "Print the city of theaddress of the second person in the addressbook." ("New York"是每个语句都要打印的。)为进一步帮助您理解,
py_obj.__class__ 就是 "addressbook",与 XML文档的根元素对应。每个不仅包含简单文本的属性都是特定类的实例;这个特定类是根据定义它的XML 标记命名的。
如您所见,
xml.dom 使用起来通常比较难,它的语法也很模糊。本机 Python对象使用起来就简单得多。请注意,
xml_objectify 在内部广泛利用了 DOM。实际上,每个
XML_Objectify 实例都包含了一个
._dom 属性,该属性是打开的 XML 文档的DOM 树。不过,创建的实例
.make_instance 不包含任何DOM,它是根标记的类类型。
设计注意事项、限制和告诫
代码自测
使用
xml_objectify ,可以利用所有现有的常规函数。
pyobj_printer() 是个样本常规函数,其中包括
xml_objectify 模块。该函数产生
所有Python 对象可读的递归表示。通过将 XML文档表示为本机 Python 文档,可以重用现有的、以抽象方式处理 Python对象的函数。当然,DOM 对象勉强算得上是 Python对象,但要对这些对象以有用的方式使用常规函数就比较困难。例如,因为DOM 对象的属性嵌套了
.children 列表,所以使用例如
pyobj_printer() 这样的常规函数将不会产生非常有用的输出。
使用类行为的技巧
xml_objectify 提供了一种非常精妙的技巧,使用这种技巧,只有在没有定义类的情况下才动态地为属性值定义类。这可以让您定义具有复杂行为的类,和可以放入特定XML 文档内容的属性。假设类
person 使用各种方法(如果需要,包括
.__init__() 方法)进行了预定义。导入到上例中 XML 地址簿中的每个 "person"都将具有给予它的所有行为,包括对放入实例中的数据的操作方法。当然,如果在对文档运行
XML_Objectify() 之前没有进行预定义,类就只是用于在实际XML 中定义的属性的容器。
字符标记处理
XML 标记通常是块级别的,但某些也属于字符级别。依我看,自然的 Python表示在每种情况下各不相同。块级别的子标记易于通过父标记的属性表示,父标记是根据子标记命名的。子标记属性的值是新Python 对象,它也是根据子标记命名的类型。例如,
person 从层次上角度上考虑,可以具有
address 和
misc-info 。使用 Python,可以用
person.address 和
person.misc_info 来引用它们。
使用字符级别的标记时,标记的
内容是文本数据和这些数据的标记(常常是排版方面的)的混合体,子标记在层次结构上不是父标记中的一部分。例如,
misc_info 对象实际上没有
ital 属性。那么,下面的 XML 类型应该如何表示呢?
<misc-info>One of the <ital>most</ital> talented
actresses on TV.</misc-info>
|
xml_objectify 将一个称为
._XML 的特殊属性添加到看起来包含标记字符数据的对象/标记。这个属性在标记中包含文字XML。例如,如果给定的嵌套对象有
._XML 属性,
pyobj_printer() 函数显示这个文字 XML而不是递归属性。不过,仍然执行标准递归子标记-对象的创建,因此可以知道哪些属性和结构最适当。
本机 Python 对象只包含根文档
许多 XML文档都伴随着标记和字符数据内容提供处理指令和/或注释。不过,由
XML_Objectify 对象的
.make_instance() 方法创建的本机 Python 对象只包含文档根标记的内容。而且将忽略 XML注释;只表示标记属性和字符数据。
在上面的
从 XML 文档创建 Python对象示例中,如果保留原始的
XML_Objectify 对象(
xml_obj ),可以访问它的
.processing_instruction 属性,甚至可以访问它的
._dom 属性来查看本机 Python 对象忽略了什么。
属性类型简化
所有 XML 属性都转换成字符串类型的 Python 对象属性。当前,Python不表示属性的 XML枚举或数字类型。在以后的版本中可能加入这样的能力,但这些通常需要DTD,而
xml_objectify 不具有 DTD。
子标记属性
XML 子标记
或者由对象类型的 Python属性表示,
或者由这种对象的列表表示,这要取决于是一个还是几个相同类型的子标记。即由包含相同类型多个子标记的特定标记决定。例如,在上面第一个
address.xml 示例中,一个人的联系信息可能包括 1个家庭电话,而另一个人的联系信息可能包括 0 个或几个。相应地,一些
contact_info 对象没有
.home_phone 属性,一些具有包含一个
home_phone 对象的
.home_phone 属性,还有一些具有包含许多
home_phone 对象的
.home_phone 属性。尽管使用 DTD 的情况下有可能施加更多法则,但我认为,Python应用程序需要这种动态能力。
Python 名称空间限制
要知道 Python 名称空间比 XML名称空间小。因此,有时要修改标记或属性的 XML名称。
xml_objectify 将破折号、冒号和镑值/散列标志转换成下划线。该模块不处理任何其它名称空间冲突。例如,如果XML 文档有标记
<spam-eggs> 、
<spam_eggs> 、
<spam:eggs> 和
<spam#eggs> ,那么
xml_objectify 所创建的 Python 对象就不能正确表示 XML文档。在大多数情况下,这不是问题,因为人们不希望得到的 XML文档具有这些冲突的标记。
xml_objectify的前景如何?
目前,还不能将本机 Python 对象转回带有与读取的 XML 文档相同结构的XML 文档。因为
xml_objectify 故意舍去 XML文档中有关顺序的信息来产生更易理解的 Python对象,所以可能发生问题。Python 属性没有任何预先确定的顺序,但需要XML 标记和属性以特定的顺序排列。即使不
需要XML标记来以特定的顺序出现,顺序在语义上还是很重要。(请注意在重复公共子标记的情况下,Python列表维护顺序。)为了转换回XML,我们需要或者选择任意顺序,或者保留本机 Python对象中的顺序信息,使得它看上去不太象 Python。
重新构造 Python 对象中已删除的信息的一个选择是在转换回 XML时强制 DTD。即使我使用这一做法的,但在如何处理 Python运行时添加、删除或修改的属性时,问题仍然存在。修改 Python对象可能导致有些部分不符合原始 XML 文档的DTD。不过,如果用户有特定需要,我会将这些能力添加到
xml_objectify 。
参考资料
关于作者
对本文的评价
|