162

我想使用“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”等方法时如何忽略命名空间?

4

11 回答 11

71

最好先对其进行解析,然后再修改结果中的标签,而不是修改 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

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

更新: rpartition而不是确保即使没有命名空间也partition能获得标签名称。postfix因此你可以浓缩它:

for _, el in it:
    _, _, el.tag = el.tag.rpartition('}') # strip ns
于 2014-09-18T19:37:36.667 回答
46

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

import re

xmlstring = re.sub(' xmlns="[^"]+"', '', xmlstring, count=1)
于 2013-03-26T15:44:24.643 回答
20

到目前为止的答案明确地将命名空间值放在脚本中。对于更通用的解决方案,我宁愿从 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
于 2013-11-20T19:07:52.140 回答
15

这是@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

显然这是对 XML 的永久破坏,但如果这是可以接受的,因为没有非唯一的标记名称,并且因为您不会编写需要原始名称空间的文件,那么这可以使访问它更容易

于 2015-11-30T11:21:06.370 回答
14

改进 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。

于 2018-12-12T07:52:21.660 回答
6

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

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)
于 2013-10-08T10:18:17.417 回答
5

在 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

于 2020-09-04T16:59:19.703 回答
3

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

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

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

于 2018-01-19T15:56:47.670 回答
3

我可能会迟到,但我认为这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 上测试。

Trytry语句很有用,以防您在代码中的某个地方重新加载或导入模块两次,您会遇到一些奇怪的错误,例如

  • 超出最大递归深度
  • 属性错误:XMLParser

顺便说一句,该死的 etree 源代码看起来真的很乱。

于 2019-03-20T13:11:31.037 回答
1

让我们将nonagon 的回答mzjn 对相关问题的回答结合起来:

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. 遍历创建的迭代器以获取命名空间字典,我们稍后可以传入每个字典find()按照 iMom0 的建议findall()调用。

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

我认为这是最好的方法,因为不涉及任何源 XML 或生成的解析xml.etree.ElementTree输出的操作。

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

于 2019-08-13T09:00:10.420 回答
-2

只是偶然地在这里找到了答案:XSD 条件类型分配默认类型混淆?. 这不是主题问题的确切答案,但如果名称空间不重要,则可能适用。

<?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属性可以满足我的需要。

重新检查

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
于 2020-10-14T18:34:35.920 回答