Python ElementTree模块:当使用“find”,“findall”方法时,如何忽略XML文件的命名空间以找到匹配的元素

时间:2012-11-16 07:53:17

标签: 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: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>

示例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>

虽然它可以工作,但因为有一个命名空间“{http://www.test.com}”,所以在每个标记前添加命名空间非常不方便。

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

11 个答案:

答案 0 :(得分:46)

最好解析它,然后修改结果中的标记,而不是修改XML文档本身。这样您就可以处理多个名称空间和名称空间别名:

from StringIO import StringIO
import xml.etree.ElementTree as ET

# instead of ET.fromstring(xml)
it = ET.iterparse(StringIO(xml))
for _, el in it:
    if '}' in el.tag:
        el.tag = el.tag.split('}', 1)[1]  # strip all namespaces
root = it.root

这是基于这里的讨论: http://bugs.python.org/issue18304

答案 1 :(得分:43)

如果在解析之前从xml中删除了xmlns属性,那么树中的每个标记都不会添加名称空间。

import re

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

答案 2 :(得分:19)

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

import re
def get_namespace(element):
  m = re.match('\{.*\}', element.tag)
  return m.group(0) if m else ''

在find方法中使用它:

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

答案 3 :(得分:12)

这是对nonagon的答案的扩展,它还剥离了属性名称空间:

from StringIO import StringIO
import xml.etree.ElementTree as ET

# instead of ET.fromstring(xml)
it = ET.iterparse(StringIO(xml))
for _, el in it:
    if '}' in el.tag:
        el.tag = el.tag.split('}', 1)[1]  # strip all namespaces
    for at in 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

答案 4 :(得分:6)

改善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)后,在无关库中出现错误后,我最终创建了该代码。

答案 5 :(得分:3)

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

ns='http://www.test.com'
el2 = tree.findall("{%s}DEAL_LEVEL/{%s}PAID_OFF" %(ns,ns))

或者,如果您确定 PAID_OFF 仅出现在树的一个级别中:

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

答案 6 :(得分:2)

在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)

文档链接:-https://docs.python.org/3.5/library/xml.etree.elementtree.html#parsing-xml-with-namespaces

答案 7 :(得分:1)

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

from xml.parsers import expat
oldcreate = expat.ParserCreate
expat.ParserCreate = lambda encoding, sep: oldcreate(encoding, None)

ElementTree尝试通过调用ParserCreate()来使用Expat,但没有提供不提供命名空间分隔符字符串的选项,上面的代码将导致它被忽略但是会被警告这可能会破坏其他事情。 / p>

答案 8 :(得分:1)

让我们将nonagon's answermzjn's answer to a related question结合起来:

def parse_xml(xml_path: Path) -> Tuple[ET.Element, Dict[str, str]]:
    xml_iter = ET.iterparse(xml_path, events=["start-ns"])
    xml_namespaces = dict(prefix_namespace_pair for _, prefix_namespace_pair in xml_iter)
    return xml_iter.root, xml_namespaces

使用此功能,我们:

  1. 创建一个迭代器以同时获取名称空间和已解析的树对象

  2. 遍历已创建的迭代器,以获取我们可以使用的命名空间命令     以后再传递每个as sugested by iMom0find()findall()呼叫。

  3. 返回已解析树的根元素对象和名称空间。

我认为这是最好的方法,因为无论源XML还是解析后的xml.etree.ElementTree输出都不会受到任何操纵。

我也想感谢barny's answer提供了这个难题的重要组成部分(您可以从迭代器中获得解析的根)。直到我实际上在我的应用程序中遍历了两次XML树(一次获取名称空间,第二次获取根)。

答案 9 :(得分:0)

为此,我可能会迟到,但是我认为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)

在Python 3.6上进行了测试。

尝试try语句很有用,以防您在代码中的某处重新加载或导入模块两次而遇到类似

的奇怪错误
  • 超过了最大递归深度
  • AttributeError:XMLParser

该死的etree源代码看起来真的很乱。

答案 10 :(得分:0)

碰巧在这里找到答案:XSD conditional type assignment default type confusion?

<?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>

另请参阅:https://www.w3.org/TR/xmlschema-1/#xsi_schemaLocation

为我工作。我在应用程序中调用XML验证过程。但是我也想在编辑XML时快速查看PyCharm中的验证高亮和自动完成。这个noNamespaceSchemaLocation属性可以满足我的需求。