python - 为什么 ElementTree 吃/忽略命名空间(在属性值中)?
问题描述
我正在尝试读取 XMLElementTree
并将结果写回磁盘。我的长期目标是以这种方式美化 XML。但是,在我幼稚的方法中,ElementTree 吃掉了文档中的所有命名空间声明,我不明白为什么。这是一个例子
测试.xsd
<?xml version='1.0' encoding='UTF-8'?>
<xs:schema xmlns:xs='http://www.w3.org/2001/XMLSchema'
xmlns='sdformat/pose' targetNamespace='sdformat/pose'
xmlns:pose='sdformat/pose'
xmlns:types='http://sdformat.org/schemas/types.xsd'>
<xs:import namespace='sdformat/pose' schemaLocation='./pose.xsd'/>
<xs:element name='pose' type='poseType' />
<xs:simpleType name='string'><xs:restriction base='xs:string' /></xs:simpleType>
<xs:simpleType name='pose'><xs:restriction base='types:pose' /></xs:simpleType>
<xs:complexType name='poseType'>
<xs:simpleContent>
<xs:extension base="pose">
<xs:attribute name='relative_to' type='string' use='optional' default=''>
</xs:attribute>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
</xs:schema>
测试.py
from xml.etree import ElementTree
ElementTree.register_namespace("types", "http://sdformat.org/schemas/types.xsd")
ElementTree.register_namespace("pose", "sdformat/pose")
ElementTree.register_namespace("xs", "http://www.w3.org/2001/XMLSchema")
tree = ElementTree.parse("test.xsd")
tree.write("test_out.xsd")
产生test_out.xsd
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="sdformat/pose">
<xs:import namespace="sdformat/pose" schemaLocation="./pose.xsd" />
<xs:element name="pose" type="poseType" />
<xs:simpleType name="string"><xs:restriction base="xs:string" /></xs:simpleType>
<xs:simpleType name="pose"><xs:restriction base="types:pose" /></xs:simpleType>
<xs:complexType name="poseType">
<xs:simpleContent>
<xs:extension base="pose">
<xs:attribute name="relative_to" type="string" use="optional" default="">
</xs:attribute>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
</xs:schema>
请注意test_out.xsd如何缺少来自test.xsd的任何命名空间声明。我希望它们是相同的。我通过验证后者验证了后者是有效的 XML。它验证除了我选择的命名空间 URI 之外,我认为这无关紧要。
更新:
根据 mzji 的评论,我意识到这只发生在属性值上。考虑到这一点,我可以像这样手动添加命名空间:
from xml.etree import ElementTree
namespaces = {
"types": "http://sdformat.org/schemas/types.xsd",
"pose": "sdformat/pose",
"xs": "http://www.w3.org/2001/XMLSchema"
}
for prefix, ns in namespaces.items():
ElementTree.register_namespace(prefix, ns)
tree = ElementTree.parse("test.xsd")
root = tree.getroot()
queue = [tree.getroot()]
while queue:
element:ElementTree.Element = queue.pop()
for value in element.attrib.values():
try:
prefix, value = value.split(":")
except ValueError:
# no namespace, nothing to do
pass
else:
if prefix == "xs":
break # ignore XMLSchema namespace
root.attrib[f"xmlns:{prefix}"] = namespaces[prefix]
for child in element:
queue.append(child)
tree.write("test_out.xsd")
虽然这解决了问题,但它是一个非常丑陋的解决方案。我仍然不明白为什么会发生这种情况,所以它没有回答这个问题。
解决方案
这种行为是有正当理由的,但它需要对 XML Schema 概念有很好的理解。
首先,一些重要的事实:
- 您的 XML 文档不仅仅是任何旧的 XML 文档。这是一个 XSD。
- XSD 由架构描述(请参阅架构的架构)
- 属性 xs:restriction/@base 不是 xs:string。它的类型是 xs:QName。
基于以上事实,我们可以断言:
- 如果 test.xsd 被解析为 XML 文档,但不知道“模式的模式”,则
base
属性的值将被视为字符串(技术上,作为 PCDATA)。 - 如果使用验证 XML 解析器解析 test.xsd,并将“schema for schema”作为 XSD,则
base
属性的值将被解析为 xs:QName
当 ElementTree 写入输出 XML 时,其行为应取决于base
. 如果base
是 QName,那么 ElementTree应该检测到它正在使用命名空间前缀“types”,并且它应该发出相应的命名空间声明。
如果您在解析 test.xsd 时没有提供“模式的模式”,那么 ElementTree 就可以摆脱困境,因为它不可能知道base
应该被解释为 QName。
推荐阅读
- sql - Oracle:在运算符中获取空值?
- c++ - Visual Studio 16.7.0 2019 Release x64 Win10 Pro 2004 中的错误 LNK2005 和错误 LNK1169 多重定义
- android - Firebase 中没有服务器端编码的应用内通知
- python - IronPython“语法无效”
- java - 对同一 JMenu 内的 JMenuItem 组进行排序,而不对其他 JMenuItem 进行排序
- azure-machine-learning-service - 无法导入名称“RollingOriginValidator”
- angular - 如何调用模拟方法而不被间谍检测到?
- c - SEGFAULT 出现在 DevC++ 中,但不出现在其他编译器中
- python - 为社会设计可维护/可编辑的网站
- oracle - PLS-00123:程序太大(戴安娜节点)