如何使用Python从XML中删除元素

15

我被XML和Python卡住了。任务很简单,但我一直无法解决它,花了很长时间。我来这里寻求建议,如何用几行代码解决它。

感谢任何关于遍历树的帮助。我总是得到太多或太少的元素。元素可以无限嵌套。给出的示例只是一个例子。我接受任何解决方案,不挑剔dom,minidom,sax等等..

我有一个类似于这个的XML文件:

<root>
    <elm>
        <elm>Common content</elm>

        <elm xmlns="http://example.org/ns">
            <elm lang="en">Content EN</elm>
            <elm lang="cs">žluťoučký koníček</elm>
        </elm>

        <elm xml:id="abc123">Common content</elm>

        <elm lang="en">Content EN</elm>
        <elm lang="cs">Content CS</elm>

        <elm lang="en">
            <elm>Content EN</elm>
            <elm>Content EN</elm>
        </elm>

        <elm lang="cs">
            <elm>Content CS</elm>
            <elm>Content CS</elm>
        </elm>
    </elm>
</root>

我需要的是解析XML并编写一个新文件。新文件应包含给定语言的所有元素和没有lang属性的元素。

对于“cs”语言,输出文件应包含以下内容:

<root>
    <elm>
        <elm>Common content</elm>

        <elm xmlns="http://example.org/ns">
            <elm lang="cs">žluťoučký koníček</elm>
        </elm>

        <elm xml:id="abc123">Common content</elm>

        <elm lang="cs">Content CS</elm>

        <elm lang="cs">
            <elm>Content CS</elm>
            <elm>Content CS</elm>
        </elm>
    </elm>
</root>

如果你能在新文件中省略lang属性,那就更好了,但这并不是很重要。

UPDATE1: 添加了Unicode字符和命名空间属性。

UPDATE2: 使用Python 2.5,优先使用标准库。


“对于"en"语言,输出文件应该包含这个内容:” 我猜您的意思是给定的输出是针对 "cs" 语言的? - LarsH
@LarsH:我更新了问题,添加了一些Unicode字符。你是对的,应该写成:“对于”cs“语言”。我会更改它。 - dwich
3个回答

14

使用 lxml:

import lxml.etree as le

with open('doc.xml','r') as f:
    doc=le.parse(f)
    for elem in doc.xpath('//*[attribute::lang]'):
        if elem.attrib['lang']=='en':
            elem.attrib.pop('lang')
        else:
            parent=elem.getparent()
            parent.remove(elem)
    print(le.tostring(doc))
产生(yields)。
<root>
    <elm>Common content</elm>

    <elm>
        <elm>Content EN</elm>
        </elm>

    <elm>Common content</elm>

    <elm>Content EN</elm>
    <elm>
        <elm>Content EN</elm>
        <elm>Content EN</elm>
    </elm>

    </root>

非常感谢。我无法在我的WinXP上安装lxml,出现了编译器问题。稍后会再试一次。 - dwich
可以了!谢谢!你救了我的晚上 :) 我感谢你们两个,两种解决方案都很好。 - dwich

6
我不确定如何最好地删除lang属性,但这里有一些代码可以做其他更改(Python 2.7; 对于2.5或2.6,请使用getIterator而不是iter),假设当您删除一个元素时,您也总是想要删除该元素中包含的所有内容。
此代码仅将结果打印到标准输出(当然,您可以将其重定向为所需,或直接将其写入某个新文件等)。
import sys
from xml.etree import cElementTree as et

def picklang(path, lang='en'):
    tr = et.parse(path)
    for element in tr.iter():
        for subelement in element:
            la = subelement.get('lang')
            if la is not None and la != lang:
                element.remove(subelement)
    return tr

if __name__ == '__main__':
    tr = picklang('la.xml')
    tr.write(sys.stdout)
    print

la.xml 为例,这将写入

<root>
    <elm>Common content</elm>

    <elm>
        <elm lang="en">Content EN</elm>
        </elm>

    <elm>Common content</elm>

    <elm lang="en">Content EN</elm>
    <elm lang="en">
        <elm>Content EN</elm>
        <elm>Content EN</elm>
    </elm>

    </root>

谢谢Alex,非常好用。除了两个问题 - 命名空间和Unicode。如果有xmlns属性,例如<elm xmlns="http://example.org/ns">,新节点本身会得到一个xmlns:ns0="http://example.org/ns"属性,所有子节点都会得到一个<ns0:前缀。这些前缀在源文件中不存在。另外无法强制write()方法以原始形式写入Unicode字符。我将更新示例文件。 - dwich
@dwich,对于编写,您只需在write调用中添加一个您选择的encoding参数即可。 诸如命名空间问题之类的美学问题(我认为不会改变XML的语义)更加棘手,遗憾的是(就像您可能已经注意到的那样,输出中的缩进不同,因为元素中的空格也被删除了)。 - Alex Martelli
那个 Unicode 的问题是我的错误,我开始尝试使用编解码器,虽然我使用了 encoding='utf-8',但由于打开方式不正确,它并没有起作用。谢谢你的回答,我会选择 ~unutbu 的解决方案,因为他的代码在命名空间方面没有问题。两个答案都是正确的。谢谢大家! - dwich
@dwich,我同意你的观点 - @unutbu的答案更好(如果可以使用第三方包,如lxml),因为它可以像你理想中的那样删除属性,而我的方法则不能。 - Alex Martelli

3

更新@Alex Martelli的代码,以消除元素列表在原地更新的错误。如果输入稍微复杂,上面的解决方案将会给出错误的答案。

import sys
from xml.etree import cElementTree as et

def picklang(path, lang='en'):
    tr = et.parse(path)
    for element in tr.iter():
        for subelement in element[:]:
            la = subelement.get('lang')

            if la is not None and la != lang:
                element.remove(subelement)
    return tr

if __name__ == '__main__':
    tr = picklang('la.xml')
    tr.write(sys.stdout)
    print

第7行的代码 for subelement in element: 被改为 for subelement in element[:]:,因为在遍历列表时原地更新列表是不正确的。

这段代码遍历元素列表的副本,并在原始元素列表中 lang != "en" 时删除元素。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接