如何在不解析整个文件的情况下获取树的根节点?
我正在制作一个xml解析器,用来解析来自不同工具的xml报告,而每个工具生成的报告都有不同的标签。
举个例子:
Arachni生成的xml报告的根标签是<arachni_report></arachni_report>
。
nmap生成的xml报告的根标签是<nmaprun></nmaprun>
。
我想尽量避免解析整个文件,除非它是我想要的工具生成的有效报告。
我首先想到的是使用ElementTree,解析整个xml文件(假设它是有效的xml),然后根据根标签来判断这个报告是属于Arachni还是nmap。
目前我在使用cElementTree,据我所知,这里没有getroot()这个选项,但我的目标是让这个解析器只处理被认可的文件,而不去解析那些不必要的文件。
顺便说一下,我还在学习xml解析,提前谢谢大家的帮助。
6 个回答
我理解你的问题是这样的:你想检查一个文件,看看它是否是你能识别的格式,只有在确认它是你认可的格式时,才将其解析为XML。@eyquem说得对:你应该使用简单的字符串方法。
最简单的方法是从文件的开头读取一小部分内容,看看里面是否有你能识别的根元素:
f = open(the_file)
head = f.read(200)
f.close()
if "<arachni_report" in head:
#.. re-open and parse as arachni ..
elif "<nmaprun" in head:
#.. re-open and parse as nmaprun ..
这个方法的好处是,在判断这个文件是否有趣之前,只需要读取文件的一小部分。
这对一个懂XML的人来说是不是挺有意思的呢?
ch = """\
<?xml version="1.0" encoding="ISO-8859-1" ?>
<!-- Edited by XMLSpy® -->
<CATALOG>
<CD>
<TITLE>Empire Burlesque</TITLE>
<ARTIST>Bob Dylan</ARTIST>
<COUNTRY>USA</COUNTRY>
<COMPANY>Columbia</COMPANY>
<PRICE>10.90</PRICE>
<YEAR>1985</YEAR>
</CD>
<CD>
<TITLE>Hide your heart</TITLE>
<ARTIST>Bonnie Tyler</ARTIST>
<COUNTRY>UK</COUNTRY>
<COMPANY>CBS Records</COMPANY>
<PRICE>9.90</PRICE>
<YEAR>1988</YEAR>
</CD>
</CATALOG>
<!-- This is the end of arachni report -->
"""
chrstr = ch.strip()
x = max(chrstr.rfind('\r'),chrstr.rfind('\n'))
lastline = chrstr[x+1:]
if lastline[0:5]=='<!-- ':
chrstr = ch[0:x].rstrip()
x = max(chrstr.rfind('\r'),chrstr.rfind('\n'))
print chrstr[x+1:]
else:
print lastline
结果,依然如此
</CATALOG>
如果需要的话,可以加一个检查,确保树的根节点的开始标签在文件的开头附近。
.
如果文件很大,为了加快处理速度,我们可以把文件指针移动到文件的末尾附近(比如距离末尾200或600个字符的位置),这样就只需要读取和搜索200或600个字符的内容(树根的结束标签不会比这个长,对吧?)
from os.path import getsize
with open('I:\\uuu.txt') as f:
L = getsize('I:\\uuu.txt')
print 'L==',L
f.seek( -min(600,L) , 2)
ch = f.read()
if '\r' not in ch and '\n' not in ch:
f.seek(0,0)
ch = f.read()
chrstr = ch.strip()
x = max(chrstr.rfind('\r'),chrstr.rfind('\n'))
lastline = chrstr[x+1:]
if lastline[0:5]=='<!-- ':
chrstr = ch[0:x].rstrip()
x = max(chrstr.rfind('\r'),chrstr.rfind('\n'))
print chrstr[x+1:]
else:
print lastline
“简单的字符串方法”是所有问题的根源——下面有例子说明。
更新 2 代码和输出现在显示,提议的正则表达式效果也不是很好。
使用 ElementTree。你需要的函数是 iterparse
。启用“开始”事件。在第一次迭代时退出。
代码:
# coding: ascii
import xml.etree.cElementTree as et
# import xml.etree.ElementTree as et
# import lxml.etree as et
from cStringIO import StringIO
import re
xml_text_1 = """\
<?xml version="1.0" ?>
<!-- this is a comment -->
<root
><foo>bar</foo></root
>
"""
xml_text_2 = """\
<?xml version="1.0" ?>
<!-- this is a comment -->
<root
><foo>bar</foo></root
>
<!--
That's all, folks!
-->
"""
xml_text_3 = '''<?xml version="1.0" ?>
<!-- <mole1> -->
<root><foo /></root>
<!-- </mole2> -->'''
xml_text_4 = '''<?xml version="1.0" ?><!-- <mole1> --><root><foo /></root><!-- </mole2> -->'''
for xml_text in (xml_text_1, xml_text_2, xml_text_3, xml_text_4):
print
chrstr = xml_text.strip()
x = max(chrstr.rfind('\r'),chrstr.rfind('\n'))
lastline = chrstr[x:]
print "*** eyquem 1:", repr(lastline.strip())
chrstr = xml_text.strip()
x = max(chrstr.rfind('\r'),chrstr.rfind('\n'))
lastline = chrstr[x+1:]
if lastline[0:5]=='<!-- ':
chrstr = xml_text[0:x].rstrip()
x = max(chrstr.rfind('\r'),chrstr.rfind('\n'))
print "*** eyquem 2:", repr(chrstr[x+1:])
else:
print "*** eyquem 2:", repr(lastline)
m = None
for m in re.finditer('^</[^>]+>', xml_text, re.MULTILINE):
pass
if m: print "*** eyquem 3:", repr(m.group())
else: print "*** eyquem 3:", "FAIL"
m = None
for m in re.finditer('</[^>]+>', xml_text):
pass
if m: print "*** eyquem 4:", repr(m.group())
else: print "*** eyquem 4:", "FAIL"
m = re.search('^<(?![?!])[^>]+>', xml_text, re.MULTILINE)
if m: print "*** eyquem 5:", repr(m.group())
else: print "*** eyquem 5:", "FAIL"
m = re.search('<(?![?!])[^>]+>', xml_text)
if m: print "*** eyquem 6:", repr(m.group())
else: print "*** eyquem 6:", "FAIL"
filelike_obj = StringIO(xml_text)
tree = et.parse(filelike_obj)
print "*** parse:", tree.getroot().tag
filelike_obj = StringIO(xml_text)
for event, elem in et.iterparse(filelike_obj, ('start', 'end')):
print "*** iterparse:", elem.tag
break
上面的 ElementTree 相关代码适用于 Python 2.5 到 2.7。也可以在 Python 2.2 到 2.4 上运行;你只需要从 effbot.org 获取 ElementTree 和 cElementTree,并做一些条件导入。应该可以与任何 lxml 版本兼容。
输出:
*** eyquem 1: '>'
*** eyquem 2: '>'
*** eyquem 3: FAIL
*** eyquem 4: '</root\n>'
*** eyquem 5: '<root\n>'
*** eyquem 6: '<root\n>'
*** parse: root
*** iterparse: root
*** eyquem 1: '-->'
*** eyquem 2: '-->'
*** eyquem 3: FAIL
*** eyquem 4: '</root\n>'
*** eyquem 5: '<root\n>'
*** eyquem 6: '<root\n>'
*** parse: root
*** iterparse: root
*** eyquem 1: '<!-- </mole2> -->'
*** eyquem 2: '<root><foo /></root>'
*** eyquem 3: FAIL
*** eyquem 4: '</mole2>'
*** eyquem 5: '<root>'
*** eyquem 6: '<mole1>'
*** parse: root
*** iterparse: root
*** eyquem 1: '>'
*** eyquem 2: '<?xml version="1.0" ?><!-- <mole1> --><root><foo /></root><!-- </mole2> -->'
*** eyquem 3: FAIL
*** eyquem 4: '</mole2>'
*** eyquem 5: FAIL
*** eyquem 6: '<mole1>'
*** parse: root
*** iterparse: root
更新 1 上面的代码是演示代码。下面的更像是实际应用代码……只需添加异常处理。已在 Python 2.7 和 2.2 上测试。
try:
import xml.etree.cElementTree as ET
except ImportError:
import cElementTree as ET
def get_root_tag_from_xml_file(xml_file_path):
result = f = None
try:
f = open(xml_file_path, 'rb')
for event, elem in ET.iterparse(f, ('start', )):
result = elem.tag
break
finally:
if f: f.close()
return result
if __name__ == "__main__":
import sys, glob
for pattern in sys.argv[1:]:
for filename in glob.glob(pattern):
print filename, get_root_tag_from_xml_file(filename)