在xml.etree.ElementTree中合并元素
我有一份很大的测试数据,像下面提供的那样(是的,我对这些数据没有控制权)。每一行实际上分成6个部分,我需要根据这些数据生成一个XML文件。
Nav;Basic;Dest;Smoke;No;Yes;
Nav;Dest;Recent;Regg;No;Yes;
Nav;Dest;Favourites;Regg;No;Yes;
...
Nav;Dest using on board;By POI;Smoke;No;Yes;
Nav;Dest using on board;Other;Regg;No;Yes;
每行的前3个元素表示一个“测试套件”(test suites)的XML元素,最后3个元素则应该创建一个“测试用例”(test case)的XML元素。
我已经成功地用以下代码将其转换成XML:
# testsuite (root)
testsuite = ET.Element('testsuite')
testsuite.set("name", "Tests")
def _create_testcase_tag(elem):
global testsuite
level1, level2, level3, elem4, elem5, elem6 = elem
# -- testsuite (level1)
testsuite_level1 = ET.SubElement(testsuite, "testsuite")
testsuite_level1.set("name", level1)
# -- testsuite (level2)
testsuite_level2 = ET.SubElement(testsuite_level1, "testsuite")
testsuite_level2.set("name", level2)
# -- testsuite (level3)
testsuite_level2 = ET.SubElement(testsuite_level2, "testsuite")
testsuite_level2.set("name", level3)
# -- testcase
testcase = ET.SubElement(testsuite_level2, "testcase")
testcase.set("name", "TBD")
summary = ET.SubElement(testcase, "summary")
summary.text = "Test Type= %s, Automated= %s, Available=%s" %(elem4, elem5, elem6)
with open(input_file) as in_file:
for line_number, a_line in enumerate(in_file):
try:
parameters = a_line.split(';')
if len(parameters) >= 6:
level1 = parameters[0].strip()
level2 = parameters[1].strip()
level3 = parameters[2].strip()
elem4 = parameters[3].strip()
elem5 = parameters[4].strip()
elem6 = parameters[5].strip()
lines_as_list.append((level1, level2, level3, elem4, elem5, elem6))
except ValueError:
pass
lines_as_list.sort()
for elem in lines_as_list:
_create_testcase_tag(elem)
output_xml = ET.ElementTree(testsuite)
ET.ElementTree.write(output_xml, output_file, xml_declaration=True, encoding="UTF-8")
上面的代码生成了这样的XML:
<testsuite name="Tests">
<testsuite name="Nav">
<testsuite name="Basic navigation">
<testsuite name="Set destination">
<testcase name="TBD">
<summary>Test Type= Smoke test Automated= No, Available=Yes</summary>
</testcase>
</testsuite>
</testsuite>
</testsuite>
<testsuite name="Nav">
<testsuite name="Set destination">
<testsuite name="Recent">
<testcase name="TBD">
<summary>
Test Type= Reggression test Automated= No, Available=Yes
</summary>
</testcase>
</testsuite>
</testsuite>
</testsuite>
</testsuite>
...
这一切都是正确的,但正如你所看到的,我为每一行创建了一个完整的树结构,这并不是我想要的。我需要把所有同名的testsuite
合并成一个testsuite
,并且要递归地进行处理。所以,XML应该看起来像这样:
<testsuite name="Tests">
<testsuite name="Nav">
<testsuite name="Basic navigation">
<testsuite name="Set destination">
<testcase name="TBD">
<summary>Test Type= Smoke test Automated= No, Available=Yes</summary>
</testcase>
</testsuite>
<testsuite name="Recent">
<testcase name="TBD">
<summary>
Test Type= Reggression test Automated= No, Available=Yes
</summary>
</testcase>
</testsuite>
</testsuite>
</testsuite>
</testsuite>
我希望你能理解我的意思,level1、level2和level3应该是唯一的,并且里面要有测试用例。
我该怎么做呢?请不要建议使用任何外部库!我不能在客户现场安装新库。xml.etree.ElementTree
是我唯一能用的。
谢谢
1 个回答
1
先把你的数据按照前三个字段进行排序,然后用 itertools.groupby()
来整理数据,这样在添加到 XML 之前就能按照你想要的方式组织好。 这假设了你示例中展示的嵌套层级:
from operator import itemgetter
from itertools import groupby
names = itemgetter(slice(3))
test_items = itemgetter(slice(3,None))
level1 = itemgetter(0)
level2 = itemgetter(1)
level3 = itemgetter(2)
s = """Nav;Basic;Dest;Smoke;No;Yes;
Nav;Dest;Recent;Regg;No;Yes;
Nav;Dest;Favourites;Regg;No;Yes;
Nav;Dest using on board;By POI;Smoke;No;Yes;
Nav;Dest;Recent;Regg;Yes;Yes;
Nav;Dest using on board;Other;Regg;No;Yes;
Nav;Basic;Dest;Smoke;Yes;Yes;
Nav;Basic;Dest;Smoke;Yes;No;
"""
data = list()
for line in s.splitlines():
data.append(line.rstrip(';').split(';'))
data.sort(key = names)
##for thing in data:
## print thing
testsuite = ET.Element('testsuite')
testsuite.set("name", "Tests")
for key1, group1 in groupby(data, level1):
print '***', key1
testsuite_level1 = ET.SubElement(testsuite, "testsuite")
testsuite_level1.set("name", key1)
for key2, group2 in groupby(group1, level2):
print '******', key2
testsuite_level2 = ET.SubElement(testsuite_level1, "testsuite")
testsuite_level2.set("name", key2)
for key3, group3 in groupby(group2, level3):
print '*********', key3
testsuite_level3 = ET.SubElement(testsuite_level2, "testsuite")
testsuite_level3.set("name", key3)
testcase = ET.SubElement(testsuite_level3, "testcase")
testcase.set("name", "TBD")
for element in group3:
print ' ', test_items(element)
summary = ET.SubElement(testcase, "summary")
summary.text = "Test Type= {}, Automated= {}, Available={}".format(*test_items(element))
>>>
*** Nav
****** Basic
********* Dest
['Smoke', 'No', 'Yes']
['Smoke', 'Yes', 'Yes']
['Smoke', 'Yes', 'No']
****** Dest
********* Favourites
['Regg', 'No', 'Yes']
********* Recent
['Regg', 'No', 'Yes']
['Regg', 'Yes', 'Yes']
****** Dest using on board
********* By POI
['Smoke', 'No', 'Yes']
********* Other
['Regg', 'No', 'Yes']
>>>
针对评论中提到的 proper XML solution
,我想出了这个方案——也许这正是你想要的。我不得不使用独特的标签,而不是每个标签都用 testsuite
。这样做的好处是可以实时创建树形结构 on-the-fly:
def _create_testcase_tag(testsuite, elem):
"""Add elem to testsuite.
testsuite --> xml.etree.ElementTree
elem --> list
return xml.etree.ElementTree
"""
level1, level2, level3, elem4, elem5, elem6 = elem
level1 = level1.replace(' ','_')
level2 = level2.replace(' ','_')
level3 = level3.replace(' ','_')
# -- testsuite (level1)
testsuite_level1 = testsuite.find(level1)
if not testsuite_level1:
testsuite_level1 = ET.SubElement(testsuite, level1)
# -- testsuite (level2)
testsuite_level2 = testsuite_level1.find(level2)
if not testsuite_level2:
testsuite_level2 = ET.SubElement(testsuite_level1, level2)
# -- testsuite (level3)
testsuite_level3 = testsuite_level2.find(level3)
if not testsuite_level3:
testsuite_level3 = ET.SubElement(testsuite_level2, level3)
# -- testcase
testcase = ET.SubElement(testsuite_level3, "testcase")
testcase.set("name", "TBD")
summary = ET.SubElement(testcase, "summary")
summary.text = "Test Type= {}, Automated= {}, Available={}".format(elem4, elem5, elem6)
return testsuite
# testsuite (root)
testsuite = ET.Element('testsuite')
testsuite.set("name", "Tests")
with open(input_file) as in_file:
for line in in_file:
line = line.strip().rstrip(';').split(';')
testsuite = _create_testcase_tag(testsuite, line)