在 Python XPath 查询中注册命名空间

问题描述

这是我拥有的 XML 文档:

<products xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <Product Id="1">
      <Product Id="1_1">
        <Attribute Name="Whatever"></Attribute>
      </Product>
      <Attributes xmlns="http://some/path/to/entity/def">
        <Attribute Name="Identifier">NumberOne</Attribute>
      </Attributes>
  </Product>
  <Product Id="2">
    <Attributes xmlns="http://some/path/to/entity/def">
      <Attribute Name="Identifier">NumberTwo</Attribute>
    </Attributes>
  </Product>
</products>

我正在尝试使用 XPath 通过其子 Attributes.Attribute[Name=Identifier] 值(例如 "NumberOne"获取产品。 所以在这种情况下,我的预期结果是:

<Product Id="1">
      <Product Id="1_1">
        <Attribute Name="Whatever"></Attribute>
      </Product>
      <Attributes xmlns="http://some/path/to/entity/def">
        <Attribute Name="Identifier">NumberOne</Attribute>
      </Attributes>
</Product>

基于 this 的解释,我尝试使用 lxml 库在 Python 中实现查询

found_products = xml_tree_from_string.xpath('//products//Product[c:Attributes[Attribute[@Name="Identifier" and text()="NumberOne"]]]',namespaces={"c": "http://some/path/to/entity/def"})

不幸的是,由于 Attributes 命名空间定义,这永远不会返回结果。

我错过了什么?

解决方法

我错过了什么?

您没有注意到 Attribute 也与 Attributes 位于同一命名空间中,因为默认命名空间声明是由后代 XML 元素继承的。

因此,只需在您的 XPath 中将 c: 添加到 Attribute,它应该可以作为 Jack 的答案的 you observed in your comment

,

您需要首先定义一个命名空间映射,为那些没有的命名空间声明一个前缀(就像这里的情况),然后应用 xpath:

from lxml import etree
prods ="""[your xml above]"""
ns = { (k if k else "xx"):(v) for k,v in doc.xpath('//namespace::*') } #create ns map
doc = etree.XML(prods)
for product in doc.xpath('//products//Product[.//xx:Attribute[@Name="Identifier"][text()="NumberOne"]]',namespaces=ns):
    print(etree.tostring(product).decode())

输出:

<Product xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" Id="1">
      <Product Id="1_1">
        <Attribute Name="Whatever"/>
      </Product>
      <Attributes xmlns="http://some/path/to/entity/def">
        <Attribute Name="Identifier">NumberOne</Attribute>
      </Attributes>
  </Product>

要取消命名空间属性,请将 for 循环更改为:

for product in doc.xpath('//products//Product[.//xx:Attribute[@Name="Identifier"][text()="NumberOne"]]',namespaces=ns):
    etree.cleanup_namespaces(doc) #note: the parameter is "doc",not "product"
    print(etree.tostring(product).decode())

输出:

<Product Id="1">
      <Product Id="1_1">
        <Attribute Name="Whatever"/>
      </Product>
      <Attributes xmlns="http://some/path/to/entity/def">
        <Attribute Name="Identifier">NumberOne</Attribute>
      </Attributes>
  </Product>