欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

详解Python中DOM方法的动态性

程序员文章站 2022-06-10 18:38:24
文档对象模型 xml.dom 模块对于 python 程序员来说,可能是使用 xml 文档时功能最强大的工具。不幸的是,xml-sig 提供的文档目前来说还比较少。w3c...

文档对象模型

xml.dom 模块对于 python 程序员来说,可能是使用 xml 文档时功能最强大的工具。不幸的是,xml-sig 提供的文档目前来说还比较少。w3c 语言无关的 dom 规范填补了这方面的部分空白。但 python 程序员最好有一个特定于 python 语言的 dom 的快速入门指南。本文旨在提供这样一个指南。在 上一篇专栏文章 中,某些样本中使用了样本 quotations.dtd 文件,并且这些文件可以与本文中的代码样本档案文件一起使用。

有必要了解 dom 的确切含义。这方面,正式解释非常好:

    “文档对象模型”是平台无关和语言无关的接口,它允许程序和脚本动态访问和更新文档的内容、结构和样式。可以进一步处理文档,而处理的结果也可以合并到已显示的页面中。(万维网联盟 dom 工作组)

dom 将 xml 文档转换成树 -- 或森林 -- 表示。万维网联盟 (w3c) 规范给出了一个 html 表的 dom 版本作为例子。

详解Python中DOM方法的动态性

如上图所示,dom 从一个更加抽象的角度定义了一组可以遍历、修剪、改组、输出和操作树的方法,而这种方法要比 xml 文档的线性表示更为便利。

将 html 转换成 xml

有效的 html 几乎就是有效的 xml,但又不完全相同。这里有两个主要的差异,xml 标记是区分大小写的,并且所有 xml 标记都需要一个显式的结束符号(作为结束标记,而这对于某些 html 标记是可选的;例如: <img src="x.png" /> )。使用 xml.dom 的一个简单示例就是使用 htmlbuilder() 类将 html 转换成 xml。
try_dom1.py

"""convert a valid html document to xml
  usage: python try_dom1.py < infile.html > outfile.xml
"""
    
    
import
    
     sys
    
    from
    
     xml.dom 
    
    import
    
     core
    
    from
    
     xml.dom.html_builder 
    
    import
    
     htmlbuilder
    
    # construct an htmlbuilder object and feed the data to it
b = htmlbuilder()
b.feed(sys.stdin.read())
    
    # get the newly-constructed document object
doc = b.document
    
    # output it as xml
    
    
print
    
     doc.toxml()

htmlbuilder() 类很容易实现它继承的部分基本 xml.dom.builder 模板的功能,它的源码值得研究。然而,即使我们自己实现了模板功能,dom 程序的轮廓还是相似的。在一般情况下,我们将用一些方法构建一个 dom 实例,然后对该实例进行操作。dom 实例的 .toxml() 方法是一种生成 dom 实例的字符串表示的简单方法(在以上的情况中,只要在生成后将它打印出来)。

将 python 对象转换成 xml

python 程序员可以通过将任意 python 对象导出为 xml 实例来实现相当多的功能和通用性。这就允许我们以习惯的方式来处理 python 对象,并且可以选择最终是否使用实例属性作为生成 xml 中的标记。只需要几行(从 building.py 示例派生出),我们就可以将 python“原生”对象转换成 dom 对象,并对包含对象的那些属性执行递归处理。
try_dom2.py

"""build a dom instance from scratch, write it to xml
  usage: python try_dom2.py > outfile.xml
"""
    
    
import
    
     types
    
    from
    
     xml.dom 
    
    import
    
     core
    
    from
    
     xml.dom.builder 
    
    import
    
     builder
    
    # recursive function to build dom instance from python instance
    
    
defobject_convert
    
    (builder, inst):
  
    
    # put entire object inside an elem w/ same name as the class.
  builder.startelement(inst.__class__.__name__)
  
    
    for
    
     attr 
    
    in
    
     inst.__dict__.keys():
    
    
    if
    
     attr[0] == 
    
    '_':   
    
    # skip internal attributes
              
     
     continue
    
    
    value = getattr(inst, attr)
    
    
    if
    
     type(value) == types.instancetype:
      
    
    # recursively process subobjects
      object_convert(builder, value)
    
    
    else
    
    :
      
    
    # convert anything else to string, put it in an element
      builder.startelement(attr)
      builder.text(str(value))
      builder.endelement(attr)
  builder.endelement(inst.__class__.__name__)
    
    if
    
     __name__ == 
    
    '__main__':
  
    
    # create container classes
    
    
  classquotations
    
    : 
    
    pass
  classquotation
    
    : 
    
    pass
    # create an instance, fill it with hierarchy of attributes
    
    
  inst = quotations()
  inst.title = 
    
    "quotations file (not quotations.dtd conformant)"
  inst.quot1 = quot1 = quotation()
  quot1.text = 
    
    """'"is not a quine" is not a quine' is a quine"""
  quot1.source = 
    
    "joshua shagam, kuro5hin.org"
  inst.quot2 = quot2 = quotation()
  quot2.text = 
    
    "python is not a democracy. voting doesn't help. "+\
         
    
    "crying may..."
  quot2.source = 
    
    "guido van rossum, comp.lang.python"
    
    

     # create the dom builder
  builder = builder()
  object_convert(builder, inst)
  
    
    print
    
     builder.document.toxml()

函数 object_convert() 有一些限制。例如,不可能用以上的过程生成符合 xml 文档的 quotations.dtd:#pcdata 文本不能直接放到 quotation 类中,而只能放到类的属性中(如 .text )。一个简单的变通方法就是让 object_convert() 以特殊方式处理一个带有名称的属性,例如 .pcdata 。可以用各种方法使对 dom 的转换变得更巧妙,但该方法的妙处在于我们可以从整个 python 对象开始,以简明的方式将它们转换成 xml 文档。

还应值得注意的是在生成的 xml 文档中,处于同一个级别的元素没有什么明显的顺序关系。例如,在作者的系统中使用特定版本的 python,源码中定义的第二个 quotation 在输出中却第一个出现。但这种顺序关系在不同的版本和系统之间会改变。python 对象的属性并不是按固定顺序排列的,因此这种特性就具有意义。对于与数据库系统相关的数据,我们希望它们具有这种特性,但是对于标记为 xml 的文章却显然不希望具有这种特性(除非我们想要更新 william burroughs 的 "cut-up" 方法)。

将 xml 文档转换成 python 对象

从 xml 文档生成 python 对象就像其逆向过程一样简单。在多数情况下,用 xml.dom 方法就可以了。但在某些情况下,最好使用与处理所有“类属”python 对象相同的技术来处理从 xml 文档生成的对象。例如,在以下的代码中,函数 pyobj_printer() 也许是已经用来处理任意 python 对象的函数。
try_dom3.py

"""read in a dom instance, convert it to a python object
"""
    
    
from
    
     xml.dom.utils 
    
    import
    
     filereader
    
    classpyobject
    
    : 
    
    pass
defpyobj_printer
    
    (py_obj, level=0):
  
    
    """return a "deep" string description of a python object"""
     
     
     from
     
     
    
     string 
    
    import
    
     join, split
  
    
    import
    
     types
  descript = 
    
    ''
     
     
     for
     
     
    
     membname 
    
    in
    
     dir(py_obj):
    member = getattr(py_obj,membname)
    
    
    if
    
     type(member) == types.instancetype:
      descript = descript + (
    
    ' '*level) + 
    
    '{'+membname+
    
    '}\n'
      descript = descript + pyobj_printer(member, level+3)
    
    
    elif
    
     type(member) == types.listtype:
      descript = descript + (
    
    ' '*level) + 
    
    '['+membname+
    
    ']\n'
             
     
     for
     
     
    
     i 
    
    in
    
     range(len(member)):
        descript = descript+(
    
    ' '*level)+str(i+1)+
    
    ': '+ \
              pyobj_printer(member[i],level+3)
    
    
    else
    
    :
      descript = descript + membname+
    
    '='
      descript = descript + join(split(str(member)[:50]))+
    
    '...\n'
     
     
     return
     
     
    
     descript
    
    defpyobj_from_dom
    
    (dom_node):
  
    
    """converts a dom tree to a "native" python object"""
  py_obj = pyobject()
  py_obj.pcdata = 
    
    ''
     
     
     for
     
     
    
     node 
    
    in
    
     dom_node.get_childnodes():
    
    
    if
    
     node.name == 
    
    '#text':
      py_obj.pcdata = py_obj.pcdata + node.value
    
    
    elif
    
     hasattr(py_obj, node.name):
      getattr(py_obj, node.name).append(pyobj_from_dom(node))
    
    
    else
    
    :
      setattr(py_obj, node.name, [pyobj_from_dom(node)])
  
    
    return
    
     py_obj
    
    # main test
dom_obj = filereader(
    
    "quotes.xml").document
py_obj = pyobj_from_dom(dom_obj)
    
    if
    
     __name__ == 
    
    "__main__":
  
    
    print
    
     pyobj_printer(py_obj)

这里的关注焦点应该是函数 pyobj_from_dom() ,特别是起实际作用的 xml.dom 方法 .get_childnodes() 。在 pyobj_from_dom() 中,我们直接抽取标记之间的所有文本,将它放到保留属性 .pcdata 中。对于任何遇到的嵌套标记,我们创建一个新属性,其名称与标记匹配,并将一个列表分配给该属性,这样就可以潜在地包含在在父代块中多次出现的标记。当然,使用列表要维护在 xml 文档中遇到的标记的顺序。

除了使用旧的 pyobj_printer() 类属函数(或者,更复杂和健壮的函数)之外,我们可以使用正常的属性记号来访问 py_obj 的元素。
python 交互式会话

>>> 
    
    from
    
     try_dom3 
    
    import
    
     *
>>> py_obj.quotations[0].quotation[3].source[0].pcdata
    
    'guido van rossum, '

重新安排 dom 树

dom 的一大优点是它可以让程序员以非线性方式对 xml 文档进行操作。由相匹配的开/关标记括起的每一块都只是 dom 树中的一个“节点”。当以类似于列表的方式维护节点以保留顺序信息时,则顺序并没有什么特殊之处,也并非不可改变。我们可以轻易地剪下某个节点,嫁接到 dom 树的另一个位置(如果 dtd 允许,甚至嫁接到另一层上)。或者添加新的节点、删除现有节点,等等。
try_dom4.py

"""manipulate the arrangement of nodes in a dom object
"""
    
    
from
    
     try_dom3 
    
    import
    
     *
    
    #-- var 'doc' will hold the single <quotations> "trunk"
doc = dom_obj.get_childnodes()[0]
    
    #-- pull off all the nodes into a python list
# (each node is a <quotation> block, or a whitespace text node)
nodes = []
    
    while
    
     1:
  
    
    try
    
    : node = doc.removechild(doc.get_childnodes()[0])
  
    
    except
    
    : 
    
    break
    
    
  nodes.append(node)
    
    #-- reverse the order of the quotations using a list method
# (we could also perform more complicated operations on the list:
# delete elements, add new ones, sort on complex criteria, etc.)
nodes.reverse()
    
    #-- fill 'doc' back up with our rearranged nodes
    
    
for
    
     node 
    
    in
    
     nodes:
  
    
    # if second arg is none, insert is to end of list
  doc.insertbefore(node, none)
    
    #-- output the manipulated dom
    
    
print
    
     dom_obj.toxml()

如果我们将 xml 文档只看作一个文本文件,或者使用一个面向序列的模块(如 xmllib 或 xml.sax),那么在以上几行中执行对 quotation 节点的重新安排操作将引出一个值得考虑的问题。然而如果使用 dom,则问题就如同对 python 列表执行的任何其它操作一样简单。