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

developerWorks 中国  >  XML  >

XML 问题 #2: 将 XML 文档作为对象处理的“Python 化”

将 XML 文档作为对象的“Python 化”处理 (II)

developerWorks
文档选项

未显示需要 JavaScript 的文档选项


级别: 初级

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_picklexml_objectify ,前者用于以 XML 表示任意的 Python对象,后者用于将 XML 文档本机表示为 Python 对象。本文主要讨论 xml_objectify

在 Python 中,例如 xmllibxml.saxpyxiexml.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 从层次上角度上考虑,可以具有 addressmisc-info 。使用 Python,可以用 person.addressperson.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



参考资料



关于作者

如果只为了押韵,David Mertz 希望把这一专栏叫作 "Ex nihilo XML fit";但他认为出版商希望标题能直接反映内容。可以通过 mertz@gnosis.cx 与 David Mertz 取得联系;在 http://gnosis.cx/publish/ 上详细介绍了他的生活。非常欢迎对过去的、这一篇或将来的专栏文章提出意见和建议。




对本文的评价










回页首


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