在 lxml 中查找具有未知命名空间的元素

Find element that has unknown namespace in lxml

我有一个 XML 有很多级别。每个级别都可以附加名称空间。我想 find 一个我知道其名称但不知道其名称空间的特定元素。例如:

my_file.xml

<?xml version="1.0" encoding="UTF-8"?>
<data xmlns="aaa:bbb:ccc:ddd:eee">
  <country name="Liechtenstein" xmlns="aaa:bbb:ccc:liechtenstein:eee">
    <rank updated="yes">2</rank>
    <year>2008</year>
    <gdppc>141100</gdppc>
    <neighbor name="Austria" direction="E"/>
    <neighbor name="Switzerland" direction="W"/>
  </country>
  <country name="Singapore" xmlns="aaa:bbb:ccc:singapore:eee">
    <continent>Asia</continent>
    <holidays>
      <christmas>Yes</christmas>
    </holidays>
    <rank updated="yes">5</rank>
    <year>2011</year>
    <gdppc>59900</gdppc>
    <neighbor name="Malaysia" direction="N"/>
  </country>
  <country name="Panama" xmlns="aaa:bbb:ccc:panama:eee">
    <rank updated="yes">69</rank>
    <year>2011</year>
    <gdppc>13600</gdppc>
    <neighbor name="Costa Rica" direction="W"/>
    <neighbor name="Colombia" direction="E"/>
  </country>
</data>
import lxml.etree as etree

tree = etree.parse('my_file.xml')
root = tree.getroot()

cntry_node = root.find('.//country')

上面的 findcntry_node 没有 return 任何东西。在我的真实数据中,层次比这个例子更深。 lxml 文档讨论了名称空间。当我这样做时:

root.nsmap

我看到了这个:

{None: 'aaa:bbb:ccc:ddd:eee'}

如果有人可以解释如何访问完整的 nsmap and/or 如何将其用于 find 特定元素?非常感谢。

您可以声明所有名称空间,但考虑到示例的结构 xml,我认为您最好完全忽略名称空间并只使用 local-name();所以

cntry_node = root.xpath('.//*[local-name()="country"]')
cntry_node

returns

[<Element {aaa:bbb:ccc:liechtenstein:eee}country at 0x1cddf1d4680>,
 <Element {aaa:bbb:ccc:singapore:eee}country at 0x1cddf1d47c0>,
 <Element {aaa:bbb:ccc:panama:eee}country at 0x1cddf1d45c0>]

另一种选择是使用 {*} 作为命名空间通配符...

cntry_node = root.find('.//{*}country')

注意:这仅适用于 find()findall()iter() 等;不是 xpath().

See here了解更多详情。

nsmap 不是 XML 文档的所有名称空间的全局集合

我相信您的印象是 nsmap 所有 名称空间的集合,这些名称空间出现在 XML 文档中。并且该集合在解析文档后可用。事实并非如此。

nsmap 只允许您访问一个元素的命名空间定义。所以这个:

root = tree.getroot()
root.nsmap

为您提供在 root 元素的上下文中已知的命名空间定义。请记住,“root”只是一个 Python 变量的名称,实际上包含 XML 文档的最外层元素(我知道这一点是因为你调用了 getroot())。您文档的最外层元素是:

<data xmlns="aaa:bbb:ccc:ddd:eee">

所以它的 nsmap 应该包含

{None: 'aaa:bbb:ccc:ddd:eee'}

(nsmap 中有 None,因为这是一个没有命名空间前缀的默认命名空间,可以到达 None 所在的位置。)

XML 文档结构很糟糕

通常,处理命名空间的最佳方法是自己定义它们(而不是从输入文档中获取它们)。假设我们要查找以下元素:

<country name="Liechtenstein" xmlns="aaa:bbb:ccc:liechtenstein:eee">

country 元素位于 默认名称空间 中,名称空间 URI 为“aaa:bbb:ccc:liechtenstein:eee”。要用lxml查找,定义一个映射:

my_own_namespace_mapping = {'prefix': 'aaa:bbb:ccc:liechtenstein:eee'}

然后在检索节点时使用它:

root.xpath('.//prefix:country', namespaces=my_own_namespace_mapping)
[<Element {aaa:bbb:ccc:liechtenstein:eee}country at 0x7fea87f363f8>]

但是,对于您的输入文档,您似乎需要为每个 country 元素单独执行此操作,因为它们各自位于自己的默认命名空间中:

root.xpath('.//prefix:country', namespaces={'prefix': 'aaa:bbb:ccc:singapore:eee'})
[<Element {aaa:bbb:ccc:singapore:eee}country at 0x7fea879cfd40>]

等等。这是非常不切实际的,不是因为 lxml 或命名空间很复杂,而是因为有人设计这种 XML 格式很糟糕。


顺便说一句,一旦找到其中一个元素,您可以再次使用 nsmap 来测试我上面所说的是否正确:

root.xpath('.//prefix:country', namespaces={'prefix': 'aaa:bbb:ccc:liechtenstein:eee'})[0].nsmap
{None: 'aaa:bbb:ccc:liechtenstein:eee'}