Python:使用ElementTree更新XML文件,同时尽可能地保留布局

时间:2012-03-06 07:43:22

标签: python xml

我有一个使用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. 我想保留<?xml version="1.0"?>行。
  2. 我不想为所有标签添加前缀,我希望保持原样。
  3. 总之,我不想把文件弄得比我绝对要多。

    我当前的代码(除了上面提到的缺陷之外起作用)产生了上述结果。

    我已经创建了一个实用程序函数,它使用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')
    

4 个答案:

答案 0 :(得分:3)

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

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

它有一个主要缺点:

  • XML评论丢失。我所学到的是这种情况不可接受的(我最初认为输入数据没有任何评论,但事实证明它已经存在)。

我的帮助类,例如:

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>

如果有人对此解决方案有所改进,请不要犹豫,抓住代码并进行改进。

答案 1 :(得分:2)

不幸的是,往返不是一个微不足道的问题。使用XML,除非使用特殊的解析器(如DecentXML,但那是用于Java),否则通常无法保留原始文档。

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

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

  • 您可以过滤输出。 XML只允许在一个地方使用字符串<ns0:,因此您可以使用<搜索并替换它,然后使用<group xmlns:ns0="<group xmlns="进行搜索和替换。这非常安全,除非您的XML中有CDATA

  • 您可以编写自己的简单XML解析器。将输入作为字符串读取,然后为每对<>创建元素以及它们在输入中的位置。这使您可以快速分开输入,但仅适用于小输入。

答案 2 :(得分:1)

当我在代码

上保存xml add default_namespace参数时很容易避免使用ns0

密钥代码:xmltree.write(xmlfiile,“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 :(得分:0)

来自lxml的

etree提供此功能。

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

  2. 在写入文件时,它不会更改名称空间。

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

    P.S:lxml应单独安装。