blob: ada4e31b0e2d535c352ffbe8efbf30606f7ce23f [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
125 rc = 0
126 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
137 xpath = node.get('xpath', None)
138 patch_type = node.get('type', 'add')
139 patch_key = node.get('key', None)
140 delete_attrs(node, ['xpath', 'type', 'key'])
141
142 if xpath is None:
143 print("No XPath attribute found for patch " + str(patch_num))
144 rc = -1
145 else:
146 target = tree.find(xpath)
147
148 if target is None:
149 print("Patch " + str(patch_num) + ": Could not find XPath "
150 "target " + xpath)
151 rc = -1
152 else:
153 print("Patch " + str(patch_num) + ":")
154
155 if patch_type == "add":
156
157 print(" Adding element " + target.tag + " to " + xpath)
158
159 #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)
164 else:
165 target.append(node)
166
167 elif patch_type == "remove":
168
169 print(" Removing element " + xpath)
170 parent = target.find("..")
171 if parent is None:
172 print("Could not find parent of " + xpath +
173 " so can't remove this element")
174 rc = -1
175 else:
176 parent.remove(target)
177
178 elif patch_type == "replace":
179
180 print(" Replacing element " + xpath)
181 parent = target.find("..")
182 if parent is None:
183 print("Could not find parent of " + xpath +
184 " so can't replace this element")
185 rc = -1
186 else:
187 parent.remove(target)
188 parent.append(node)
189
190 elif patch_type == "add-child":
191
192 for child in node:
193 print(" Adding a '" + child.tag + "' child element "
194 "to " + xpath)
195 target.append(child)
196
197 elif patch_type == "replace-child":
198
199 if patch_key is not None:
200 updates = []
201 for child in node:
202 #Use the key to figure out which element to replace
203 key_element = child.find(patch_key)
204 for target_child in target:
205 for grandchild in target_child:
206 if (grandchild.tag == patch_key) and \
207 (grandchild.text == key_element.text):
208 update = {}
209 update['remove'] = target_child
210 update['add'] = child
211 updates.append(update)
212
213 for update in updates:
214 print(" Replacing a '" + update['remove'].tag +
215 "' element in path " + xpath)
216 target.remove(update['remove'])
217 target.append(update['add'])
218
219 else:
220 print("Patch type is replace-child, but 'key' "
221 "attribute isn't set")
222 rc = -1
223
224 else:
225 print("Unknown patch type attribute found: " + patch_type)
226 rc = -1
227
228 patch_num = patch_num + 1
229
230 tree.write(args.output_xml)
231 sys.exit(rc)