我们的项目从这种形式的上游XML获取:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="7.0.0.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
<appSettings>
<add key="foo" value="default">
...
</appSettings>
</configuration>
然后,它使用 ElementTree 读取/解析此 XML,然后对于与某个键 ("foo") 匹配的每个应用程序设置,它会写入一个它知道上游进程不知道的新值(在这种情况下,键 "foo" 应该具有值 "bar")。
使用过滤的 XML 的下游进程是,啊啊...脆弱。它希望完全按照上述形式接收 XML。
如果我在没有注册命名空间的情况下解析此 XML,那么 ElementTree 会在输入时像这样破坏我的树:
<configuration xmlns:ns0="urn:schemas-microsoft-com:asm.v1">
<runtime>
<ns0:assemblyBinding>
<ns0:dependentAssembly>
<ns0:assemblyIdentity culture="neutral" name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" />
<ns0:bindingRedirect newVersion="7.0.0.0" oldVersion="0.0.0.0-6.0.0.0" />
</ns0:dependentAssembly>
</ns0:assemblyBinding>
</runtime>
<appSettings>
<add key="foo" value="default">
...
</appSettings>
</configuration>
下游流程无法处理这个问题,因为它不够聪明,无法意识到,从语义上讲,这是一回事。因此,我决定将我知道上游进程将提供的命名空间注册为默认命名空间,以避免前缀随处可见,现在我得到这个:
<configuration xmlns="urn:schemas-microsoft-com:asm.v1">
<runtime>
<assemblyBinding>
<dependentAssembly>
<assemblyIdentity culture="neutral" name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" />
<bindingRedirect newVersion="7.0.0.0" oldVersion="0.0.0.0-6.0.0.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
<appSettings>
<add key="foo" value="default">
...
</appSettings>
</configuration>
我对XML了解不多,但这也是下游组件的呼声,在我看来,这并不意味着这个默认xmlns
现在适用于<configuration>
内所有包含的元素,而以前它只适用于<assemblyBinding>
元素?
无论如何,使用 ElementTree 来处理这个命名空间,以便我可以接收上游的 XML,设置foo
的值,然后将其传递给下游,而无需移动命名空间,并完全保持我找到它的样子?
我可以使用基于 lxml 的解决方案,它似乎可以处理这个问题,但是,lxml 依赖于 C,下游组件真的希望不必支持:纯 Python 解决方案更可取。
我可以将文档读取为 HTML,这将忽略命名空间属性,让作我想要的值,然后传递文档;但是,我还没有找到一个不会将所有元素名称大写的 Python 解析器,并且我的下游组件需要保留所有元素名称的大小写。
我可以诉诸字符串解析和正则表达式。我宁愿不编写自己的解析器。
到目前为止,我能找到的关于 ElementTree 中命名空间处理的唯一建议是"注册默认命名空间以避免前缀"方法,我认为这种方法是合适的,但 ElementTree 随后坚持在转储时将xmlns
声明移动到根节点。
我也可以聪明地建立一个字符串,分阶段以正确的顺序将树倾倒出来,将xmlns
声明放回"正确的节点"上,但这也让我感到非常脆弱。
有没有人设法克服这样的问题?
据我所知,更适合您需求的解决方案是使用 xml.etree.ElementTree
公开的功能编写纯 Python 自定义渲染。这是一种可能的解决方案:
from xml.etree import ElementTree as ET
from re import findall, sub
def render(root, buffer='', namespaces=None, level=0, indent_size=2, encoding='utf-8'):
buffer += f'<?xml version="1.0" encoding="{encoding}" ?>n' if not level else ''
root = root.getroot() if isinstance(root, ET.ElementTree) else root
_, namespaces = ET._namespaces(root) if not level else (None, namespaces)
for element in root.iter():
indent = ' ' * indent_size * level
tag = sub(r'({[^}]+}s*)*', '', element.tag)
buffer += f'{indent}<{tag}'
for ns in findall(r'{[^}]+}', element.tag):
ns_key = ns[1:-1]
if ns_key not in namespaces: continue
buffer += ' xmlns' + (f':{namespaces[ns_key]}' if namespaces[ns_key] != '' else '') + f'="{ns_key}"'
del namespaces[ns_key]
for k, v in element.attrib.items():
buffer += f' {k}="{v}"'
buffer += '>' + element.text.strip() if element.text else '>'
children = list(element)
for child in children:
sep = 'n' if buffer[-1] != 'n' else ''
buffer += sep + render(child, level=level+1, indent_size=indent_size, namespaces=namespaces)
buffer += f'{indent}</{tag}>n' if 0 != len(children) else f'</{tag}>n'
return buffer
通过向上述render
函数发出您给出的XML
数据,如下所示:
data=
'''<?xml version="1.0" encoding="utf-8"?>
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="7.0.0.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
<appSettings>
<add key="foo" value="default" />
</appSettings>
</configuration>'''
e = ET.fromstring(data)
ET.register_namespace('', "urn:schemas-microsoft-com:asm.v1")
r = ET.ElementTree(e)
您将获得以下结果XML
拥有您声明要查找的属性:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral"></assemblyIdentity>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="7.0.0.0"></bindingRedirect>
</dependentAssembly>
</assemblyBinding>
</runtime>
<appSettings>
<add key="foo" value="default"></add>
</appSettings>
</configuration>
我知道我来晚了。无论如何,希望这能帮助您和许多其他遇到相同问题的人,这是一个很好的解决方案。祝您编码愉快!