blob: 0deb5d96be722f2b665138d1342b99bbbc15fad8 [file] [log] [blame]
Matt Spinlerab015d72016-09-06 15:33:17 -05001#!/usr/bin/env python
2
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
101from lxml import etree
102import sys
103import argparse
104
105
106def delete_attrs(element, attrs):
107 for a in attrs:
108 try:
109 del element.attrib[a]
110 except:
111 pass
112
113if __name__ == '__main__':
114
115 parser = argparse.ArgumentParser("Applies fixes to XML files")
116 parser.add_argument("-x", dest='xml', help='The input XML file')
117 parser.add_argument("-p", dest='patch_xml', help='The patch XML file')
118 parser.add_argument("-o", dest='output_xml', help='The output XML file')
119 args = parser.parse_args()
120
121 if not all([args.xml, args.patch_xml, args.output_xml]):
122 parser.print_usage()
123 sys.exit(-1)
124
Matt Spinlercb99d1e2016-09-13 14:47:25 -0500125 errors = []
Matt Spinlerab015d72016-09-06 15:33:17 -0500126 patch_num = 0
127 patch_tree = etree.parse(args.patch_xml)
128 patch_root = patch_tree.getroot()
129 tree = etree.parse(args.xml)
130 root = tree.getroot()
131
132 for node in patch_root:
133 if (node.tag is etree.PI) or (node.tag is etree.Comment) or \
134 (node.tag == "targetFile"):
135 continue
136
Matt Spinlercb99d1e2016-09-13 14:47:25 -0500137 patch_num = patch_num + 1
138
Matt Spinlerab015d72016-09-06 15:33:17 -0500139 xpath = node.get('xpath', None)
140 patch_type = node.get('type', 'add')
141 patch_key = node.get('key', None)
142 delete_attrs(node, ['xpath', 'type', 'key'])
143
Matt Spinlercb99d1e2016-09-13 14:47:25 -0500144 print("Patch " + str(patch_num) + ":")
145
146 try:
147 if xpath is None:
148 raise Exception(" E> No XPath attribute found")
149
Matt Spinlerab015d72016-09-06 15:33:17 -0500150 target = tree.find(xpath)
151
152 if target is None:
Matt Spinlercb99d1e2016-09-13 14:47:25 -0500153 raise Exception(" E> Could not find XPath target " + xpath)
Matt Spinlerab015d72016-09-06 15:33:17 -0500154
Matt Spinlercb99d1e2016-09-13 14:47:25 -0500155 if patch_type == "add":
Matt Spinlerab015d72016-09-06 15:33:17 -0500156
Matt Spinlercb99d1e2016-09-13 14:47:25 -0500157 print(" Adding element " + target.tag + " to " + xpath)
Matt Spinlerab015d72016-09-06 15:33:17 -0500158
Matt Spinlercb99d1e2016-09-13 14:47:25 -0500159 #The ServerWiz API is dependent on ordering for the
160 #elements at the root node, so make sure they get appended
161 #at the end.
162 if (xpath == "./") or (xpath == "/"):
163 root.append(node)
Matt Spinlerab015d72016-09-06 15:33:17 -0500164 else:
Matt Spinlercb99d1e2016-09-13 14:47:25 -0500165 target.append(node)
Matt Spinlerab015d72016-09-06 15:33:17 -0500166
Matt Spinlercb99d1e2016-09-13 14:47:25 -0500167 elif patch_type == "remove":
168
169 print(" Removing element " + xpath)
170 parent = target.find("..")
171 if parent is None:
172 raise Exception(" E> Could not find parent of " + xpath +
173 " so can't remove this element")
174 parent.remove(target)
175
176 elif patch_type == "replace":
177
178 print(" Replacing element " + xpath)
179 parent = target.find("..")
180 if parent is None:
181 raise Exception(" E> Could not find parent of " + xpath +
182 " so can't replace this element")
183 parent.remove(target)
184 parent.append(node)
185
186 elif patch_type == "add-child":
187
188 for child in node:
189 print(" Adding a '" + child.tag + "' child element"
190 " to " + xpath)
191 target.append(child)
192
193 elif patch_type == "replace-child":
194
195 if patch_key is None:
196 raise Exception(" E> Patch type is replace-child, but"
197 " 'key' attribute isn't set")
198 updates = []
199 for child in node:
200 #Use the key to figure out which element to replace
201 key_element = child.find(patch_key)
202 for target_child in target:
203 for grandchild in target_child:
204 if (grandchild.tag == patch_key) and \
205 (grandchild.text == key_element.text):
206 update = {}
207 update['remove'] = target_child
208 update['add'] = child
209 updates.append(update)
210
211 for update in updates:
212 print(" Replacing a '" + update['remove'].tag +
213 "' element in path " + xpath)
214 target.remove(update['remove'])
215 target.append(update['add'])
216
217 else:
218 raise Exception(" E> Unknown patch type attribute found: " +
219 patch_type)
220
221 except Exception as e:
222 print e
223 errors.append(e)
Matt Spinlerab015d72016-09-06 15:33:17 -0500224
225 tree.write(args.output_xml)
Matt Spinlercb99d1e2016-09-13 14:47:25 -0500226
227 if errors:
228 print("Exiting with " + str(len(errors)) + " total errors")
229 sys.exit(-1)