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

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

但是,源 xml 文件(test.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}"可以工作,但是在每个标记前面添加名称空间是非常不方便的。

当使用像 findfindall、 ... 这样的函数时,我怎么能忽略名称空间呢?

106619 次浏览

如果在解析 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 ''

并在 find 方法中使用它:

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:
_, _, el.tag = el.tag.rpartition('}') # strip ns
root = it.root

这是基于 给你的讨论。

下面是@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 的永久性破坏,但是如果这是可以接受的,因为没有非唯一的标记名,并且因为您不需要编写需要原始名称空间的文件,那么这可以使访问它更加容易

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

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

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

改善 用 ericspod 回答:

我们不需要全局更改解析模式,而是将其封装在一个支持 with 结构的对象中。

from xml.parsers import expat


class DisableXmlNamespaces:
def __enter__(self):
self.old_parser_create = expat.ParserCreate
expat.ParserCreate = lambda encoding, sep: self.old_parser_create(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 的版本之后,我在不相关的库中发现了错误,最终创建了这个版本,而 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)

在 Python 3.6上测试。

如果在代码中的某个地方重新加载或导入一个模块两次,就会出现一些奇怪的错误,比如

  • 超过最大递归深度
  • AttributeError: XMLParser

顺便说一下,该死的树的源代码看起来真的很混乱。

让我们把 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. 在创建的迭代器上进行迭代,以获得我们可以使用的名称空间 dict 之后传入每个 find()findall()调用 < a href = “ https://stackoverflow. com/questions/13412496/python-elementtree-module-how-to-恨-the-nampace-of-xml-files-to-location-ma # comments 18328039 _ 13412496”> IMom0 .

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

我认为这是最好的方法,因为无论是源 XML 还是解析后的 xml.etree.ElementTree输出都不需要操作。

我还想赞扬 Balmy 的回答提供了这个难题的一个重要部分(您可以从迭代器获得解析后的根)。在此之前,我在应用程序中实际遍历了两次 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)

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

只是偶然掉进了这里的答案: 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