lxml.etree API的一些细节说明

lxml.etree API的一些细节说明

lxml.etree努力尽可能地与已建立的API保持一致。然而,有时为了用一种简单的方式暴漏某个特性,导致一个新的API产生。这个页面描述了主要的差异,以及一些相较于ElementTree API新增的部分。

如果需要完整的API参考,请参见“generated API documentation”。

lxml是极度可扩展的,主要体现在XPath functions in Python,定制的python元素类,定制的URL解析器,甚至在C级别。

包含内容:
- lxml.etree
- 其他元素API(Other Element APIs)
- “树”和文档(Trees and Documents)
- 迭代(Iteration)
- 错误处理和异常(Error handling on exceptions)
- 错误记录(Error logging)
- 序列化(Serialisation)
- 增量XML生成(Incremental XML generation)
- CDATA
- XInclude and ElementInclude
- write_c14n on ElementTree

lxml.etree

lxml.etree努力与ElementTree API保持一致,如果可以。然而,也存在一些不适用。这些扩展将在这儿被记录。

如果你需要知道当前安装的lxml版本是多少,你可以访问属性lxml.etree.LXML_VERSION来检索版本元组。注,然而,在版本1.0之前,是不存在该属性的。所以在早期的版本中,你会得到一个AttributeError。对于libxml2libxslt的版本,你同样可以通过属性LIBXML_VERSIONLIBXSLT_VERSION获得。

以下的例子都假定你首先执行了以下语句:

>>> from lxml import etree

其他元素API(Other Element APIs)

“树”和文档(Trees and Documents)

与原始的ElementTree API相比,lxml.etree拥有一个扩展过的“树”模型。它知道自己的父节点和相邻节点。

>>> root = etree.Element("root")
>>> a = etree.SubElement(root, "a")
>>> b = etree.SubElement(root, "b")
>>> c = etree.SubElement(root, "c")
>>> d = etree.SubElement(root, "d")
>>> e = etree.SubElement(d,    "e")
>>> b.getparent() == root
True
>>> print(b.getnext().tag)
c
>>> print(c.getprevious().tag)
b

在lxml中,元素(Elements)通常“存活”于文档上下文中。这也意味着存在着一个绝对的文档根的概念。为了获取一个文档的根节点,你可以从它的任意一个元素检索“ElementTree”。

>>> tree = d.getroottree()
>>> print(tree.getroot().tag)
root

注意,这不同于把元素(Element)用“树”(ElementTree)包裹起来。你可以给定明确的根节点,然后使用ElementTree来创建XML“树”。

>>> tree = etree.ElementTree(d)
>>> print(tree.getroot().tag)
d
>>> etree.tostring(tree)
b'<d><e/></d>'

ElementTree对象是作为一个完整的文档序列化的,包括预先、结尾处理声明和注释。

你所有在ElementTree上的操作(比如XPath, XSLT等)都是在明确文档根节点的前提下进行的。在ElementTree之外的任何节点,它们都将“无视”。然而,ElementTree并不会修改它下属的节点。

>>> element = tree.getroot()
>>> print(element.tag)
d
>>> print(element.getparent().tag)
root
>>> print(element.getroottree().getroot().tag)
root

这个规则就是所有应用到元素(Elements)的操作,要么使用它本身作为参考,或者是包含这个节点的文档根节点。所有在“节点树”(ElementTree)的操作都使用它的根节点作为参考。

迭代(Iteration)

ElementTree API为了支持对孩子节点的迭代,使得元素本身就是可迭代的。使用上面定义的树,我们可以获得:

>>> [ child.tag for child in root ]
['a', 'b', 'c', 'd']

为了能够反方向迭代,你可以使用内建函数reversed()(在python2.4及之后的版本中都是可用的)。

“树”递归遍历(即遍历树下的所有节点)应当使用element.iter()方法:

>>> [ el.tag for el in root.iter() ]
['root', 'a', 'b', 'c', 'd', 'e']

作为附加的特性,lxml.etree同样支持对某个节点的“孩子节点”、“前序、后序相邻节点”、“祖先节点”、“子孙节点”进行遍历。

>>> [ child.tag for child in root.iterchildren() ]
['a', 'b', 'c', 'd']
>>> [ child.tag for child in root.iterchildren(reversed=True) ]
['d', 'c', 'b', 'a']
>>> [ sibling.tag for sibling in b.itersiblings() ]
['c', 'd']
>>> [ sibling.tag for sibling in c.itersiblings(preceding=True) ]
['b', 'a']
>>> [ ancestor.tag for ancestor in e.iterancestors() ]
['d', 'root']
>>> [ el.tag for el in root.iterdescendants() ]
['a', 'b', 'c', 'd', 'e']

注意,使用element.iterdescendants()遍历子孙节点时,并不包含它自己。这与element.iter()恰好相反。

上面所有这些迭代器都支持一个(或多个,从lxml3.0开始)额外的参数,以使用标签名来过滤产生的元素:

>>> [ child.tag for child in root.iterchildren('a') ]
['a']
>>> [ child.tag for child in d.iterchildren('a') ]
[]
>>> [ el.tag for el in root.iterdescendants('d') ]
['d']
>>> [ el.tag for el in root.iter('d') ]
['d']
>>> [ el.tag for el in root.iter('d', 'a') ]
['a', 'd']

注意,元素的顺序取决于迭代的顺序,大部分情况下是文档的顺序(除了前序相邻节点、祖先节点,它们的顺序与文档顺序相反)。

最常见的XML树遍历的方式是按照文档顺序遍历的“深度优先”方法。.iter()方法也是按照它实现的。虽然不存在专有的方法实现“广度优先”的遍历方式,但如果使用collections.deque(在python2.4及之后的版本中可用)实现也是相当简单的。

>>> root = etree.XML('<root><a><b/><c/></a><d><e/></d></root>')
>>> print(etree.tostring(root, pretty_print=True, encoding='unicode'))
<root>
  <a>
    <b/>
    <c/>
  </a>
  <d>
    <e/>
  </d>
</root>

>>> queue = deque([root])
>>> while queue:
...    el = queue.popleft()  # pop next element
...    queue.extend(el)      # append its children
...    print(el.tag)
root
a
d
b
c
e

错误处理和异常(Error handling on exceptions)

序列化(Serialisation)

lxml.etree对XML的美化输出有着直接的支持。方法比如ElementTree.write()tostring()是通过关键字参数来支持这一点。

>>> root = etree.XML("<root><test/></root>")
>>> etree.tostring(root)
b'<root><test/></root>'

>>> print(etree.tostring(root, pretty_print=True))
<root>
  <test/>
</root>

注意,如果启用美化输出,文档末尾会追加一个空行。这是在lxml2.0之后追加的特性。

默认情况下,lxml仅在输出到标准输出上时,才会输出XML头声明。

>>> unicode_root = etree.Element( u"t\u3120st" )
>>> unicode_root.text = u"t\u0A0Ast"
>>> etree.tostring(unicode_root, encoding="utf-8")
b'<t\xe3\x84\xa0st>t\xe0\xa8\x8ast</t\xe3\x84\xa0st>'

>>> print(etree.tostring(unicode_root, encoding="iso-8859-1"))
<?xml version='1.0' encoding='iso-8859-1'?>
<t&#12576;st>t&#2570;st</t&#12576;st>

你可以通过关键字参数xml_declaration来明确开启、或关闭在序列化时产生的XML声明:

>>> print(etree.tostring(root, xml_declaration=True))
<?xml version='1.0' encoding='ASCII'?>
<root><test/></root>

>>> unicode_root.clear()
>>> etree.tostring(unicode_root, encoding="UTF-16LE",
...                              xml_declaration=False)
b'<\x00t\x00 1s\x00t\x00/\x00>\x00'

增量XML生成(Incremental XML generation)

从版本3.1开始,lxml提供了一个xmlfile*API用于增量的XML生成,它使用*with声明。它的主要目的是安全、自由地在预先内建于内存中的树下混合周边元素,从而能够写出大型的文档,这些文档通常由大量重复的子树构成。但是,它在许多情况下是非常有用的,比如内存消耗是紧要时,或者XML的生成是按顺序地生成时。从lxml 3.4.1开始,有一个等价的上下文管理器htmlfile用于HTML的序列化。

这个API可以序列化到真实的文件中(文件路径、或文件对象作为参数时),同样也包括类文件对象(io.BytesIO)。这里有一个简单的例子:

>>> f = BytesIO()
>>> with etree.xmlfile(f) as xf:
...     with xf.element('abc'):
...         xf.write('text')

>>> print(f.getvalue().decode('utf-8'))
<abc>text</abc>

xmlfile()可以接收文件路径作为第一个参数,或者是文件(类文件)对象,就像上面的例子那样。在第一种情况中,它要注意打开、关闭它本身,因为文件(类文件)对象默认情况下不会关闭。关闭文件的任务就交给了代码本身。从lxml 3.4开始,你可以传递一个参数close=True来让lxml在退出xmlfile上下文管理器时调用文件对象的close()方法。

为了能够插入预先构造好的元素和子树,你只需把它们传递给write()方法:

>>> f = BytesIO()
>>> with etree.xmlfile(f) as xf:
...     with xf.element('abc'):
...         with xf.element('in'):
...
...             for value in '123':
...                 # construct a really complex XML tree
...                 el = etree.Element('xyz', attr=value)
...
...                 xf.write(el)
...
...                 # no longer needed, discard it right away!
...                 el = None

>>> print(f.getvalue().decode('utf-8'))
<abc><in><xyz attr="1"/><xyz attr="2"/><xyz attr="3"/></in></abc>

通常情况下,会有一个、或多个嵌套的element()块,然后循环构建基于内存的子树(使用ElementTree API,builder API或者其他),并把它们一个接一个地写入到文件中。在这种方式下,(子树)可以在构建之后从内存中移除,这将极大地减少应用的内存消耗,同时也使得从头到尾地XML生成更加简单、安全和正确。

配合使用python修饰器,它可用于异步地、非阻塞地XML生成:

def writer(out_stream):
    with xmlfile(out_stream) as xf:
         with xf.element('{http://etherx.jabber.org/streams}stream'):
              try:
                  while True:
                      el = (yield)
                      xf.write(el)
                      # 此处明确调用flush(),确保缓冲区内容及时写入文件  
                      xf.flush()
              except GeneratorExit:
                  pass

w = writer(stream)
next(w)   # start writing (run up to 'yield')

然后,无论何时,元素准备就绪可写,就可以调用:

w.send(element)

如果写入结束:

w.close()

请注意,在上面的例子中一个额外的调用xf.flush(),这个特性是从lxml3.4之后开启有的。一般情况下,为了避免过度的IO调用,输出流都会被缓冲。无论何时,缓冲的空间被填满之后,它们将被立即写入到文件中。在上面的例子中,我们想确保我们写的每一条消息都及时写到文件中,因此我们明确调用flush方法。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值