删除跨越XML树分支的两个标签之间的所有内容

1 投票
3 回答
1122 浏览
提问于 2025-04-16 02:16

我正在尝试使用Python和lxml删除XML文档中两个标签之间的所有内容。问题是这些标签可能在树的不同分支上(但总是在相同的深度)。下面是一个示例文档。

<root>
    <p> Hello world <start />this is a paragraph </p>
    <p> Goodbye world. <end />I'm leaving now </p>
</root>

我想删除开始标签和结束标签之间的所有内容,这样最终只会剩下一个p标签:

<root>
    <p> Hello world I'm leaving now </p>
</root>

有没有人知道如何使用lxml和Python来实现这个?

3 个回答

1

我知道可能会有人因为这个想法想要攻击我,但你可以直接用正则表达式来解决这个问题:

import re
new_string = re.sub(r'<start />(.*?)<end />', '', your_string, re.S)

当你的内容不是有效的XML时,就不能使用XML解析器。

1

你现在遇到了一团糟,真应该给那个故意搞乱XML嵌套规则的人一个教训。

你最好使用像SAX这样的工具,来识别<start/>标签,然后开始丢弃输入,直到遇到<end/>。SAX的好处在于,它可以让你在处理每一个词法单元时采取任意的操作,而lxml在你接触到它们之前就已经把开始和结束标签分开了。

顺便说一下,你可能还想把这些文档转换成可用的XML格式。

0

你可以试试使用类似SAX的目标解析器接口

from lxml import etree

class SkipStartEndTarget:
    def __init__(self, *args, **kwargs):
        self.builder = etree.TreeBuilder()
        self.skip = False

    def start(self, tag, attrib, nsmap=None):
        if tag == 'start':
            self.skip = True
        if not self.skip:
            self.builder.start(tag, attrib, nsmap)

    def data(self, data):
        if not self.skip:
            self.builder.data(data)

    def comment(self, comment):
        if not self.skip:
            self.builder.comment(self)

    def pi(self, target, data):
        if not self.skip:
            self.builder.pi(target, data)

    def end(self, tag):
        if not self.skip:
            self.builder.end(tag)
        if tag == 'end':
            self.skip = False

    def close(self):
        self.skip = False
        return self.builder.close()

然后你可以用SkipStartEndTarget这个类来创建一个parser target,并用这个目标创建一个自定义的XMLParser,像这样:

parser = etree.XMLParser(target=SkipStartEndTarget())

如果你需要的话,你仍然可以给解析器提供其他选项。然后你可以把这个解析器传给你正在使用的解析函数,比如:

elem = etree.fromstring(xml_str, parser=parser)

这个方法同样适用于etree.XML()etree.parse(),你甚至可以用etree.setdefaultparser()把解析器设置为默认解析器(不过这可能不是个好主意)。有一点需要注意的是:即使使用etree.parse(),它也不会返回一个元素树,而是总是返回一个元素(就像etree.XML()etree.fromstring()那样)。我觉得目前还做不到这一点,所以如果这对你来说是个问题,你需要想办法解决。

另外,使用lxml.sax从SAX事件创建元素树也是可能的,不过这可能会稍微复杂一些,而且速度也会慢一些。与上面的例子不同,它会返回一个元素树,但我认为它不会提供使用etree.parse()时能得到的.docinfo。我还相信它(目前)不支持注释和处理指令(pi)。(我还没用过,所以现在不能更准确地说)

还要注意,任何类似SAX的解析方法都要求在<start/><end/>之间跳过的内容仍然要形成一个有效的文档。在你的例子中是这样的,但如果第二个<p><p2>的话就不行了,因为那样你会得到<p>....</p2>

撰写回答