python - 在 Python 中使用 XML namespace 时,如何使我的代码更具可读性和 DRYer?

标签 python xml lxml xml-namespaces elementtree

Python 的内置 xml.etree 包支持解析带有命名空间的 XML 文件,但命名空间前缀会扩展为括号中的完整 URI。所以在官方文档中的示例文件中:

<actors xmlns:fictional="http://characters.example.com"
    xmlns="http://people.example.com">
    <actor>
        <name>John Cleese</name>
        <fictional:character>Lancelot</fictional:character>
        <fictional:character>Archie Leach</fictional:character>
    </actor>
    ...

actor 标签扩展为 {http://people.example.com}actorfictional:character {http://characters.example.com}字符

我可以看到这如何使一切都非常明确并减少歧义(文件可以具有相同的 namespace 但具有不同的前缀等),但使用起来非常麻烦。 Element.find() 方法和其他方法允许将 dict 映射前缀传递给命名空间 URI,因此我仍然可以执行 element.find('fictional:character', nsmap) 但据我所知,标签属性没有任何相似之处。这会导致像 element.attrib['{{{}}}attrname'.format(nsmap['prefix'])] 这样烦人的东西。

流行的 lxml 包提供了相同的 API 和一些扩展,其中之一是它们从文档继承的元素的 nsmap 属性。然而,似乎没有一种方法真正使用它,所以我仍然必须执行 element.find('fictional:character', element.nsmap) 每次都不必要地重复输入.它也仍然不适用于属性。

幸运的是 lxml 支持子类化 BaseElement,所以我只是用 p(前缀)属性创建了一个,它具有相同的 API 但自动使用使用元素的 nsmap 的 namespace 前缀(编辑: 可能最好分配代码中定义的自定义 nsmap)。所以我只做 element.p.find('fictional:character')element.p.attrib['prefix:attrname'],这样重复性要低得多,而且我想办法更具可读性。

我只是觉得我真的遗漏了一些东西,如果不是内置的 etree 包的话,这真的应该已经是 lxml 的一个特性了。我是不是做错了?

最佳答案

是否有可能去掉命名空间映射?

是否需要将其作为参数传递到每个函数调用中?一个选项是在属性中设置要在 XML 文档中使用的前缀。

这很好,直到您将 XML 文档传递给第 3 方函数。该函数也想使用前缀,因此它将属性设置为其他内容,因为它不知道您将其设置为什么。

一旦您取回 XML 文档,它就被修改了,因此您的前缀不再起作用。

总而言之:不,它不安全,因此它很好。

这种设计不仅存在于 Python 中,它也存在于 .NET 中。 SelectNodes() [MSDN]如果不需要前缀,可以使用。但是只要存在前缀,它就会抛出异常。因此,您必须使用 the overloaded SelectNodes() [MSDN]它使用 XmlNamespaceManager 作为参数。

XPath 作为解决方案

我建议学习XPath (lxml specific link) ,您可以在其中使用前缀。由于这可能是特定于版本的,假设我使用 Python 2.7 x64 和 lxml 3.6.0 运行了这段代码(我对 Python 不太熟悉,所以这可能不是最干净的代码,但它可以很好地作为演示) :

from lxml import etree as ET
from pprint import pprint
data = """<?xml version="1.0"?>
<d:data xmlns:d="dns">
    <country name="Liechtenstein">
        <rank>1</rank>
        <year>2008</year>
        <gdppc>141100</gdppc>
        <neighbor d:name="Austria" direction="E"/>
        <neighbor name="Switzerland" direction="W"/>
    </country>
    <country name="Singapore">
        <rank>4</rank>
        <year>2011</year>
        <gdppc>59900</gdppc>
        <neighbor name="Malaysia" direction="N"/>
    </country>
</d:data>"""
root = ET.fromstring(data)
my_namespaces = {'x':'dns'}
xp=root.xpath("/x:data/country/neighbor/@x:name", namespaces=my_namespaces)
pprint(xp)
xp=root.xpath("//@x:name", namespaces=my_namespaces)
pprint(xp)
xp=root.xpath("/x:data/country/neighbor/@name", namespaces=my_namespaces)
pprint(xp)

输出是

C:\Python27x64\python.exe E:/xpath.py
['Austria']
['Austria']
['Switzerland', 'Malaysia']

Process finished with exit code 0

注意 XPath 如何很好地解决了从命名空间表中的 x 前缀到 XML 文档中的 d 前缀的映射。

这消除了真正可怕的阅读 element.attrib['{{{}}}attrname'.format(nsmap['prefix'])]

简短(不完整)的 XPath 介绍

要选择一个元素,请编写 /element,可选择使用前缀。

xp=root.xpath("/x:data", namespaces=my_namespaces)

要选择属性,请编写 /@attribute,可选择使用前缀。

#See example above

要向下导航,请连接多个元素。如果您不知道中间的项目,请使用 //。要向上移动,请使用 /..。如果后面没有 /..,则属性必须放在最后。

xp=root.xpath("/x:data/country/neighbor/@x:name/..", namespaces=my_namespaces)

要使用条件,请将其写在方括号中。 /element[@attribute] 表示:选择所有具有该属性的元素。 /element[@attribute='value'] 表示:选择所有具有该属性且该属性具有特定值的元素。 /element[./subelement] 表示:选择具有特定名称的子元素的所有元素。可选择在任何地方使用前缀。

xp=root.xpath("/x:data/country[./neighbor[@name='Switzerland']]/@name", namespaces=my_namespaces)

还有很多东西有待发现,例如 text()、各种兄弟选择方式甚至函数。

关于“为什么”

原来的问题标题是

Why does working with XML namespaces seem so difficult in Python?

对于一些用户来说,他们只是不理解这个概念。如果用户理解这个概念,开发者可能不理解。也许这只是众多选择中的一个,而决定就是朝这个方向发展。在这种情况下,唯一能够回答“为什么”部分的人就是开发人员本人。

引用资料

关于python - 在 Python 中使用 XML namespace 时,如何使我的代码更具可读性和 DRYer?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37263363/

相关文章:

python - 如何使用python转置csv文件中的数据

jquery - 如何构建允许同时上传多个文件的网络文件 uploader ?

ruby-on-rails - Rails 模型的 XML 映射和验证

python - 使用 MinGW 在 Windows 上构建 lxml

python - 在 Python 3 中使用 Open Arbitrary 标签解析 SGML

python - 创建太长整数的数据帧

java - java中没有根元素的子元素

Android:自定义微调器大小问题

python - 如何控制 lxml xpath text() 函数中的换行处理?

python - Geoviews:与英国等值区域图重叠的 map 图 block 无法完全对齐