0
***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

4 回答 4

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)

如果“外部结构”比这更复杂——例如,如果每个文档在另一个结束之后立即开始,或者如果您需要状态信息来区分树内空行和树间空行——那么这个解决方案将不起作用(或者,至少,它会更难而不是更容易)。

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


看看iterparse。它使您可以动态解析文档,在到达元素末尾时生成每个元素(如果树太大而无法放入内存,甚至可以在进行过程中修剪树)。

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

您可以通过读取第一个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

filter(你可以用and使它更简洁一些itertools,但我认为显式版本对于从未使用过的人来说更容易理解iterparse。)

所以,你可以在 EOF 之前循环执行此操作,对吗?嗯,不。问题是它iterparse不会将读取指针留在下一个文档的开头,并且无法找出下一个文档的开始位置。

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


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

它没有准确记录类文件方法iterparse需要什么,但是正如源代码所示,您所需要的只是read(size)(并且您可以返回少于size字节的字节,就像真实文件一样)和close(),所以这并不难.


或者,您可以下拉一个级别并ET.XMLParser直接使用。这听起来很吓人,但并没有那么糟糕——看看 shortiterparse的源代码有多短,而你真正需要的东西有多么少。

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

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
于 2013-06-25T02:02:55.187 回答
3

只需创建一个带有根/结束根的字符串:

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

然后使用ET.fromstring(xml_data)

于 2013-06-25T01:27:20.637 回答
0

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

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 树(它们的根元素)。

示例

import sys
import xml.etree.cElementTree as etree

for root in parse_multiple(sys.stdin):
    etree.dump(root)
于 2013-06-25T04:35:16.567 回答
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 吗?

是的,它会的。从来没有任何充分的理由使用readlines().

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

如果您想将 10 棵树的森林组合成一棵大树,您几乎已经阅读了全部内容,对吧?

除非你改变你做事的方式。做到这一点的简单方法是将您自己的简单解析器(将文件拆分为空行的东西)放在 ET 前面。例如:

while True:
    lines = iter(inputFile.readline, '')
    if not lines:
        break
    xml = ET.fromstringlist(lines)
    # do stuff with this tree
于 2013-06-25T01:27:49.193 回答