***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代码执行相同操作,依此类推。
任何帮助将不胜感激,谢谢。
答案 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
(您可以使filter
和itertools
更加简洁,但我认为明确的版本对于从未使用过的人来说更容易理解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 answer的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树(它们的root elements)。
import sys
import xml.etree.cElementTree as etree
for root in parse_multiple(sys.stdin):
etree.dump(root)