如何在不移动命名空间的情况下使用Python的ElementTree解析和编写XML?

时间:2016-07-18 14:10:48

标签: python xml namespaces nunit elementtree

我们的项目来自这种形式的上游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,然后对于与某个键匹配的每个应用程序设置(&#34; foo&#34;),它会写一个 it 知道的新值上游流程没有(在这种情况下,密钥&#34; foo&#34;应该具有值&#34; bar&#34;)。

使用过滤后的XML的下游进程是aaahhhh ... fragile 。它希望在完全上面的表格中接收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中找到关于命名空间处理的唯一建议是建议&#34;注册一个默认命名空间以避免前缀&#34;方法,我认为是合适的,但ElementTree然后坚持在转储时将xmlns声明移动到根节点。

我也可以聪明地建立一个字符串,将树分阶段转储出来,并按照正确的顺序将xmlns声明放回&#34;右节点&#34;,但是那个罢工我也很脆弱。

有没有人设法解决这样的问题?

1 个答案:

答案 0 :(得分:0)

据我所知,最适合您需求的解决方案是使用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

通过向上述XML函数发布您的render日期,如下所示:

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具有您要查找的属性:

<?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>

我知道我参加晚会很晚。无论如何希望这对您和其他遇到相同问题的人有所帮助,这是一个解决方案。编码愉快!