lxml XPath中不需要的命名空间声明

时间:2012-02-28 21:33:53

标签: python xpath lxml

我想选择特定元素的第一个子元素(subelement),但是这个子元素的命名空间与父元素的命名空间不同。而且,这个孩子可以是任何命名空间。

xml = '''<root xmlns="default_ns">
    <subelement>
        <!-- here we can have an element of any namespace  -->
        <some_prefix:a xmlns:some_prefix="some_namespace">
            <some_prefix:b/>
        </some_prefix:a>
    </subelement>
</root>'''
root = etree.fromstring(xml)
evaluator = etree.XPathEvaluator(root, namespaces={'def':'default_ns'})
child = evaluator.evaluate('//def:subelement/child::*')[0]
a_string = etree.tostring(child)
print a_string

这给出了:

<some_prefix:a xmlns:some_prefix="some_namespace" xmlns="default_ns">
    <some_prefix:b/>
</some_prefix:a>

但我想得的是没有来自父xmlns="default_ns"的名称空间声明的孩子:

<some_prefix:a xmlns:some_prefix="some_namespace">
    <some_prefix:b/>
</some_prefix:a>

2 个答案:

答案 0 :(得分:1)

  

但我想得到的是没有名称空间声明的孩子   parent xmlns =“default_ns”。

仅通过评估XPath表达式无法实现。

在XML中,任何元素都会继承其所有父级的命名空间节点,除非它重新定义了特定的命名空间。

这意味着some_prefix:a从其父("default_ns")继承默认命名空间subelement ,它本身从顶部元素继承了相同的默认命名空间节点root

XPath是XML文档的查询语言。因此,它只能帮助选择节点,但 XPath表达式的评估永远不会破坏,添加或更改节点,包括命名空间节点

因此,作为评估XPath表达式的结果,不能销毁属于some_prefix:a的默认命名空间节点 - 因此,当some_prefix:a被序列化为文本时,将显示此命名空间节点。

解决方案:使用您最喜欢的承载XPath的PL来删除不需要的命名空间节点。

例如,如果托管语言是XSLT

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:d="default_ns">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

 <xsl:template match="/">
  <xsl:apply-templates mode="delNS"
    select="/*/d:subelement/*[1]"/>
 </xsl:template>

 <xsl:template match="*" mode="delNS">
   <xsl:element name="{name()}" namespace="{namespace-uri()}">
    <xsl:copy-of select="namespace::*[name()]"/>
    <xsl:copy-of select="@*"/>
    <xsl:apply-templates mode="delNS" select="node()"/>
   </xsl:element>
 </xsl:template>
</xsl:stylesheet>

将此转换应用于提供的XML文档

<root xmlns="default_ns">
    <subelement>
        <!-- here we can have an element of any namespace  -->
        <some_prefix:a xmlns:some_prefix="some_namespace">
            <some_prefix:b/>
        </some_prefix:a>
    </subelement>
</root>

产生了想要的正确结果

<some_prefix:a xmlns:some_prefix="some_namespace">
   <some_prefix:b/>
</some_prefix:a>

答案 1 :(得分:0)

Dimitre 完全解释了为什么名称空间被继承以及如何使用XSLT去掉名称空间。

我使用了来自copy deepcopy 来删除不需要的命名空间。

这是我使用Python的最终解决方案:

from lxml import etree
from copy import deepcopy

xml = '''<root xmlns="default_ns">
    <subelement>
        <!-- here we can have an element of any namespace  -->
        <some_prefix:a xmlns:some_prefix="some_namespace">
            <some_prefix:b/>
        </some_prefix:a>
    </subelement>
</root>'''
root = etree.fromstring(xml)
evaluator = etree.XPathEvaluator(root, namespaces={'def':'default_ns'})
child = evaluator.evaluate('//def:subelement/child::*')[0]
child = deepcopy(child)
a_string = etree.tostring(child)
print a_string