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