前言
Lxml是处理XML和Html的强大工具,但对于文本处理的方式和常见的DOM
不同,特翻译相关文档,并增加部分注释。
1. Elements中的文本
Elements可以包含文本(text)。
>>> root = etree.Element("root")
>>> root.text = "TEXT"
>>> print(root.text)
TEXT
>>> etree.tostring(root)
b'<root>TEXT</root>'
在许多XML文档中(数据密集型文档),这是唯一可以找到文本的地方。它由树层次结构最底层的叶子标签(leaf tag)封装。
但是,如果 XML 用于“标记文本文档”(如 (X)HTML),则文本也可以出现在不同元素之间,恰好在树的中间。
译注:中间是和叶子节点对比的说法,表示没有被最底层的节点包围。
<html><body>Hello<br/>World</body></html>
此处,<br/>
标记被文本包围。这通常称为文档样式(document-style)或混合内容 XML(mixed-content XML)。Elements通过其tail
属性支持这一点。它包含直接跟随该元素的文本,直至 XML 树中的下一个元素:
>>> html = etree.Element("html")
>>> body = etree.SubElement(html, "body")
>>> body.text = "TEXT"
>>> etree.tostring(html)
b'<html><body>TEXT</body></html>'
>>> br = etree.SubElement(body, "br")
>>> etree.tostring(html)
b'<html><body>TEXT<br/></body></html>'
>>> br.tail = "TAIL"
>>> etree.tostring(html)
b'<html><body>TEXT<br/>TAIL</body></html>'
.text 和 .tail 这两个属性足以表示 XML 文档中的任何文本内容。这样,除了 Element 类之外,ElementTree API 不需要任何特殊的文本节点,这些节点往往会经常造成阻碍(正如您可能从经典的 DOM API 中知道的那样)。
译注:下面介绍了tostring()方法提供的便利功能。
但是,在某些情况下,尾部(tail)文本也会妨碍您。例如,当您从树的中间(译注:树的中间节点,也就是序列化树的一个子树
)序列化 Element 时,您并不总是希望其尾部文本出现在结果中(尽管您仍然需要其孩子节点的尾部文本)。为此,tostring()函数接受关键字参数with_tail:
>>> etree.tostring(br)
b'<br/>TAIL'
>>> etree.tostring(br, with_tail=False) # lxml.etree only!
b'<br/>'
如果只想读取文本,即不带任何中间标记,则必须以正确的顺序递归连接所有文本和尾部属性。同样,tostring() 函数可以帮上忙,这次使用 method 关键字:
>>> etree.tostring(html, method="text")
b'TEXTTAIL'
2. 使用Xpath来查找文本
提取树的文本内容的另一种方法是 XPath,它还允许您将单独的文本块(text chunks)提取到列表中:
>>> print(html.xpath("string()")) # lxml.etree only!
TEXTTAIL
>>> print(html.xpath("//text()")) # lxml.etree only!
['TEXT', 'TAIL']
如果要频繁地使用它,可以将其包装在函数中:
>>> build_text_list = etree.XPath("//text()") # lxml.etree only!
>>> print(build_text_list(html))
['TEXT', 'TAIL']
请注意,XPath 返回的字符串结果是一个特殊的"智能"对象,它知道其来源(origins)。你可以通过它的getparent方法询问它来自哪里,就像使用Elements对象一样:
>>> texts = build_text_list(html)
>>> print(texts[0])
TEXT
>>> parent = texts[0].getparent()
>>> print(parent.tag)
body
>>> print(texts[1])
TAIL
>>> print(texts[1].getparent().tag)
br
您还可以找出它是正常的文本内容还是尾部文本:
>>> print(texts[0].is_text)
True
>>> print(texts[1].is_text)
False
>>> print(texts[1].is_tail)
True
虽然这适用于 text()函数(译注:这个text()函数,表示xpath表达式最后的text(),例如/root/text()
)的结果,但 lxml 不会告诉您由 XPath 函数 string()或 concat() 构造的字符串值的来源:
>>> stringify = etree.XPath("string()")
>>> print(stringify(html))
TEXTTAIL
>>> print(stringify(html).getparent())
None