听起来您真正想做的是解析一系列 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