【问题标题】:Faithfully Preserve Comments in Parsed XML在解析的 XML 中忠实地保留注释
【发布时间】:2016-02-08 00:20:24
【问题描述】:

我想在操作 XML 时尽可能忠实地保留 cmets。

我设法保留了 cmets,但内容被 XML 转义了。

#!/usr/bin/env python
# add_host_to_tomcat.py

import xml.etree.ElementTree as ET
from CommentedTreeBuilder import CommentedTreeBuilder
parser = CommentedTreeBuilder()

if __name__ == '__main__':
    filename = "/opt/lucee/tomcat/conf/server.xml"

    # this is the important part: use the comment-preserving parser
    tree = ET.parse(filename, parser)

    # get the node to add a child to
    engine_node = tree.find("./Service/Engine")

    # add a node: Engine.Host
    host_node = ET.SubElement(
        engine_node,
        "Host",
        name="local.mysite.com",
        appBase="webapps"
    )
    # add a child to new node: Engine.Host.Context
    ET.SubElement(
        host_node,
        'Context',
        path="",
        docBase="/path/to/doc/base"
    )

    tree.write('out.xml')
#!/usr/bin/env python
# CommentedTreeBuilder.py

from xml.etree import ElementTree

class CommentedTreeBuilder ( ElementTree.XMLTreeBuilder ):
    def __init__ ( self, html = 0, target = None ):
        ElementTree.XMLTreeBuilder.__init__( self, html, target )
        self._parser.CommentHandler = self.handle_comment

    def handle_comment ( self, data ):
        self._target.start( ElementTree.Comment, {} )
        self._target.data( data )
        self._target.end( ElementTree.Comment )

然而,cmets 喜欢这样:

  <!--
EXAMPLE HOST ENTRY:
    <Host name="lucee.org" appBase="webapps">
         <Context path="" docBase="/var/sites/getrailo.org" />
     <Alias>www.lucee.org</Alias>
     <Alias>my.lucee.org</Alias>
    </Host>

HOST ENTRY TEMPLATE:
    <Host name="[ENTER DOMAIN NAME]" appBase="webapps">
         <Context path="" docBase="[ENTER SYSTEM PATH]" />
     <Alias>[ENTER DOMAIN ALIAS]</Alias>
    </Host>
  -->

最终结果为:

  <!--
            EXAMPLE HOST ENTRY:
    &lt;Host name="lucee.org" appBase="webapps"&gt;
         &lt;Context path="" docBase="/var/sites/getrailo.org" /&gt;
         &lt;Alias&gt;www.lucee.org&lt;/Alias&gt;
         &lt;Alias&gt;my.lucee.org&lt;/Alias&gt;
    &lt;/Host&gt;

    HOST ENTRY TEMPLATE:
    &lt;Host name="[ENTER DOMAIN NAME]" appBase="webapps"&gt;
         &lt;Context path="" docBase="[ENTER SYSTEM PATH]" /&gt;
         &lt;Alias&gt;[ENTER DOMAIN ALIAS]&lt;/Alias&gt;
    &lt;/Host&gt;
   -->

我也在CommentedTreeBuilder.py 中尝试了self._target.data( saxutils.unescape(data) ),但它似乎没有做任何事情。事实上,我认为问题发生在handle_commment() 步骤之后的某个地方。

顺便说一句,这个问题和this类似。

【问题讨论】:

标签: python xml python-2.7 elementtree


【解决方案1】:

使用 Python 2.7 和 3.5 测试,以下代码应按预期工作。

#!/usr/bin/env python
# CommentedTreeBuilder.py
from xml.etree import ElementTree

class CommentedTreeBuilder(ElementTree.TreeBuilder):
    def comment(self, data):
        self.start(ElementTree.Comment, {})
        self.data(data)
        self.end(ElementTree.Comment)

然后,在主代码中使用

parser = ElementTree.XMLParser(target=CommentedTreeBuilder())

作为解析器而不是当前解析器。

顺便说一句,cmets 开箱即用,lxml 可以正常工作。也就是说,你可以这样做

import lxml.etree as ET
tree = ET.parse(filename)

以上都不需要。

【讨论】:

  • 两种解决方案似乎都保留了 cmets,谢谢!但是其他元素会重新格式化(并且可能会重新排序属性)。我知道这对于机器可读性无关紧要,但对于我的目的(人类可读性、版本控制和仅触摸显式触摸的元素)而言,它很重要。 FWIW,我的原始版本碰巧保留了其他元素完整(即格式化为原始版本)。这个答案确实解决了我的明确问题,因此它将获得答案奖,但我想知道是否也可以保留非评论元素格式。
  • 据我所知,唯一需要修改的是属性的顺序和标签内的空格(如果我遗漏了任何内容,请纠正我)。您通常不应该拥有或关心后者。由于属性存储在xml 的字典中,因此它们在输出中的顺序是随机的。要解决此问题,您可以使用类似于 cmets 的解决方法,请参阅 stackoverflow.com/q/2741480/2997179。或者正如使用lxml 进行的快速测试所显示的那样,它似乎保留了属性顺序并且似乎是一个可行的解决方案。无论哪种方式,我认为这值得一个单独的 SO 问题。
  • 好吧.. 除了属性,xml 还丢弃处理指令标签(例如&lt;?xml-stylesheet href="mystyle.css" type="text/css"?&gt;)并重命名 xmlns 命名空间。同样,lxml 两者都没有。
  • xmllxml 也默认省略 &lt;?xml...?&gt;&lt;!DOCTYPE ...&gt; 声明。 stackoverflow.com/q/18173983/2997179stackoverflow.com/q/12966488/2997179 对此有解决方案。
  • 适用于 Python 3.5.4。
【解决方案2】:

Python 3.8insert_comments 参数添加到TreeBuilder,其中:

class xml.etree.ElementTree.TreeBuilder(element_factory=None, *, comment_factory=None, pi_factory=None, insert_comments=False, insert_pis=False)

当 insert_cmets 和/或 insert_pis 为真时,如果 cmets/pis 出现在根元素内(但不在根元素之外),它们将被插入到树中。

例子:

parser = ElementTree.XMLParser(target=ElementTree.TreeBuilder(insert_comments=True))

【讨论】:

  • 使用 Python3.8 为我工作
【解决方案3】:

Martin 的代码对我不起作用。我修改了以下内容,按预期工作。

import xml.etree.ElementTree as ET

class CommentedTreeBuilder(ET.XMLTreeBuilder):
    def __init__(self, *args, **kwargs):
        super(CommentedTreeBuilder, self).__init__(*args, **kwargs)
        self._parser.CommentHandler = self.comment

    def comment(self, data):
        self._target.start(ET.Comment, {})
        self._target.data(data)
        self._target.end(ET.Comment)

这是测试

    parser=CommentedTreeBuilder()
    tree = ET.parse(filename, parser)
    tree.write('out.xml')

【讨论】:

  • 这是python 3.52及以上版本
【解决方案4】:

看起来@Martin 和@sukhbinder 的两个答案都对我不起作用...所以将其作为python 3.x 上可行的完整解决方案

from xml.etree import ElementTree

string = '''<?xml version="1.0"?>
<data>
    <!--Test
    -->
    <country name="Liechtenstein">
        <rank>1</rank>
        <year>2008</year>
        <gdppc>141100</gdppc>
        <neighbor name="Austria" direction="E"/>
        <neighbor name="Switzerland" direction="W"/>
    </country>
</data>'''

class CommentedTreeBuilder(ElementTree.TreeBuilder):
    def comment(self, data):
        self.start(ElementTree.Comment, {})
        self.data(data)
        self.end(ElementTree.Comment)

parser = ElementTree.XMLParser(target=CommentedTreeBuilder())
tree = ElementTree.fromstring(string, parser)
print(tree.find("./*[0]").text)
# or ElementTree.parse(filename, parser)

【讨论】:

    【解决方案5】:

    Martin 的回答是正确的,只是缺少一些代码, 我知道这对于更有经验的程序员来说可能是显而易见的,但作为一个新程序员,我花了一分钟才明白: 马丁的回答:

    import xml.etree.ElementTree as ET
    from xml.etree import ElementTree
    
    class CommentedTreeBuilder(ElementTree.TreeBuilder):
    # This class will retain remarks and comments oposed to the xml parser default
        def comment(self, data):
            self.start(ElementTree.Comment, {})
            self.data(data)
            self.end(ElementTree.Comment)
    
    # the missing part:
    def parse_xml_with_remarks(filepath):
        ctb = CommentedTreeBuilder()
        xp = ET.XMLParser(target=ctb)
        tree = ET.parse(filepath, parser=xp)
        return tree
    
    # parsing the file, and getting root
    tree=parse_xml_with_remarks(file)
    root=tree.getroot()
    

    【讨论】:

      猜你喜欢
      • 2011-05-27
      • 1970-01-01
      • 2016-02-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-08-15
      • 1970-01-01
      • 2022-01-19
      相关资源
      最近更新 更多