这就是您要找的东西,John Machin:我们连续剧的续集。我验证了这次我的大脑在正确的位置,我继续思考这个问题。
所以你已经扩展了演示代码。现在,通过您的几个示例文本,我很清楚字符串方法远远不够,我理解为什么。我非常有兴趣了解过程的底层并了解肯定的具体原因。
然后,我比以往任何时候都更深入地研究了 XML 规范,并使用W3c的验证器进行了测试,以增加我对 XML 文本结构细节的理解。这是一个相当严重的职业,但很有趣。我看到 XML 的格式是非常严格的规则和温文尔雅的自由的混合体。
根据您在示例中使用的将代码撕成碎片的技巧,我得出结论,XML 格式不需要将文本分成几行。事实上,正如W3c\n
的验证器向我展示的那样,字符\r
和\t
可以位于 XML 文本中的许多位置,只要它们不违反结构规则。
例如,它们被授权在标签之间没有任何限制:因此,一个元素可能占据多行。此外,即使标签也可以分成几行,或者在几个表格之间\t
,只要它们出现在一个标签的名称之后。也不需要像我一直看到的那样对 XML 文本的行进行缩进:我现在理解它只是为了便于阅读和逻辑理解而选择的个人便利。
好吧,你比我更清楚,约翰·马钦。多亏了您,我现在意识到 XML 格式的复杂性,并且我更好地理解了通过专用解析器以外的其他方式进行解析不切实际的原因。我顺便想知道普通编码人员是否意识到 XML 格式的这种尴尬:\n
在 XML 文本中到处出现字符的可能性。
.
无论如何,因为我已经在这个概念沸腾的锅中待了一段时间,所以我继续为你的 whac_moles 寻找解决方案,John Machin,作为一个指导性的游戏。
字符串方法退出游戏,我完善了我的正则表达式。
我知道,我知道:您会说即使使用正则表达式也无法分析 XML 文本。现在我知道了为什么,我同意。但我不会假装解析 XML 文本:我的正则表达式不会提取 XML 树的任何部分,它只会搜索一小部分文本。对于 OP 提出的问题,我认为使用正则表达式是非异端的。
.
从一开始,我认为搜索根的结束标记更容易和自然,因为结束标记没有属性,并且它周围的“噪音”比根的开始标记要少。
所以我现在的解决方案是:
~~ 打开 XML 文件
~~ 将文件的指针从末尾移动到位置-200
~~ 读取文件的最后 200 个字符
~~这里,两种策略:
- 要么只删除评论,然后使用正则表达式搜索标签,并考虑字符 \n、\r、\t
- 或在使用更简单的正则表达式搜索标记之前删除注释和所有字符 \n、\r、\t
文件越大,与使用 parse 或 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,urllib
xml5 = """\
<?xml version="1.0" ?>
<!-- this is a comment -->
<root\t
\r\t\r \r
><foo
>bar</foo\t \r></root
>
"""
xml6 = """\
<?xml version="1.0" ?>
<!-- this is a comment -->
<root
><foo
>bar</foo\n\t \t></root \t
\r>
<!-- \r \t
That's all, folks!
\t-->
"""
xml7 = '''<?xml version="1.0" ?>
<!-- <mole1> -->
<root><foo
\t\t\r\r\t/></root \t
>
<!-- </mole2>\t \r
\r-->
<!---->
'''
xml8 = '''<?xml version="1.0" ?><!-- \r<mole1> --><root> \t\t<foo \t\r\r/></root>\t<!-- </mole2> -->'''
sock = urllib.urlopen('http://www.cafeconleche.org/books/bible/examples/18/18-4.xsl')
xml9 = sock.read()
sock.close()
def rp(x):
return '\\r' if x.group()=='\r' else '\\t'
for xml_text in (xml5, xml6, xml7, xml8, xml9):
print '\\n\n'.join(re.sub('\r|\t',rp,xml_text).split('\n'))
print '-----------------------------'
xml_text_noc = re.sub('<!--.*?-->|[\n\r\t]','', xml_text,flags=re.DOTALL)
RE11 = '(?<=</)[^ >]+(?= *>)(?!.*</[^>]+>)' # with assertions # ^
m = re.search(RE11, xml_text_noc,re.DOTALL)
print "*** eyquem 11: " + repr(m.group() if m else "FAIL")
xml_text_noc = re.sub('<!--.*?-->|[\n\r\t]','', xml_text,flags=re.DOTALL)
RE12 = '</([^ >]+) *>(?!.*</[^>]+>)' # with group(1) # ^
m = re.search(RE12, xml_text_noc,re.DOTALL)
print "*** eyquem 12: " + repr(m.group(1) if m else "FAIL")
xml_text_noc = re.sub('<!--.*?-->|[\n\r\t]','', xml_text,flags=re.DOTALL)
RE13 = '</[^ >]+ *>(?!.*</[^>]+>)' # without group(1) # ^
m = re.search(RE13, xml_text_noc,re.DOTALL)
print "*** eyquem 13: " + repr(m.group()[2:-1].rstrip() if m else "FAIL")
xml_text_noc = re.sub('<!--.*?-->','', xml_text,flags=re.DOTALL)
RE14 = '(?<=</)[^ \n\r\t>]+(?=[ \n\r\t]*>)(?!.*</[^>]+>)' # with assertions # ^
m = re.search(RE14, xml_text_noc,re.DOTALL)
print "*** eyquem 14: " + repr(m.group() if m else "FAIL")
xml_text_noc = re.sub('<!--.*?-->','', xml_text,flags=re.DOTALL)
RE15 = '</([^ \n\r\t>]+)[ \n\r\t]*>(?!.*</[^>]+>)' # with group(1) # <
m = re.search(RE15, xml_text_noc,re.DOTALL)
print "*** eyquem 15: " + repr(m.group(1).rstrip() if m else "FAIL")
xml_text_noc = re.sub('<!--.*?-->','', xml_text,flags=re.DOTALL)
RE16 = '</[^ \n\r\t>]+[ \n\r\t]*>(?!.*</[^>]+>)' # without group(1) # <
m = re.search(RE16, xml_text_noc,re.DOTALL)
print "*** eyquem 16: " + repr(m.group()[2:-1].rstrip() if m else "FAIL")
print
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
print '\n============================================='
结果
<?xml version="1.0" ?> \n
<!-- this is a comment --> \n
<root\t\n
\r\t\r \r\n
><foo\n
\n
>bar</foo\t \r></root\n
>\n
-----------------------------
*** eyquem 11: 'root'
*** eyquem 12: 'root'
*** eyquem 13: 'root'
*** eyquem 14: 'root'
*** eyquem 15: 'root'
*** eyquem 16: 'root'
*** parse: root
*** iterparse: root
=============================================
<?xml version="1.0" ?> \n
<!-- this is a comment --> \n
<root\n
><foo\n
>bar</foo\n
\t \t></root \t\n
\r>\n
<!-- \r \t\n
That's all, folks!\n
\n
\t-->\n
-----------------------------
*** eyquem 11: 'root'
*** eyquem 12: 'root'
*** eyquem 13: 'root'
*** eyquem 14: 'root'
*** eyquem 15: 'root'
*** eyquem 16: 'root'
*** parse: root
*** iterparse: root
=============================================
<?xml version="1.0" ?>\n
<!-- <mole1> --> \n
<root><foo\n
\n
\t\t\r\r\t/></root \t\n
> \n
<!-- </mole2>\t\n
-->\n
<!---->\n
-----------------------------
*** eyquem 11: 'root'
*** eyquem 12: 'root'
*** eyquem 13: 'root'
*** eyquem 14: 'root'
*** eyquem 15: 'root'
*** eyquem 16: 'root'
*** parse: root
*** iterparse: root
=============================================
<?xml version="1.0" ?><!-- \r<mole1> --><root> \t\t<foo \t\r\r/></root>\t<!-- </mole2> -->
-----------------------------
*** eyquem 11: 'root'
*** eyquem 12: 'root'
*** eyquem 13: 'root'
*** eyquem 14: 'root'
*** eyquem 15: 'root'
*** eyquem 16: 'root'
*** parse: root
*** iterparse: root
=============================================
<?xml version="1.0"?>\r\n
<stylesheet\r\n
xmlns="http://www.w3.org/XSL/Transform/1.0"\r\n
xmlns:fo="http://www.w3.org/XSL/Format/1.0"\r\n
result-ns="fo">\r\n
\r\n
<template match="/">\r\n
<fo:root xmlns:fo="http://www.w3.org/XSL/Format/1.0">\r\n
\r\n
<fo:layout-master-set>\r\n
<fo:simple-page-master page-master-name="only">\r\n
<fo:region-body/>\r\n
</fo:simple-page-master>\r\n
</fo:layout-master-set>\r\n
\r\n
<fo:page-sequence>\r\n
\r\n
<fo:sequence-specification>\r\n
<fo:sequence-specifier-single page-master-name="only"/>\r\n
</fo:sequence-specification>\r\n
\r\n
<fo:flow>\r\n
<apply-templates select="//ATOM"/>\r\n
</fo:flow>\r\n
\r\n
</fo:page-sequence>\r\n
\r\n
</fo:root>\r\n
</template>\r\n
\r\n
<template match="ATOM">\r\n
<fo:block font-size="20pt" font-family="serif">\r\n
<value-of select="NAME"/>\r\n
</fo:block>\r\n
</template>\r\n
\r\n
</stylesheet>\r\n
-----------------------------
*** eyquem 11: 'stylesheet'
*** eyquem 12: 'stylesheet'
*** eyquem 13: 'stylesheet'
*** eyquem 14: 'stylesheet'
*** eyquem 15: 'stylesheet'
*** eyquem 16: 'stylesheet'
*** parse: {http://www.w3.org/XSL/Transform/1.0}stylesheet
*** iterparse: {http://www.w3.org/XSL/Transform/1.0}stylesheet
=============================================
此代码现在测量执行的时间:
# 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
import urllib
from time import clock
sock = urllib.urlopen('http://www.cafeconleche.org/books/bible/examples/18/18-4.xsl')
ch = sock.read()
sock.close()
# the following lines are intended to insert additional lines
# into the XML text before its recording in a file, in order to
# obtain a real file to use, containing an XML text
# long enough to observe easily the timing's differences
li = ch.splitlines(True)[0:6] + 30*ch.splitlines(True)[6:-2] + ch.splitlines(True)[-2:]
with open('xml_example.xml','w') as f:
f.write(''.join(li))
print 'length of XML text in a file : ',len(''.join(li)),'\n'
# timings
P,I,A,B,C,D,E,F = [],[],[],[],[],[],[],[],
n = 50
for cnt in xrange(50):
te = clock()
for i in xrange (n):
with open('xml_example.xml') as filelike_obj:
tree = et.parse(filelike_obj)
res_parse = tree.getroot().tag
P.append( clock()-te)
te = clock()
for i in xrange (n):
with open('xml_example.xml') as filelike_obj:
for event, elem in et.iterparse(filelike_obj, ('start', 'end')):
res_iterparse = elem.tag
break
I.append( clock()-te)
RE11 = '(?<=</)[^ >]+(?= *>)(?!.*</[^>]+>)' # with assertions # ^
te = clock()
for i in xrange (n):
with open('xml_example.xml') as f:
f.seek(-200,2)
xml_text = f.read()
xml_text_noc = re.sub('(<!--.*?-->|[\n\r\t])','', xml_text,flags=re.DOTALL)
m = re.search(RE11, xml_text_noc,re.DOTALL)
res_eyq11 = m.group() if m else "FAIL"
A.append( clock()-te)
RE12 = '</([^ >]+) *>(?!.*</[^>]+>)' # with group(1) # ^
te = clock()
for i in xrange (n):
with open('xml_example.xml') as f:
f.seek(-200,2)
xml_text = f.read()
xml_text_noc = re.sub('(<!--.*?-->|[\n\r\t])','', xml_text,flags=re.DOTALL)
m = re.search(RE12, xml_text_noc,re.DOTALL)
res_eyq12 = m.group(1) if m else "FAIL"
B.append( clock()-te)
RE13 = '</[^ >]+ *>(?!.*</[^>]+>)' # without group(1) # ^
te = clock()
for i in xrange (n):
with open('xml_example.xml') as f:
f.seek(-200,2)
xml_text = f.read()
xml_text_noc = re.sub('(<!--.*?-->|[\n\r\t])','', xml_text,flags=re.DOTALL)
m = re.search(RE13, xml_text_noc,re.DOTALL)
res_eyq13 = m.group()[2:-1] if m else "FAIL"
C.append( clock()-te)
RE14 = '(?<=</)[^ \n\r\t>]+(?=[ \n\r\t]*>)(?!.*</[^>]+>)' # with assertions # ^
te = clock()
for i in xrange (n):
with open('xml_example.xml') as f:
f.seek(-200,2)
xml_text = f.read()
xml_text_noc = re.sub('<!--.*?-->','', xml_text,flags=re.DOTALL)
m = re.search(RE14, xml_text_noc,re.DOTALL)
res_eyq14 = m.group() if m else "FAIL"
D.append( clock()-te)
RE15 = '</([^ \n\r\t>]+)[ \n\r\t]*>(?!.*</[^>]+>)' # with group(1) # <
te = clock()
for i in xrange (n):
with open('xml_example.xml') as f:
f.seek(-200,2)
xml_text = f.read()
xml_text_noc = re.sub('<!--.*?-->','', xml_text,flags=re.DOTALL)
m = re.search(RE15, xml_text_noc,re.DOTALL)
res_eyq15 = m.group(1) if m else "FAIL"
E.append( clock()-te)
RE16 = '</[^ \n\r\t>]+[ \n\r\t]*>(?!.*</[^>]+>)' # without group(1) # <
te = clock()
for i in xrange (n):
with open('xml_example.xml') as f:
f.seek(-200,2)
xml_text = f.read()
xml_text_noc = re.sub('<!--.*?-->','', xml_text,flags=re.DOTALL)
m = re.search(RE16, xml_text_noc,re.DOTALL)
res_eyq16 = m.group()[2:-1].rstrip() if m else "FAIL"
F.append( clock()-te)
print "*** parse: " + res_parse, ' parse'
print "*** iterparse: " + res_iterparse, ' iterparse'
print
print "*** eyquem 11: " + repr(res_eyq11)
print "*** eyquem 12: " + repr(res_eyq12)
print "*** eyquem 13: " + repr(res_eyq13)
print "*** eyquem 14: " + repr(res_eyq14)
print "*** eyquem 15: " + repr(res_eyq15)
print "*** eyquem 16: " + repr(res_eyq16)
print
print str(min(P))
print str(min(I))
print
print '\n'.join(str(u) for u in map(min,(A,B,C)))
print
print '\n'.join(str(u) for u in map(min,(D,E,F)))
结果:
length of XML text in a file : 22548
*** parse: {http://www.w3.org/XSL/Transform/1.0}stylesheet parse
*** iterparse: {http://www.w3.org/XSL/Transform/1.0}stylesheet iterparse
*** eyquem 11: 'stylesheet'
*** eyquem 12: 'stylesheet'
*** eyquem 13: 'stylesheet'
*** eyquem 14: 'stylesheet'
*** eyquem 15: 'stylesheet'
*** eyquem 16: 'stylesheet'
0.220554691169
0.172240771802
0.0273236743636
0.0266525536625
0.0265308269626
0.0246300539733
0.0241203758299
0.0238024015203
.
.
考虑到您的简单需求,Aereal,我认为您不在乎有一个带有可能字符的根的结束标签\r
\n
\t
,而不是单独的名称;因此,在我看来,最适合您的解决方案是:
def get_root_tag_from_xml_file(xml_file_path):
with open(xml_file_path) as f:
try: f.seek(-200,2)
except: f.seek(0,0)
finally: xml_text_noc = re.sub('<!--.*?-->','', f.read(), flags= re.DOTALL)
try:
return re.search('</[^>]+>(?!.*</[^>]+>)' , xml_text_noc, re.DOTALL).group()
except :
return 'FAIL'
感谢 John Machin 的专业知识,这个解决方案比我以前的解决方案做得更可靠;此外,它准确地满足了需求,正如它所表达的那样:没有解析,因此是一种更快的方法,因为它是隐含的目标。
.
John Machin,您会发现 XML 格式的一个新的棘手功能会使该解决方案无效吗?