【问题标题】:Find element that has unknown namespace in lxml在 lxml 中查找具有未知名称空间的元素
【发布时间】:2021-05-11 18:42:41
【问题描述】:

我有一个包含多个级别的 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')

上面的find 不会向cntry_node 返回任何内容。在我的真实数据中,层次比这个例子更深。 lxml 文档讨论了命名空间。当我这样做时:

root.nsmap

我看到了:

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

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

【问题讨论】:

  • @MathiasMüller @mzjn 也许我还是不明白这里的命名空间概念。我假设在解析 XML 文件时,命名空间存储在 nsmap 中。因此,我们可以访问它。从这里的答案看来,我必须手动定义这个文件?对于大型 XML 文件来说,这需要大量工作。

标签: python xml lxml


【解决方案1】:

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

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

返回

[<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>]

【讨论】:

  • 不错的答案!不过,它并没有解决 OP 对 nsmap 的困惑。你可以加some commentsabout that
  • 再次感谢您的帮助!
  • @TristanTran 很高兴它有帮助!
【解决方案2】:

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

我相信您的印象是nsmap 是 XML 文档中存在的 all 命名空间的集合。并且该集合在解析文档后可用。事实并非如此。

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'}

【讨论】:

  • 谢谢。这清除了很多。我希望像这样的一些细节包含在lxml 文档中。
  • Muller 我刚刚尝试了一些东西并更新了我的问题。想知道你是否可以看看。谢谢。
  • @TristanTran 更新您的问题可能会令人困惑,因为您已经接受了一个可能不再适合的答案,并且该问题不再有明确的重点。请考虑提出一个新问题,而不是更新您的问题(您可以在此处链接到它)。谢谢!
【解决方案3】:

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

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

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

See here了解更多详情。

【讨论】:

  • 谢谢。这很有帮助!
猜你喜欢
  • 2016-09-17
  • 1970-01-01
  • 1970-01-01
  • 2014-02-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-07-03
相关资源
最近更新 更多