Lxml 元素带命名空间的相等性
我正在尝试使用Lxml来解析.docx文档的内容。我知道lxml会把命名空间前缀替换成实际的命名空间,但这让检查我正在处理的元素标签变得非常麻烦。我希望能做一些像这样的事情:
if (someElement.tag == "w:p"):
但是由于lxml总是要在前面加上完整的命名空间,我要么得做一些像这样的事情:
if (someElemenet.tag == "{http://schemas.openxmlformats.org/wordprocessingml/2006/main}p'):
要么就得从元素的nsmap属性中查找完整的命名空间名,像这样:
targetTag = "{%s}p" % someElement.nsmap['w']
if (someElement.tag == targetTag):
如果有更简单的方法让我让lxml:
- 给我没有附加命名空间的标签字符串,这样我可以结合前缀属性来检查我正在处理的标签,或者
- 直接给我使用前缀的标签字符串
这样在写这个解析器的时候可以省下很多输入的时间。这可能吗?我是不是在文档中漏掉了什么?
6 个回答
5
etree.Qname
应该能帮你解决问题。
from lxml import etree
# [...]
tag = etree.QName(someElement)
print(tag.namespace, tag.localname)
对于你提到的标签,这段代码会输出:
http://schemas.openxmlformats.org/wordprocessingml/2006/main p
需要注意的是,QName
可以接受一个 Element
对象或者一个字符串(比如来自 Element.tag
的内容)。
而且,正如你所提到的,你也可以使用 Element.nsmap
来将任意前缀映射到一个命名空间。
所以像这样:
if tag.namespace == someElement.nsmap["w"] and tag.localname == "p":
5
我找不到获取元素中不带命名空间的标签名的方法——lxml会把完整的命名空间也算作标签名。这里有几个可能对你有帮助的选项。
你也可以使用 QName
类来构建一个带命名空间的标签,以便进行比较:
import lxml.etree
from lxml.etree import QName
tree = lxml.etree.fromstring('<root xmlns:f="foo"><f:test/></root>')
qn = QName(tree.nsmap['f'], 'test')
assert tree[0].tag == qn
如果你需要纯粹的标签名,你就得写一个工具函数来提取它:
def get_bare_tag(elem):
return elem.tag.rsplit('}', 1)[-1]
assert get_bare_tag(tree[0]) == 'test'
不幸的是,按照我所知,你不能使用 lxml 的 xpath / find 方法来搜索带有“任何命名空间”的标签(例如 {*}test
)。
更新: 请注意,lxml 不会构建仅包含 {
或 }
的标签——这样会引发 ValueError: invalid tag name,因此可以安全地假设,标签名以 {
开头的元素是平衡的。
lxml.etree.Element('{foo')
ValueError: Invalid tag name
22
也许可以使用 local-name() 这个函数:
import lxml.etree as ET
tree = ET.fromstring('<root xmlns:f="foo"><f:test/></root>')
elt=tree[0]
print(elt.xpath('local-name()'))
# test