Skip to content

Commit b97d874

Browse files
authored
Adding a script to test for xml attribute coverage (#1102)
1 parent 84fc35b commit b97d874

File tree

2 files changed

+176
-1
lines changed

2 files changed

+176
-1
lines changed
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
2+
from lxml import etree as ElementTree
3+
import os
4+
from pathlib import Path
5+
import argparse
6+
7+
8+
def parse_schema_element(root,
9+
node,
10+
xsd='{http://www.w3.org/2001/XMLSchema}',
11+
recursive_types=['PeriodicEvent', 'SoloEvent', 'HaltEvent'],
12+
folders=['src', 'examples']):
13+
"""Parse the xml schema at the current level
14+
15+
@arg root the root schema node
16+
@arg node current schema node
17+
@arg xsd the file namespace
18+
@arg recursive_types node tags that allow recursive nesting
19+
@arg folders folders to sort xml attribute usage into
20+
"""
21+
22+
element_type = node.get('type')
23+
element_name = node.get('name')
24+
element_def = root.find("%scomplexType[@name='%s']" % (xsd, element_type))
25+
local_types = {'attributes': {}, 'children': {}}
26+
27+
# Parse attributes
28+
for attribute in element_def.findall('%sattribute' % (xsd)):
29+
attribute_name = attribute.get('name')
30+
local_types['attributes'][attribute_name] = {ka: [] for ka in folders}
31+
if ('default' in attribute.attrib):
32+
local_types['attributes'][attribute_name]['default'] = attribute.get('default')
33+
34+
# Parse children
35+
choice_node = element_def.findall('%schoice' % (xsd))
36+
if choice_node:
37+
for child in choice_node[0].findall('%selement' % (xsd)):
38+
child_name = child.get('name')
39+
if not ((child_name in recursive_types) and (element_name in recursive_types)):
40+
local_types['children'][child_name] = parse_schema_element(root, child)
41+
42+
return local_types
43+
44+
45+
def parse_schema(fname):
46+
"""Parse the schema file into the xml attribute usage dict
47+
48+
@arg fname schema name
49+
"""
50+
xml_tree = ElementTree.parse(fname)
51+
xml_root = xml_tree.getroot()
52+
problem_node = xml_root.find("{http://www.w3.org/2001/XMLSchema}element")
53+
return {'Problem': parse_schema_element(xml_root, problem_node)}
54+
55+
56+
def collect_xml_attributes_level(local_types, node, folder):
57+
"""Collect xml attribute usage at the current level
58+
59+
@arg local_types dict containing attribute usage
60+
@arg node current xml node
61+
@arg folder the source folder for the current file
62+
"""
63+
for ka in node.attrib.keys():
64+
local_types['attributes'][ka][folder].append(node.get(ka))
65+
66+
for child in node:
67+
if child.tag in local_types['children']:
68+
collect_xml_attributes_level(local_types['children'][child.tag],
69+
child,
70+
folder)
71+
72+
73+
def collect_xml_attributes(xml_types, fname, folder):
74+
"""Collect xml attribute usage in a file
75+
76+
@arg xml_types dict containing attribute usage
77+
@arg fname name of the target file
78+
@arg folder the source folder for the current file
79+
"""
80+
parser = ElementTree.XMLParser(remove_comments=True, remove_blank_text=True)
81+
xml_tree = ElementTree.parse(fname, parser=parser)
82+
xml_root = xml_tree.getroot()
83+
84+
collect_xml_attributes_level(xml_types['Problem'], xml_root, folder)
85+
86+
87+
def write_attribute_usage_xml_level(local_types, node, folders=['src', 'examples']):
88+
"""Write xml attribute usage file at a given level
89+
90+
@arg local_types dict containing attribute usage at the current level
91+
@arg node current xml node
92+
"""
93+
94+
# Write attributes
95+
for ka in local_types['attributes'].keys():
96+
attribute_node = ElementTree.Element(ka)
97+
node.append(attribute_node)
98+
99+
if ('default' in local_types['attributes'][ka]):
100+
attribute_node.set('default', local_types['attributes'][ka]['default'])
101+
102+
unique_values = []
103+
for f in folders:
104+
sub_values = list(set(local_types['attributes'][ka][f]))
105+
unique_values.extend(sub_values)
106+
attribute_node.set(f, ' | '.join(sub_values))
107+
108+
unique_length = len(set(unique_values))
109+
attribute_node.set('unique_values', str(unique_length))
110+
111+
# Write children
112+
for ka in sorted(local_types['children']):
113+
child = ElementTree.Element(ka)
114+
node.append(child)
115+
write_attribute_usage_xml_level(local_types['children'][ka], child)
116+
117+
118+
def write_attribute_usage_xml(xml_types, fname):
119+
"""Write xml attribute usage file
120+
121+
@arg xml_types dict containing attribute usage by xml type
122+
@arg fname output file name
123+
"""
124+
xml_root = ElementTree.Element('Problem')
125+
xml_tree = ElementTree.ElementTree(xml_root)
126+
127+
write_attribute_usage_xml_level(xml_types['Problem'], xml_root)
128+
xml_tree.write(fname, pretty_print=True)
129+
130+
131+
def process_xml_files(geosx_root, output_name):
132+
"""Test for xml attribute usage
133+
134+
@arg geosx_root GEOSX root directory
135+
@arg output_name output file name
136+
"""
137+
138+
# Parse the schema
139+
geosx_root = os.path.expanduser(geosx_root)
140+
schema = '%ssrc/coreComponents/schema/schema.xsd' % (geosx_root)
141+
xml_types = parse_schema(schema)
142+
143+
# Find all xml files, collect their attributes
144+
for folder in ['src', 'examples']:
145+
print(folder)
146+
xml_files = Path(os.path.join(geosx_root, folder)).rglob('*.xml')
147+
for f in xml_files:
148+
print(' %s' % (str(f)))
149+
collect_xml_attributes(xml_types, str(f), folder)
150+
151+
# Consolidate attributes
152+
write_attribute_usage_xml(xml_types, output_name)
153+
154+
155+
def main():
156+
"""Entry point for the xml attribute usage test script
157+
158+
@arg -r/--root GEOSX root directory
159+
@arg -o/--output output file name
160+
"""
161+
162+
# Parse the user arguments
163+
parser = argparse.ArgumentParser()
164+
parser.add_argument('-r', '--root', type=str, help='GEOSX root', default='')
165+
parser.add_argument('-o', '--output', type=str, help='Output file name', default='attribute_test.xml')
166+
args = parser.parse_args()
167+
168+
# Parse the xml files
169+
process_xml_files(args.root, args.output)
170+
171+
172+
if __name__ == "__main__":
173+
main()
174+

geosx_xml_tools_package/setup.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
packages=['geosx_xml_tools', 'geosx_xml_tools.tests'],
99
entry_points={'console_scripts': ['preprocess_xml = geosx_xml_tools.__main__:main',
1010
'format_xml = geosx_xml_tools.xml_formatter:main',
11-
'test_geosx_xml_tools = geosx_xml_tools.tests.test_manager:run_unit_tests']},
11+
'test_geosx_xml_tools = geosx_xml_tools.tests.test_manager:run_unit_tests',
12+
'test_attribute_coverage = geosx_xml_tools.attribute_coverage:main']},
1213
install_requires=['lxml>=4.5.0'])
1314

0 commit comments

Comments
 (0)