Lxml 元素带命名空间的相等性

8 投票
6 回答
6066 浏览
提问于 2025-04-16 14:46

我正在尝试使用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:

  1. 给我没有附加命名空间的标签字符串,这样我可以结合前缀属性来检查我正在处理的标签,或者
  2. 直接给我使用前缀的标签字符串

这样在写这个解析器的时候可以省下很多输入的时间。这可能吗?我是不是在文档中漏掉了什么?

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

撰写回答