blob: e522d2cc34cb37605d069161963f68b0340ced32 [file] [log] [blame]
Patrick Williams721dcbd2020-04-08 07:32:57 -05001#!/usr/bin/env python3
Matt Spinlerab015d72016-09-06 15:33:17 -05002
3"""
4This script applies patches to an XML file.
5
6The patch file is itself an XML file. It can have any root element name,
7and uses XML attributes to specify if the elements in the file should replace
8existing elements or add new ones. An XPath attribute is used to specify
9where the fix should be applied. A <targetFile> element is required in the
10patch file to specify the base name of the XML file the patches should be
11applied to, though the targetFile element is handled outside of this script.
12
13The only restriction is that since the type, xpath, and key attributes are
14used to specify the patch placement the target XML cannot use those at a
15top level element.
16
17 It can apply patches in 5 ways:
18
19 1) Add an element:
20 Put in the element to add, along with the type='add' attribute
21 and an xpath attribute specifying where the new element should go.
22
23 <enumerationType type='add' xpath="./">
24 <id>MY_TYPE</id>
25 </enumerationType>
26
27 This will add a new enumerationType element child to the root element.
28
29 2) Replace an element:
30 Put in the new element, with the type='replace' attribute
31 and the XPath of the element you want to replace.
32
33 <enumerator type='replace'
34 xpath="enumerationType/[id='TYPE']/enumerator[name='XBUS']">
35 <name>XBUS</name>
36 <value>the new XBUS value</value>
37 </enumerator>
38
39 This will replace the enumerator element with name XBUS under the
40 enumerationType element with ID TYPE.
41
42 3) Remove an element:
43 Put in the element to remove, with the type='remove' attribute and
44 the XPath of the element you want to remove. The full element contents
45 don't need to be specified, as the XPath is what locates the element.
46
47 <enumerator type='remove'
48 xpath='enumerationType[id='TYPE]/enumerator[name='DIMM']>
49 </enumerator>
50
51 This will remove the enumerator element with name DIMM under the
52 enumerationType element with ID TYPE.
53
54 4) Add child elements to a specific element. Useful when adding several
55 child elements at once.
56
57 Use a type attribute of 'add-child' and specify the target parent with
58 the xpath attribute.
59
60 <enumerationType type="add-child" xpath="enumerationType/[id='TYPE']">
61 <enumerator>
62 <name>MY_NEW_ENUMERATOR</name>
63 <value>23</value>
64 </enumerator>
65 <enumerator>
66 <name>ANOTHER_NEW_ENUMERATOR</name>
67 <value>99</value>
68 </enumerator>
69 </enumerationType>
70
71 This will add 2 new <enumerator> elements to the enumerationType
72 element with ID TYPE.
73
74 5) Replace a child element inside another element, useful when replacing
75 several child elements of the same parent at once.
76
77 Use a type attribute of 'replace-child' and the xpath attribute
78 as described above, and also use the key attribute to specify which
79 element should be used to match on so the replace can be done.
80
81 <enumerationType type="replace-child"
82 key="name"
83 xpath="enumerationType/[id='TYPE']">
84 <enumerator>
85 <name>OLD_ENUMERATOR</name>
86 <value>newvalue</value>
87 </enumerator>
88 <enumerator>
89 <name>ANOTHER_OLD_ENUMERATOR</name>
90 <value>anothernewvalue</value>
91 </enumerator>
92 </enumerationType>
93
94 This will replace the <enumerator> elements with the names of
95 OLD_ENUMERATOR and ANOTHER_OLD_ENUMERATOR with the <enumerator>
96 elements specified, inside of the enumerationType element with
97 ID TYPE.
98"""
99
100
Matt Spinlerab015d72016-09-06 15:33:17 -0500101import argparse
Patrick Williams66367a12022-12-05 09:58:37 -0600102import sys
103
104from lxml import etree
Matt Spinlerab015d72016-09-06 15:33:17 -0500105
106
107def delete_attrs(element, attrs):
108 for a in attrs:
109 try:
110 del element.attrib[a]
Patrick Williams66367a12022-12-05 09:58:37 -0600111 except Exception:
Matt Spinlerab015d72016-09-06 15:33:17 -0500112 pass
113
Matt Spinlerab015d72016-09-06 15:33:17 -0500114
Patrick Williams66367a12022-12-05 09:58:37 -0600115if __name__ == "__main__":
Matt Spinlerab015d72016-09-06 15:33:17 -0500116 parser = argparse.ArgumentParser("Applies fixes to XML files")
Patrick Williams66367a12022-12-05 09:58:37 -0600117 parser.add_argument("-x", dest="xml", help="The input XML file")
118 parser.add_argument("-p", dest="patch_xml", help="The patch XML file")
119 parser.add_argument("-o", dest="output_xml", help="The output XML file")
Matt Spinlerab015d72016-09-06 15:33:17 -0500120 args = parser.parse_args()
121
122 if not all([args.xml, args.patch_xml, args.output_xml]):
123 parser.print_usage()
124 sys.exit(-1)
125
Matt Spinlercb99d1e2016-09-13 14:47:25 -0500126 errors = []
Matt Spinlerab015d72016-09-06 15:33:17 -0500127 patch_num = 0
128 patch_tree = etree.parse(args.patch_xml)
129 patch_root = patch_tree.getroot()
130 tree = etree.parse(args.xml)
131 root = tree.getroot()
132
133 for node in patch_root:
Patrick Williams66367a12022-12-05 09:58:37 -0600134 if (
135 (node.tag is etree.PI)
136 or (node.tag is etree.Comment)
137 or (node.tag == "targetFile")
138 ):
Matt Spinlerab015d72016-09-06 15:33:17 -0500139 continue
140
Matt Spinlercb99d1e2016-09-13 14:47:25 -0500141 patch_num = patch_num + 1
142
Patrick Williams66367a12022-12-05 09:58:37 -0600143 xpath = node.get("xpath", None)
144 patch_type = node.get("type", "add")
145 patch_key = node.get("key", None)
146 delete_attrs(node, ["xpath", "type", "key"])
Matt Spinlerab015d72016-09-06 15:33:17 -0500147
Matt Spinlercb99d1e2016-09-13 14:47:25 -0500148 print("Patch " + str(patch_num) + ":")
149
150 try:
151 if xpath is None:
152 raise Exception(" E> No XPath attribute found")
153
Matt Spinlerab015d72016-09-06 15:33:17 -0500154 target = tree.find(xpath)
155
156 if target is None:
Matt Spinlercb99d1e2016-09-13 14:47:25 -0500157 raise Exception(" E> Could not find XPath target " + xpath)
Matt Spinlerab015d72016-09-06 15:33:17 -0500158
Matt Spinlercb99d1e2016-09-13 14:47:25 -0500159 if patch_type == "add":
Matt Spinlercb99d1e2016-09-13 14:47:25 -0500160 print(" Adding element " + target.tag + " to " + xpath)
Matt Spinlerab015d72016-09-06 15:33:17 -0500161
Patrick Williams66367a12022-12-05 09:58:37 -0600162 # The ServerWiz API is dependent on ordering for the
163 # elements at the root node, so make sure they get appended
164 # at the end.
Matt Spinlercb99d1e2016-09-13 14:47:25 -0500165 if (xpath == "./") or (xpath == "/"):
166 root.append(node)
Matt Spinlerab015d72016-09-06 15:33:17 -0500167 else:
Matt Spinlercb99d1e2016-09-13 14:47:25 -0500168 target.append(node)
Matt Spinlerab015d72016-09-06 15:33:17 -0500169
Matt Spinlercb99d1e2016-09-13 14:47:25 -0500170 elif patch_type == "remove":
Matt Spinlercb99d1e2016-09-13 14:47:25 -0500171 print(" Removing element " + xpath)
172 parent = target.find("..")
173 if parent is None:
Patrick Williams66367a12022-12-05 09:58:37 -0600174 raise Exception(
175 " E> Could not find parent of "
176 + xpath
177 + " so can't remove this element"
178 )
Matt Spinlercb99d1e2016-09-13 14:47:25 -0500179 parent.remove(target)
180
181 elif patch_type == "replace":
Matt Spinlercb99d1e2016-09-13 14:47:25 -0500182 print(" Replacing element " + xpath)
183 parent = target.find("..")
184 if parent is None:
Patrick Williams66367a12022-12-05 09:58:37 -0600185 raise Exception(
186 " E> Could not find parent of "
187 + xpath
188 + " so can't replace this element"
189 )
Matt Spinlercb99d1e2016-09-13 14:47:25 -0500190 parent.remove(target)
191 parent.append(node)
192
193 elif patch_type == "add-child":
Matt Spinlercb99d1e2016-09-13 14:47:25 -0500194 for child in node:
Patrick Williams66367a12022-12-05 09:58:37 -0600195 print(
196 " Adding a '"
197 + child.tag
198 + "' child element to "
199 + xpath
200 )
Matt Spinlercb99d1e2016-09-13 14:47:25 -0500201 target.append(child)
202
203 elif patch_type == "replace-child":
Matt Spinlercb99d1e2016-09-13 14:47:25 -0500204 if patch_key is None:
Patrick Williams66367a12022-12-05 09:58:37 -0600205 raise Exception(
206 " E> Patch type is replace-child, but"
207 " 'key' attribute isn't set"
208 )
Matt Spinlercb99d1e2016-09-13 14:47:25 -0500209 updates = []
210 for child in node:
Patrick Williams66367a12022-12-05 09:58:37 -0600211 # Use the key to figure out which element to replace
Matt Spinlercb99d1e2016-09-13 14:47:25 -0500212 key_element = child.find(patch_key)
213 for target_child in target:
214 for grandchild in target_child:
Patrick Williams66367a12022-12-05 09:58:37 -0600215 if (grandchild.tag == patch_key) and (
216 grandchild.text == key_element.text
217 ):
Matt Spinlercb99d1e2016-09-13 14:47:25 -0500218 update = {}
Patrick Williams66367a12022-12-05 09:58:37 -0600219 update["remove"] = target_child
220 update["add"] = child
Matt Spinlercb99d1e2016-09-13 14:47:25 -0500221 updates.append(update)
222
223 for update in updates:
Patrick Williams66367a12022-12-05 09:58:37 -0600224 print(
225 " Replacing a '"
226 + update["remove"].tag
227 + "' element in path "
228 + xpath
229 )
230 target.remove(update["remove"])
231 target.append(update["add"])
Matt Spinlercb99d1e2016-09-13 14:47:25 -0500232
233 else:
Patrick Williams66367a12022-12-05 09:58:37 -0600234 raise Exception(
235 " E> Unknown patch type attribute found: " + patch_type
236 )
Matt Spinlercb99d1e2016-09-13 14:47:25 -0500237
238 except Exception as e:
Patrick Williams721dcbd2020-04-08 07:32:57 -0500239 print(e)
Matt Spinlercb99d1e2016-09-13 14:47:25 -0500240 errors.append(e)
Matt Spinlerab015d72016-09-06 15:33:17 -0500241
242 tree.write(args.output_xml)
Matt Spinlercb99d1e2016-09-13 14:47:25 -0500243
244 if errors:
245 print("Exiting with " + str(len(errors)) + " total errors")
246 sys.exit(-1)