xref: /openbmc/phosphor-mrw-tools/patchxml.py (revision 66367a15a9349214b682f94a8deb3ad7f9bfc07f)
1721dcbd2SPatrick Williams#!/usr/bin/env python3
2ab015d7eSMatt Spinler
3ab015d7eSMatt Spinler"""
4ab015d7eSMatt SpinlerThis script applies patches to an XML file.
5ab015d7eSMatt Spinler
6ab015d7eSMatt SpinlerThe patch file is itself an XML file.  It can have any root element name,
7ab015d7eSMatt Spinlerand uses XML attributes to specify if the elements in the file should replace
8ab015d7eSMatt Spinlerexisting elements or add new ones.  An XPath attribute is used to specify
9ab015d7eSMatt Spinlerwhere the fix should be applied.  A <targetFile> element is required in the
10ab015d7eSMatt Spinlerpatch file to specify the base name of the XML file the patches should be
11ab015d7eSMatt Spinlerapplied to, though the targetFile element is handled outside of this script.
12ab015d7eSMatt Spinler
13ab015d7eSMatt SpinlerThe only restriction is that since the type, xpath, and key attributes are
14ab015d7eSMatt Spinlerused to specify the patch placement the target XML cannot use those at a
15ab015d7eSMatt Spinlertop level element.
16ab015d7eSMatt Spinler
17ab015d7eSMatt Spinler It can apply patches in 5 ways:
18ab015d7eSMatt Spinler
19ab015d7eSMatt Spinler 1) Add an element:
20ab015d7eSMatt Spinler    Put in the element to add, along with the type='add' attribute
21ab015d7eSMatt Spinler    and an xpath attribute specifying where the new element should go.
22ab015d7eSMatt Spinler
23ab015d7eSMatt Spinler     <enumerationType type='add' xpath="./">
24ab015d7eSMatt Spinler       <id>MY_TYPE</id>
25ab015d7eSMatt Spinler     </enumerationType>
26ab015d7eSMatt Spinler
27ab015d7eSMatt Spinler     This will add a new enumerationType element child to the root element.
28ab015d7eSMatt Spinler
29ab015d7eSMatt Spinler 2) Replace an element:
30ab015d7eSMatt Spinler    Put in the new element, with the type='replace' attribute
31ab015d7eSMatt Spinler    and the XPath of the element you want to replace.
32ab015d7eSMatt Spinler
33ab015d7eSMatt Spinler     <enumerator type='replace'
34ab015d7eSMatt Spinler               xpath="enumerationType/[id='TYPE']/enumerator[name='XBUS']">
35ab015d7eSMatt Spinler       <name>XBUS</name>
36ab015d7eSMatt Spinler       <value>the new XBUS value</value>
37ab015d7eSMatt Spinler     </enumerator>
38ab015d7eSMatt Spinler
39ab015d7eSMatt Spinler    This will replace the enumerator element with name XBUS under the
40ab015d7eSMatt Spinler    enumerationType element with ID TYPE.
41ab015d7eSMatt Spinler
42ab015d7eSMatt Spinler 3) Remove an element:
43ab015d7eSMatt Spinler    Put in the element to remove, with the type='remove' attribute and
44ab015d7eSMatt Spinler    the XPath of the element you want to remove. The full element contents
45ab015d7eSMatt Spinler    don't need to be specified, as the XPath is what locates the element.
46ab015d7eSMatt Spinler
47ab015d7eSMatt Spinler    <enumerator type='remove'
48ab015d7eSMatt Spinler                xpath='enumerationType[id='TYPE]/enumerator[name='DIMM']>
49ab015d7eSMatt Spinler    </enumerator>
50ab015d7eSMatt Spinler
51ab015d7eSMatt Spinler    This will remove the enumerator element with name DIMM under the
52ab015d7eSMatt Spinler    enumerationType element with ID TYPE.
53ab015d7eSMatt Spinler
54ab015d7eSMatt Spinler 4) Add child elements to a specific element.  Useful when adding several
55ab015d7eSMatt Spinler    child elements at once.
56ab015d7eSMatt Spinler
57ab015d7eSMatt Spinler    Use a type attribute of 'add-child' and specify the target parent with
58ab015d7eSMatt Spinler    the xpath attribute.
59ab015d7eSMatt Spinler
60ab015d7eSMatt Spinler     <enumerationType type="add-child" xpath="enumerationType/[id='TYPE']">
61ab015d7eSMatt Spinler       <enumerator>
62ab015d7eSMatt Spinler         <name>MY_NEW_ENUMERATOR</name>
63ab015d7eSMatt Spinler         <value>23</value>
64ab015d7eSMatt Spinler       </enumerator>
65ab015d7eSMatt Spinler       <enumerator>
66ab015d7eSMatt Spinler         <name>ANOTHER_NEW_ENUMERATOR</name>
67ab015d7eSMatt Spinler         <value>99</value>
68ab015d7eSMatt Spinler       </enumerator>
69ab015d7eSMatt Spinler     </enumerationType>
70ab015d7eSMatt Spinler
71ab015d7eSMatt Spinler     This will add 2 new <enumerator> elements to the enumerationType
72ab015d7eSMatt Spinler     element with ID TYPE.
73ab015d7eSMatt Spinler
74ab015d7eSMatt Spinler 5) Replace a child element inside another element, useful when replacing
75ab015d7eSMatt Spinler    several child elements of the same parent at once.
76ab015d7eSMatt Spinler
77ab015d7eSMatt Spinler    Use a type attribute of 'replace-child' and the xpath attribute
78ab015d7eSMatt Spinler    as described above, and also use the key attribute to specify which
79ab015d7eSMatt Spinler    element should be used to match on so the replace can be done.
80ab015d7eSMatt Spinler
81ab015d7eSMatt Spinler     <enumerationType type="replace-child"
82ab015d7eSMatt Spinler                      key="name"
83ab015d7eSMatt Spinler                      xpath="enumerationType/[id='TYPE']">
84ab015d7eSMatt Spinler       <enumerator>
85ab015d7eSMatt Spinler         <name>OLD_ENUMERATOR</name>
86ab015d7eSMatt Spinler         <value>newvalue</value>
87ab015d7eSMatt Spinler       </enumerator>
88ab015d7eSMatt Spinler       <enumerator>
89ab015d7eSMatt Spinler         <name>ANOTHER_OLD_ENUMERATOR</name>
90ab015d7eSMatt Spinler         <value>anothernewvalue</value>
91ab015d7eSMatt Spinler       </enumerator>
92ab015d7eSMatt Spinler     </enumerationType>
93ab015d7eSMatt Spinler
94ab015d7eSMatt Spinler     This will replace the <enumerator> elements with the names of
95ab015d7eSMatt Spinler     OLD_ENUMERATOR and ANOTHER_OLD_ENUMERATOR with the <enumerator>
96ab015d7eSMatt Spinler     elements specified, inside of the enumerationType element with
97ab015d7eSMatt Spinler     ID TYPE.
98ab015d7eSMatt Spinler"""
99ab015d7eSMatt Spinler
100ab015d7eSMatt Spinler
101ab015d7eSMatt Spinlerimport argparse
102*66367a15SPatrick Williamsimport sys
103*66367a15SPatrick Williams
104*66367a15SPatrick Williamsfrom lxml import etree
105ab015d7eSMatt Spinler
106ab015d7eSMatt Spinler
107ab015d7eSMatt Spinlerdef delete_attrs(element, attrs):
108ab015d7eSMatt Spinler    for a in attrs:
109ab015d7eSMatt Spinler        try:
110ab015d7eSMatt Spinler            del element.attrib[a]
111*66367a15SPatrick Williams        except Exception:
112ab015d7eSMatt Spinler            pass
113ab015d7eSMatt Spinler
114ab015d7eSMatt Spinler
115*66367a15SPatrick Williamsif __name__ == "__main__":
116ab015d7eSMatt Spinler    parser = argparse.ArgumentParser("Applies fixes to XML files")
117*66367a15SPatrick Williams    parser.add_argument("-x", dest="xml", help="The input XML file")
118*66367a15SPatrick Williams    parser.add_argument("-p", dest="patch_xml", help="The patch XML file")
119*66367a15SPatrick Williams    parser.add_argument("-o", dest="output_xml", help="The output XML file")
120ab015d7eSMatt Spinler    args = parser.parse_args()
121ab015d7eSMatt Spinler
122ab015d7eSMatt Spinler    if not all([args.xml, args.patch_xml, args.output_xml]):
123ab015d7eSMatt Spinler        parser.print_usage()
124ab015d7eSMatt Spinler        sys.exit(-1)
125ab015d7eSMatt Spinler
126cb99d1eaSMatt Spinler    errors = []
127ab015d7eSMatt Spinler    patch_num = 0
128ab015d7eSMatt Spinler    patch_tree = etree.parse(args.patch_xml)
129ab015d7eSMatt Spinler    patch_root = patch_tree.getroot()
130ab015d7eSMatt Spinler    tree = etree.parse(args.xml)
131ab015d7eSMatt Spinler    root = tree.getroot()
132ab015d7eSMatt Spinler
133ab015d7eSMatt Spinler    for node in patch_root:
134*66367a15SPatrick Williams        if (
135*66367a15SPatrick Williams            (node.tag is etree.PI)
136*66367a15SPatrick Williams            or (node.tag is etree.Comment)
137*66367a15SPatrick Williams            or (node.tag == "targetFile")
138*66367a15SPatrick Williams        ):
139ab015d7eSMatt Spinler            continue
140ab015d7eSMatt Spinler
141cb99d1eaSMatt Spinler        patch_num = patch_num + 1
142cb99d1eaSMatt Spinler
143*66367a15SPatrick Williams        xpath = node.get("xpath", None)
144*66367a15SPatrick Williams        patch_type = node.get("type", "add")
145*66367a15SPatrick Williams        patch_key = node.get("key", None)
146*66367a15SPatrick Williams        delete_attrs(node, ["xpath", "type", "key"])
147ab015d7eSMatt Spinler
148cb99d1eaSMatt Spinler        print("Patch " + str(patch_num) + ":")
149cb99d1eaSMatt Spinler
150cb99d1eaSMatt Spinler        try:
151ab015d7eSMatt Spinler            if xpath is None:
152cb99d1eaSMatt Spinler                raise Exception("  E>  No XPath attribute found")
153cb99d1eaSMatt Spinler
154ab015d7eSMatt Spinler            target = tree.find(xpath)
155ab015d7eSMatt Spinler
156ab015d7eSMatt Spinler            if target is None:
157cb99d1eaSMatt Spinler                raise Exception("  E>  Could not find XPath target " + xpath)
158ab015d7eSMatt Spinler
159ab015d7eSMatt Spinler            if patch_type == "add":
160ab015d7eSMatt Spinler                print("  Adding element " + target.tag + " to " + xpath)
161ab015d7eSMatt Spinler
162ab015d7eSMatt Spinler                # The ServerWiz API is dependent on ordering for the
163ab015d7eSMatt Spinler                # elements at the root node, so make sure they get appended
164ab015d7eSMatt Spinler                # at the end.
165ab015d7eSMatt Spinler                if (xpath == "./") or (xpath == "/"):
166ab015d7eSMatt Spinler                    root.append(node)
167ab015d7eSMatt Spinler                else:
168ab015d7eSMatt Spinler                    target.append(node)
169ab015d7eSMatt Spinler
170ab015d7eSMatt Spinler            elif patch_type == "remove":
171ab015d7eSMatt Spinler                print("  Removing element " + xpath)
172ab015d7eSMatt Spinler                parent = target.find("..")
173ab015d7eSMatt Spinler                if parent is None:
174*66367a15SPatrick Williams                    raise Exception(
175*66367a15SPatrick Williams                        "  E>  Could not find parent of "
176*66367a15SPatrick Williams                        + xpath
177*66367a15SPatrick Williams                        + " so can't remove this element"
178*66367a15SPatrick Williams                    )
179ab015d7eSMatt Spinler                parent.remove(target)
180ab015d7eSMatt Spinler
181ab015d7eSMatt Spinler            elif patch_type == "replace":
182ab015d7eSMatt Spinler                print("  Replacing element " + xpath)
183ab015d7eSMatt Spinler                parent = target.find("..")
184ab015d7eSMatt Spinler                if parent is None:
185*66367a15SPatrick Williams                    raise Exception(
186*66367a15SPatrick Williams                        "  E>  Could not find parent of "
187*66367a15SPatrick Williams                        + xpath
188*66367a15SPatrick Williams                        + " so can't replace this element"
189*66367a15SPatrick Williams                    )
190ab015d7eSMatt Spinler                parent.remove(target)
191ab015d7eSMatt Spinler                parent.append(node)
192ab015d7eSMatt Spinler
193ab015d7eSMatt Spinler            elif patch_type == "add-child":
194ab015d7eSMatt Spinler                for child in node:
195*66367a15SPatrick Williams                    print(
196*66367a15SPatrick Williams                        "  Adding a '"
197*66367a15SPatrick Williams                        + child.tag
198*66367a15SPatrick Williams                        + "' child element to "
199*66367a15SPatrick Williams                        + xpath
200*66367a15SPatrick Williams                    )
201ab015d7eSMatt Spinler                    target.append(child)
202ab015d7eSMatt Spinler
203ab015d7eSMatt Spinler            elif patch_type == "replace-child":
204cb99d1eaSMatt Spinler                if patch_key is None:
205*66367a15SPatrick Williams                    raise Exception(
206*66367a15SPatrick Williams                        "  E>  Patch type is replace-child, but"
207*66367a15SPatrick Williams                        " 'key' attribute isn't set"
208*66367a15SPatrick Williams                    )
209ab015d7eSMatt Spinler                updates = []
210ab015d7eSMatt Spinler                for child in node:
211ab015d7eSMatt Spinler                    # Use the key to figure out which element to replace
212ab015d7eSMatt Spinler                    key_element = child.find(patch_key)
213ab015d7eSMatt Spinler                    for target_child in target:
214ab015d7eSMatt Spinler                        for grandchild in target_child:
215*66367a15SPatrick Williams                            if (grandchild.tag == patch_key) and (
216*66367a15SPatrick Williams                                grandchild.text == key_element.text
217*66367a15SPatrick Williams                            ):
218ab015d7eSMatt Spinler                                update = {}
219*66367a15SPatrick Williams                                update["remove"] = target_child
220*66367a15SPatrick Williams                                update["add"] = child
221ab015d7eSMatt Spinler                                updates.append(update)
222ab015d7eSMatt Spinler
223ab015d7eSMatt Spinler                for update in updates:
224*66367a15SPatrick Williams                    print(
225*66367a15SPatrick Williams                        "  Replacing a '"
226*66367a15SPatrick Williams                        + update["remove"].tag
227*66367a15SPatrick Williams                        + "' element in path "
228*66367a15SPatrick Williams                        + xpath
229*66367a15SPatrick Williams                    )
230*66367a15SPatrick Williams                    target.remove(update["remove"])
231*66367a15SPatrick Williams                    target.append(update["add"])
232ab015d7eSMatt Spinler
233ab015d7eSMatt Spinler            else:
234*66367a15SPatrick Williams                raise Exception(
235*66367a15SPatrick Williams                    "  E>  Unknown patch type attribute found:  " + patch_type
236*66367a15SPatrick Williams                )
237ab015d7eSMatt Spinler
238cb99d1eaSMatt Spinler        except Exception as e:
239721dcbd2SPatrick Williams            print(e)
240cb99d1eaSMatt Spinler            errors.append(e)
241ab015d7eSMatt Spinler
242ab015d7eSMatt Spinler    tree.write(args.output_xml)
243cb99d1eaSMatt Spinler
244cb99d1eaSMatt Spinler    if errors:
245cb99d1eaSMatt Spinler        print("Exiting with " + str(len(errors)) + " total errors")
246cb99d1eaSMatt Spinler        sys.exit(-1)
247