如何在python中将XML文件解析为树

时间:2013-06-25 01:22:37

标签: python elementtree

***I must use Elementtree for this project, so if you could, please suggest something that utilizes Elementtree

我有一个看起来像这样的文件(每个文件用空行隔开)

<a>
    <b>
       ....
    </b>
    <c>
       ....
    </c>
</a>
<d><c></c></d>

<a>
    <b>
       ....
    </b>
    <c>
       ....
    </c>
</a>
<d><c></c></d>

<a>
    <b>
       ....
    </b>
    <c>
       ....
    </c>
</a>
<d><c></c></d>

我知道这不是一个有效的XML,所以我要做的是将整个内容作为字符串读取并添加一个根元素,对于每个XML来说最终看起来像这样:

<root>
    <a>
        <b>
           ....
        </b>
        <c>
           ....
        </c>
    </a>
    <d><c></c></d>
</root>

我想知道是否有一种简单的方法可以逐个读取XML代码并将其与父节点连接起来,并为下一个XML代码执行相同操作,依此类推。

任何帮助将不胜感激,谢谢。

4 个答案:

答案 0 :(得分:4)

听起来你真正想做的就是解析一系列XML树 - 可能在同一个文件中不止一个,或者可能有多个文件,或者谁知道。

ElementTree开箱即可做到这一点......但你可以用它来构建一些东西。


首先,有简单的方法:只需将自己的解析器放在etree前面。如果您的XML文档实际上是用空行分隔的,并且任何文档中都没有嵌入的行,那么这很简单:

lines = []
for line in inputFile:
    if not line.strip():
        print(lines)
        xml = ET.fromstringlist(lines)
        print(xml)
        lines = []
    else:
        lines.append(line)
print(lines)
xml = ET.fromstringlist(lines)
print(xml)

如果&#34;外部结构&#34;比这更复杂 - 例如,如果每个文档在另一个文档之后立即开始,或者如果您需要有状态信息来区分树内空白行与树之间的空白行 - 那么这个解决方案将无法工作(或者,至少,它会更难而不是更容易。)

在这种情况下,事情变得更有趣。


看看iterparse。它允许你动态地解析文档,当它到达元素的末尾时产生每个元素(甚至在你去的时候修剪树,如果树太大而不适合内存)。

问题是,当iterparse到达文件末尾时,它会引发ParseError并中止,而不是转到下一个文档。

您可以通过阅读第一个start元素轻松检测到该元素,然后在到达end后立即停止。它有点复杂,但也不算太糟糕。而不是:

for _, elem in ET.iterparse(arg):
    print(elem)

你必须这样做:

parser = ET.iterparse(arg, events=('start', 'end'))
_, start = next(parser)
while True:
    event, elem = next(parser)
    if event == 'end':
        print(elem)
        if elem == start:
            break

(您可以使filteritertools更加简洁,但我认为明确的版本对于从未使用过的人来说更容易理解iterparse 。)

所以,你可以在循环中直到EOF,对吗?好吧,不。问题是iterparse没有将读指针留在下一个文档的开头,并且无法找到下一个文档的起始位置。

因此,您需要控制文件,并将数据提供给iterparse。有两种方法可以做到这一点:


首先,您可以创建自己的文件包装器对象,该对象提供ET所需的所有类文件方法,并将其传递给ET.iterparse。这样,您可以跟踪文件iterparse读取的距离,然后在该偏移量处开始下一个解析。

并未准确记录iterparse所需的类似文件的方法,但正如the source所示,您需要的只是read(size)(并且您已被允许返回少于size个字节,就像真实文件一样)和close(),所以这一点并不难。


或者,您可以下拉关卡并直接使用ET.XMLParser。这听起来很可怕,但并不是那么糟糕 - 看看iterparse的来源有多短,以及你实际需要做的事情有多少。

无论如何,它归结为类似的东西(伪代码,未经测试):

class Target(object):
    def __init__(self):
        self.start_tag = None
        self.builder = ET.TreeBuilder()
        self.tree = None
    def start(self, tag, attrib):
        if self.start_tag is None:
            self.start_tag = tag
        return self.builder.start(tag, attrib)
    def end(self, tag):
        ret = self.builder.end(tag, attrib)
        if self.start_tag == tag:
            self.tree = self.builder.close()
            return self.tree
        return ret
    def data(self, data):
        return self.builder.data(data)
    def close(self):
        if self.tree is None:
            self.tree = self.builder.close()
        return self.tree

parser = None
for line in inputFile:
    if parser is None:
        target = Target()
        parser = ET.XMLParser(target=target)
    parser.feed(line)
    if target.tree:
        do_stuff_with(target.tree)
        parser = None

答案 1 :(得分:3)

只需创建一个包含根/结束根的字符串:

with open('yourfile') as fin:
    xml_data = '<{0}>{1}</{0}>'.format('rootnode', fin.read())

然后使用ET.fromstring(xml_data)

答案 2 :(得分:0)

这里的问题非常简单。

ET.parse采用文件名(或文件对象)。但是你传给它一个行列表。那不是文件名。您收到此错误的原因:

TypeError: coercing to Unicode: need string or buffer, list found

...是它正在尝试使用您的列表,就好像它是一个字符串,这不起作用。

如果您已阅读该文件,则可以使用ET.fromstring。但是,您必须将其读入字符串,而不是字符串列表。例如:

def readXML (inputFile) : #inputFile is sys.stdin
    f= '<XML>' + inputFile.read() + '</XML>'
    newXML = ET.fromstring(f)
    print newXML.getroot().tag

或者,如果你使用的是Python 3.2或更高版本,你可以使用ET.fromstringlist,它接受​​一系列字符串 - 正是你所拥有的。


从你身边问题:

  

我在输入时遇到的另一个问题是我的输入文件有多个输入。比如说,我写的第一个XML中至少有10个以上。如果我做readlines(),是不是要读取整个XML?

是的,它会的。 There's never any good reason to use readlines()

但我不确定为什么这是一个问题。

如果你想把一棵10棵树的森林组合成一棵大树,那么你几乎已经阅读了所有内容,对吗?

除非你改变你做事的方式。执行此操作的简单方法是将您自己的简单解析器 - 将文件拆分为空白行 - 在ET前面。例如:

while True:
    lines = iter(inputFile.readline, '')
    if not lines:
        break
    xml = ET.fromstringlist(lines)
    # do stuff with this tree

答案 3 :(得分:0)

您有多个由空行分隔的xml片段。要使每个片段成为格式良好的xml文档,至少需要将它们包装在根元素中。基于@abarnert's answerfromstringlist代码示例:

from xml.etree.cElementTree import XMLParser

def parse_multiple(lines):
    for line in lines:
        parser = XMLParser()
        parser.feed("<root>")      # start of xml document
        while line.strip():        # while non-blank line
            parser.feed(line)      # continue xml document
            line = next(lines, "") # get next line
        parser.feed("</root>")     # end of xml document
        yield parser.close() # yield root Element of the xml tree

它产生xml树(它们的root elements)。

Example

import sys
import xml.etree.cElementTree as etree

for root in parse_multiple(sys.stdin):
    etree.dump(root)