使用python检索具有未知命名空间的元素的内容

时间:2013-01-16 16:09:17

标签: python xpath

我正在尝试使用python解析maven项目定义来提取版本。

项目定义如下:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
                        http://maven.apache.org/maven-v4_0_0.xsd">
   <modelVersion>4.0.0</modelVersion>

   <groupId>...</groupId>
   <artifactId>...</artifactId>
   <version>1.6.0-SNAPSHOT</version>
   ...
</project>

我可以使用以下方法提取版本:

root = ET.fromstring(xml)
version = root.find('./p:version', { 'p': 'http://maven.apache.org/POM/4.0.0' })
print(version.text)

prints: 1.6.0-SNAPSHOT

但是,使用的命名空间可能会更改,我不想依赖于此。有没有办法提取在我后续的xpath表达式中使用的命名空间?

我尝试了以下内容,看看xmlns本身是否暴露,但没有运气:

root = ET.fromstring(xml)
for k in root.attrib:
    print('%s => %s' % (k, root.attrib[k]))

prints: {http://www.w3.org/2001/XMLSchema-instance}schemaLocation => http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd

2 个答案:

答案 0 :(得分:2)

不幸的是,ElementTree命名空间支持相当不完整。

您需要使用xml.etree.ElementTree模块中的内部方法来获取命名空间映射:

_, namespaces = ET._namespaces(root, 'utf8')

namespaces现在是一个dict,URI为键,前缀为值。

您可以改用lxml。该库实现了相同的ElementTree API,但却大大增强了该API。

例如,每个节点都包含.nsmap属性,该属性将前缀映射到URI,包括密钥None下的默认命名空间。

答案 1 :(得分:2)

  

但是,使用的命名空间可能会改变,我不想依赖它。

您是说名称空间uri可能会更改,还是前缀可能会更改?如果它只是前缀,那么这不是问题,因为重要的是XPath中的前缀与您提供给XPath求值程序的前缀相匹配。在任何一种情况下,自动检测命名空间可能都是一个糟糕的调用。假设有人决定开始生成这样的XML:

<proj:project xmlns:proj="http://maven.apache.org/POM/4.0.0" 
xmlns:other="http://maven.apache.org/POM/5.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
                    http://maven.apache.org/maven-v4_0_0.xsd">

仍然完全代表与您的示例相同的命名空间中的XML,但您不知道proj前缀是您正在寻找的命名空间前缀。

我认为Apache不太可能突然改变其官方XML格式之一的命名空间,但如果你真的担心它,应该总是选择使用local-name()来命名空间 - 无法找到一个你正在寻找的节点:

version = root.find('./*[local-name() = "version"]')

另外,我不熟悉elementTree库,但你可以尝试这个尝试获取有关XML文档命名空间的信息,只是为了看看你是否可以:

namespaces = root.findall('//namespace::*')
相关问题