【问题标题】:Python: Update XML-file using ElementTree while conserving layout as much as possiblePython:使用 ElementTree 更新 XML 文件,同时尽可能保留布局
【发布时间】:2012-03-23 16:41:18
【问题描述】:

我有一个使用 XML 命名空间的文档,我想将其 /group/house/dogs 加一:(该文件名为 houses.xml

<?xml version="1.0"?>
<group xmlns="http://dogs.house.local">
    <house>
            <id>2821</id>
            <dogs>2</dogs>
    </house>
</group>

我当前使用以下代码的结果是:(创建的文件名为houses2.xml

<ns0:group xmlns:ns0="http://dogs.house.local">
    <ns0:house>
        <ns0:id>2821</ns0:id>
        <ns0:dogs>3</ns0:dogs>
    </ns0:house>
</ns0:group>

我想解决两件事(如果可以使用 ElementTree。如果不是,我会很高兴提出我应该改用什么的建议):

  1. 我想保留&lt;?xml version="1.0"?&gt; 这一行。
  2. 我不想为所有标签添加前缀,我想保持原样。

总之,我不想过多地弄乱文档。

生成上述结果的我当前的代码(除上述缺陷外都有效)如下。

我制作了一个实用函数,它使用 ElementTree 加载 XML 文件并返回 elementTree 和命名空间(因为我不想对命名空间进行硬编码,并且愿意承担它所暗示的风险):

def elementTreeRootAndNamespace(xml_file):
    from xml.etree import ElementTree
    import re
    element_tree = ElementTree.parse(xml_file)

    # Search for a namespace on the root tag
    namespace_search = re.search('^({\S+})', element_tree.getroot().tag)
    # Keep the namespace empty if none exists, if a namespace exists set
    # namespace to {namespacename}
    namespace = ''
    if namespace_search:
        namespace = namespace_search.group(1)

    return element_tree, namespace

这是我更新狗数量并将其保存到新文件houses2.xml的代码:

elementTree, namespace = elementTreeRootAndNamespace('houses.xml')

# Insert the namespace before each tag when when finding current number of dogs,
# as ElementTree requires the namespace to be prefixed within {...} when a
# namespace is used in the document.
dogs = elementTree.find('{ns}house/{ns}dogs'.format(ns = namespace))

# Increase the number of dogs by one
dogs.text = str(int(dogs.text) + 1)

# Write the result to the new file houses2.xml.
elementTree.write('houses2.xml')

【问题讨论】:

标签: python xml


【解决方案1】:

来自 lxml 的 etree 提供了这个功能。

  1. elementTree.write('houses2.xml',encoding = "UTF-8",xml_declaration = True) 帮助您不遗漏声明

  2. 写入文件时不会更改命名空间。

http://lxml.de/parsing.html 是其教程的链接。

P.S : lxml 应该单独安装。

【讨论】:

    【解决方案2】:

    当保存 xml 添加 default_namespace 参数很容易避免 ns0,在我的代码上

    关键代码:xmltree.write(xmlfile,"utf-8",default_namespace=xmlnamespace)

    if os.path.isfile(xmlfiile):
                xmltree = ET.parse(xmlfiile)
                root = xmltree.getroot()
                xmlnamespace = root.tag.split('{')[1].split('}')[0]  //get namespace
    
                initwin=xmltree.find("./{"+ xmlnamespace +"}test")
                initwin.find("./{"+ xmlnamespace +"}content").text = "aaa"
                xmltree.write(xmlfiile,"utf-8",default_namespace=xmlnamespace)
    

    【讨论】:

      【解决方案3】:

      一个基于 XML 的解决方案是为 ElementTree 编写一个帮助类:

      • 写作时在将 XML 声明行解析为 ElementTree 之前,如果不编写编码属性,则无法编写 XML 声明行(我检查了源代码)。
      • 解析输入文件一次,获取根元素的命名空间。使用 ElementTree 将该名称空间注册为具有空字符串作为前缀。完成后,将使用 ElementTree再次使用该新设置解析源文件。

      它有一个主要缺点:

      • XML-cmets 丢失了。我了解到的这种情况是不可接受的(我最初认为输入数据没有任何 cmets,但事实证明它有)。

      我的助手类与示例:

      from xml.etree import ElementTree as ET
      import re
      
      
      class ElementTreeHelper():
          def __init__(self, xml_file_name):
              xml_file = open(xml_file_name, "rb")
      
              self.__parse_xml_declaration(xml_file)
      
              self.element_tree = ET.parse(xml_file)
              xml_file.seek(0)
      
              root_tag_namespace = self.__root_tag_namespace(self.element_tree)
              self.namespace = None
              if root_tag_namespace is not None:
                  self.namespace = '{' + root_tag_namespace + '}'
                  # Register the root tag namespace as having an empty prefix, as
                  # this has to be done before parsing xml_file we re-parse.
                  ET.register_namespace('', root_tag_namespace)
                  self.element_tree = ET.parse(xml_file)
      
          def find(self, xpath_query):
              return self.element_tree.find(xpath_query)
      
          def write(self, xml_file_name):
              xml_file = open(xml_file_name, "wb")
              if self.xml_declaration_line is not None:
                  xml_file.write(self.xml_declaration_line + '\n')
      
              return self.element_tree.write(xml_file)
      
          def __parse_xml_declaration(self, xml_file):
              first_line = xml_file.readline().strip()
              if first_line.startswith('<?xml') and first_line.endswith('?>'):
                  self.xml_declaration_line = first_line
              else:
                  self.xml_declaration_line = None
              xml_file.seek(0)
      
          def __root_tag_namespace(self, element_tree):
              namespace_search = re.search('^{(\S+)}', element_tree.getroot().tag)
              if namespace_search is not None:
                  return namespace_search.group(1)
              else:
                  return None
      
      
      def __main():
          el_tree_hlp = ElementTreeHelper('houses.xml')
      
          dogs_tag = el_tree_hlp.element_tree.getroot().find(
                         '{ns}house/{ns}dogs'.format(
                               ns=el_tree_hlp.namespace))
          one_dog_added = int(dogs_tag.text.strip()) + 1
          dogs_tag.text = str(one_dog_added)
      
          el_tree_hlp.write('hejsan.xml')
      
      if __name__ == '__main__':
          __main()
      

      输出:

      <?xml version="1.0"?>
      <group xmlns="http://dogs.house.local">
          <house>
                  <id>2821</id>
                  <dogs>3</dogs>
          </house>
      </group>
      

      如果有人对此解决方案有改进,请不要犹豫,获取代码并改进它。

      【讨论】:

      • 我会等一个星期左右,以防其他人(或我)一起有改进或更好的解决方案。
      • 您是否尝试过预先添加带有空前缀的命名空间?或者你有很多命名空间,你不能提前说它会是哪一个?
      • 我有很多命名空间。我知道以同样的方式处理它们会带来风险,但对于我的数据,我认为它足够安全。这就是我解析文件一次,获取名称空间并再次解析的原因。我可以自己通过文件 io 解析命名空间,然后使用 thefile.seek(0) 并让 ElementTree 在文件上执行它的操作。但我想要相同的逻辑来解析命名空间部分。
      • 我在接受的答案中使用了 hacky 解决方案。我对此并不完全满意,但它必须这样做。
      • 下面的答案对我有帮助。阿贝尔唐
      【解决方案4】:

      不幸的是,往返并不是一个小问题。对于 XML,除非您使用特殊的解析器(例如 DecentXML,但这是针对 Java 的),否则通常无法保留原始文档。

      根据您的需要,您有以下选择:

      • 如果您控制源代码并且可以使用单元测试保护您的代码,您可以编写自己的简单解析器。此解析器不接受 XML,而只接受有限的子集。例如,您可以将整个文档作为字符串读取,然后使用 Python 的字符串操作来定位 &lt;dogs&gt; 并将任何内容替换为下一个 &lt;。黑客?是的。

      • 您可以过滤输出。 XML 只允许字符串&lt;ns0: 在一个位置,因此您可以使用&lt; 搜索和替换它,然后使用&lt;group xmlns:ns0="&lt;group xmlns=" 进行相同的搜索和替换。除非您可以在 XML 中包含 CDATA,否则这是非常安全的。

      • 您可以编写自己的简单 XML 解析器。将输入读取为字符串,然后为每对 &lt;&gt; 加上它们在输入中的位置创建元素。这使您可以快速拆分输入,但仅适用于少量输入。

      【讨论】:

      • 谢谢。是的,对于像 XML 这样简单的东西,它需要的时间比你想象的要长... ;-)
      • 因为我发现没有很好的基于 XML 的解析器能够做到这一点,所以我将此答案标记为正确。我将继续使用您在第一个列表项中提出的解决方案(它已经存在于我正在转换为 Python 的代码中)。为了得到我想要的东西,我或多或少必须编写一个自定义解析器,这对于我正在解决的任务来说会花费太长时间(尽管它会很有趣)。仅删除
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-12-04
      • 2011-05-27
      • 2012-02-17
      • 1970-01-01
      • 2012-06-20
      • 2013-04-02
      • 2019-03-23
      相关资源
      最近更新 更多