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

6

我想选择特定元素(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个回答

1
但是我想要的是从父命名空间声明中获取没有命名空间声明的子元素 xmlns="default_ns"。
仅通过评估XPath表达式无法实现此目标。
在XML中,除非重新定义特定命名空间,否则任何元素都会继承其父级的所有命名空间节点。
这意味着some_prefix:a从其父级(subelement)继承了默认命名空间"default_ns",后者本身从顶级元素root继承了相同的默认命名空间节点。
XPath是用于XML文档的查询语言。因此,它只能帮助选择节点,但XPath表达式的评估永远不会破坏、添加或更改节点,包括命名空间节点。
由于这个原因,属于some_prefix:a的默认命名空间节点不能被销毁,因为它是您的XPath表达式的结果 - 因此,在将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>

谢谢Dimitre!实际上我想使用lxml实现结果。在看了你的回答后,我发现了相似的问题,并且这个回答中的第一个评论建议使用deepcopy和clean_namespaces来实现:child = deepcopy(child) etree.cleanup_namespaces(child) - Marcin
@Marcin 这两个命令是不同的吗?此外,我的解释器显示“'deepcopy'未定义”。 - NoBugs
@NoBugs 这是两个单独的命令。此外,我发现 etree.cleanup_namespaces 是不必要的。只需要使用 deepcopy 来删除不需要的命名空间即可。deepcopy 函数来自于 copy 模块。这是最终代码: child = evaluator.evaluate('//def:subelement/child::*')[0]
child = deepcopy(child) a_string = etree.tostring(child)
- Marcin

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接