Python ElementTree模块:在使用方法“时,如何忽略XML文件的名称空间来定位匹配的元素”;查找“&引用;findall“;

Python ElementTree模块:在使用方法“时,如何忽略XML文件的名称空间来定位匹配的元素”;查找“&引用;findall“;,python,namespaces,find,elementtree,findall,Python,Namespaces,Find,Elementtree,Findall,我想使用“findall”方法在ElementTree模块中定位源xml文件的一些元素 但是,源xml文件(test.xml)具有名称空间。我截短部分xml文件作为示例: <?xml version="1.0" encoding="iso-8859-1"?> <XML_HEADER xmlns="http://www.test.com"> <TYPE>Updates</TYPE> <DATE>9/26/2012 10:3

我想使用“findall”方法在ElementTree模块中定位源xml文件的一些元素

但是,源xml文件(test.xml)具有名称空间。我截短部分xml文件作为示例:

<?xml version="1.0" encoding="iso-8859-1"?>
<XML_HEADER xmlns="http://www.test.com">
    <TYPE>Updates</TYPE>
    <DATE>9/26/2012 10:30:34 AM</DATE>
    <COPYRIGHT_NOTICE>All Rights Reserved.</COPYRIGHT_NOTICE>
    <LICENSE>newlicense.htm</LICENSE>
    <DEAL_LEVEL>
        <PAID_OFF>N</PAID_OFF>
        </DEAL_LEVEL>
</XML_HEADER>

更新
2012年9月26日上午10:30:34
版权所有。
newlicense.htm
N
示例python代码如下所示:

from xml.etree import ElementTree as ET
tree = ET.parse(r"test.xml")
el1 = tree.findall("DEAL_LEVEL/PAID_OFF") # Return None
el2 = tree.findall("{http://www.test.com}DEAL_LEVEL/{http://www.test.com}PAID_OFF") # Return <Element '{http://www.test.com}DEAL_LEVEL/PAID_OFF' at 0xb78b90>
从xml.etree导入ElementTree作为ET
tree=ET.parse(r“test.xml”)
el1=tree.findall(“交易级别/已付款”)#返回无
el2=树。findall(“{http://www.test.com}交易水平/{http://www.test.com}已付款(已付清)#回报
尽管它可以工作,因为有一个名称空间“{http://www.test.com},在每个标记前面添加名称空间非常不方便


在使用“find”、“findall”等方法时,如何忽略名称空间?

如果在解析xml之前从xml中删除xmlns属性,那么树中的每个标记前面就不会有名称空间

import re

xmlstring = re.sub(' xmlns="[^"]+"', '', xmlstring, count=1)

您也可以使用优雅的字符串格式构造:

ns='http://www.test.com'
el2 = tree.findall("{%s}DEAL_LEVEL/{%s}PAID_OFF" %(ns,ns))
或者,如果您确定“已付款”仅显示在树中的一个级别:

el2 = tree.findall(".//{%s}PAID_OFF" % ns)

到目前为止,答案明确地将名称空间值放在脚本中。对于更通用的解决方案,我宁愿从xml中提取名称空间:

import re
def get_namespace(element):
  m = re.match('\{.*\}', element.tag)
  return m.group(0) if m else ''
并在查找方法中使用它:

namespace = get_namespace(tree.getroot())
print tree.find('./{0}parent/{0}version'.format(namespace)).text

与其修改XML文档本身,不如解析它,然后修改结果中的标记。通过这种方式,您可以处理多个名称空间和名称空间别名:

from io import StringIO  # for Python 2 import from StringIO instead
import xml.etree.ElementTree as ET

# instead of ET.fromstring(xml)
it = ET.iterparse(StringIO(xml))
for _, el in it:
    prefix, has_namespace, postfix = el.tag.partition('}')
    if has_namespace:
        el.tag = postfix  # strip all namespaces
root = it.root
这是基于这里的讨论:

更新:
rpartition
而不是
partition
确保在
postfix
中获得标记名,即使没有名称空间。这样你就可以把它浓缩起来:

for _, el in it:
    _, _, el.tag = el.tag.rpartition('}') # strip ns

下面是对nonagon答案的扩展,它还将名称空间从属性中剥离:

import io
import xml.etree.ElementTree as ET

# instead of ET.fromstring(xml)
it = ET.iterparse(io.StringIO(xml))
for _, el in it:
    if '}' in el.tag:
        el.tag = el.tag.split('}', 1)[1]  # strip all namespaces
    for at in list(el.attrib.keys()): # strip namespaces of attributes too
        if '}' in at:
            newat = at.split('}', 1)[1]
            el.attrib[newat] = el.attrib[at]
            del el.attrib[at]
root = it.root
更新:添加了
list()
,因此迭代器可以工作(Python3需要)


另一个更新:将StringIO的导入更改为io的导入(不确定此操作何时中断)

如果您使用的是
ElementTree
而不是
cElementTree
,则可以通过替换
ParserCreate()
强制Expat忽略命名空间处理:


ElementTree
试图通过调用
ParserCreate()
来使用Expat,但没有提供不提供名称空间分隔符字符串的选项,上面的代码将导致其被忽略,但会被警告这可能会破坏其他内容。

改进ericspod的答案:

我们可以将其包装到支持with构造的对象中,而不是全局更改解析模式

from xml.parsers import expat

class DisableXmlNamespaces:
    def __enter__(self):
            self.oldcreate = expat.ParserCreate
            expat.ParserCreate = lambda encoding, sep: self.oldcreate(encoding, None)
    def __exit__(self, type, value, traceback):
            expat.ParserCreate = self.oldcreate
然后,可以按如下方式使用

import xml.etree.ElementTree as ET
with DisableXmlNamespaces():
     tree = ET.parse("test.xml")

这种方式的优点在于,它不会改变with块之外的无关代码的任何行为。在使用ericspod的版本(碰巧也使用了expat)之后,在不相关的库中出现了错误,我最终创建了此版本。

我可能会迟到,但我认为
re.sub
不是一个好的解决方案

但是,重写
xml.parsers.expat
不适用于Python 3.x版本

罪魁祸首是
xml/etree/ElementTree.py
参见源代码的底部

# Import the C accelerators
try:
    # Element is going to be shadowed by the C implementation. We need to keep
    # the Python version of it accessible for some "creative" by external code
    # (see tests)
    _Element_Py = Element

    # Element, SubElement, ParseError, TreeBuilder, XMLParser
    from _elementtree import *
except ImportError:
    pass
这有点悲哀

解决办法是先把它处理掉

import _elementtree
try:
    del _elementtree.XMLParser
except AttributeError:
    # in case deleted twice
    pass
else:
    from xml.parsers import expat  # NOQA: F811
    oldcreate = expat.ParserCreate
    expat.ParserCreate = lambda encoding, sep: oldcreate(encoding, None)
在Python3.6上测试

Try
Try
语句非常有用,以防在代码中的某个地方重新加载或导入模块两次时出现一些奇怪的错误,如

  • 超过最大递归深度
  • AttributeError:XMLParser
顺便说一句,etree源代码看起来非常混乱。

让我们结合以下内容:

使用此功能,我们可以:

  • 创建迭代器以获取名称空间和解析的树对象

  • 对创建的迭代器进行迭代,以获得我们可以使用的名称空间dict 稍后传入每个
    find()
    findall()
    调用

  • 返回已解析树的根元素对象和名称空间
  • 我认为这是最好的方法,因为不需要对源XML或解析后的
    XML.etree.ElementTree
    输出进行任何操作


    我还想感谢您提供了这个难题的一个重要部分(您可以从迭代器获得解析的根)。在此之前,我在应用程序中实际遍历了两次XML树(一次用于获取名称空间,第二次用于根)。

    在python 3.5中,可以在
    find()
    中将名称空间作为参数传递。 比如说,

    ns= {'xml_test':'http://www.test.com'}
    tree = ET.parse(r"test.xml")
    el1 = tree.findall("xml_test:DEAL_LEVEL/xml_test:PAID_OFF",ns)
    

    文档链接:-

    只是碰巧出现在这里的答案中:。这不是主题问题的确切答案,但如果名称空间不重要,则可能适用

    <?xml version="1.0" encoding="UTF-8"?>
    <persons xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:noNamespaceSchemaLocation="test.xsd">
        <person version="1">
            <firstname>toto</firstname>
            <lastname>tutu</lastname>
        </person>
    </persons>
    
    返回者

    >python test.py
    toto
    tutu
    

    Is
    tree.findall(“xmlns:DEAL\u LEVEL/xmlns:PAID\u OFF”,名称空间={'xmlns':'http://www.test.com“})
    够方便吗?非常感谢。我试试你的方法,它能奏效。它比我的方便,但还是有点别扭。您知道ElementTree模块中是否没有其他合适的方法来解决此问题,或者根本没有这样的方法?或者尝试
    tree.findall(“{0}DEAL_LEVEL/{0}PAID_OFF.”格式('{http://www.test.com}“)
    在Python 3.8中,可以对名称空间使用通配符。假设只有一个
    名称空间
    太多了,这没有考虑到嵌套标记可以使用不同的名称空间。这在许多情况下对我有效,但后来我遇到了多个名称空间和名称空间别名。有关处理这些情况的另一种方法,请参见我的答案。-1在解析之前通过正则表达式操纵xml是错误的。虽然它在某些情况下可能会起作用,但这不应该是最受欢迎的答案,也不应该在专业应用程序中使用
    <?xml version="1.0" encoding="UTF-8"?>
    <persons xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:noNamespaceSchemaLocation="test.xsd">
        <person version="1">
            <firstname>toto</firstname>
            <lastname>tutu</lastname>
        </person>
    </persons>
    
    from xml.etree import ElementTree as ET
    tree = ET.parse("test.xml")
    el1 = tree.findall("person/firstname")
    print(el1[0].text)
    el2 = tree.find("person/lastname")
    print(el2.text)
    
    >python test.py
    toto
    tutu