summaryrefslogtreecommitdiffstats
path: root/patchxml.py
blob: 0deb5d96be722f2b665138d1342b99bbbc15fad8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
#!/usr/bin/env python

"""
This script applies patches to an XML file.

The patch file is itself an XML file.  It can have any root element name,
and uses XML attributes to specify if the elements in the file should replace
existing elements or add new ones.  An XPath attribute is used to specify
where the fix should be applied.  A <targetFile> element is required in the
patch file to specify the base name of the XML file the patches should be
applied to, though the targetFile element is handled outside of this script.

The only restriction is that since the type, xpath, and key attributes are
used to specify the patch placement the target XML cannot use those at a
top level element.

 It can apply patches in 5 ways:

 1) Add an element:
    Put in the element to add, along with the type='add' attribute
    and an xpath attribute specifying where the new element should go.

     <enumerationType type='add' xpath="./">
       <id>MY_TYPE</id>
     </enumerationType>

     This will add a new enumerationType element child to the root element.

 2) Replace an element:
    Put in the new element, with the type='replace' attribute
    and the XPath of the element you want to replace.

     <enumerator type='replace'
               xpath="enumerationType/[id='TYPE']/enumerator[name='XBUS']">
       <name>XBUS</name>
       <value>the new XBUS value</value>
     </enumerator>

    This will replace the enumerator element with name XBUS under the
    enumerationType element with ID TYPE.

 3) Remove an element:
    Put in the element to remove, with the type='remove' attribute and
    the XPath of the element you want to remove. The full element contents
    don't need to be specified, as the XPath is what locates the element.

    <enumerator type='remove'
                xpath='enumerationType[id='TYPE]/enumerator[name='DIMM']>
    </enumerator>

    This will remove the enumerator element with name DIMM under the
    enumerationType element with ID TYPE.

 4) Add child elements to a specific element.  Useful when adding several
    child elements at once.

    Use a type attribute of 'add-child' and specify the target parent with
    the xpath attribute.

     <enumerationType type="add-child" xpath="enumerationType/[id='TYPE']">
       <enumerator>
         <name>MY_NEW_ENUMERATOR</name>
         <value>23</value>
       </enumerator>
       <enumerator>
         <name>ANOTHER_NEW_ENUMERATOR</name>
         <value>99</value>
       </enumerator>
     </enumerationType>

     This will add 2 new <enumerator> elements to the enumerationType
     element with ID TYPE.

 5) Replace a child element inside another element, useful when replacing
    several child elements of the same parent at once.

    Use a type attribute of 'replace-child' and the xpath attribute
    as described above, and also use the key attribute to specify which
    element should be used to match on so the replace can be done.

     <enumerationType type="replace-child"
                      key="name"
                      xpath="enumerationType/[id='TYPE']">
       <enumerator>
         <name>OLD_ENUMERATOR</name>
         <value>newvalue</value>
       </enumerator>
       <enumerator>
         <name>ANOTHER_OLD_ENUMERATOR</name>
         <value>anothernewvalue</value>
       </enumerator>
     </enumerationType>

     This will replace the <enumerator> elements with the names of
     OLD_ENUMERATOR and ANOTHER_OLD_ENUMERATOR with the <enumerator>
     elements specified, inside of the enumerationType element with
     ID TYPE.
"""


from lxml import etree
import sys
import argparse


def delete_attrs(element, attrs):
    for a in attrs:
        try:
            del element.attrib[a]
        except:
            pass

if __name__ == '__main__':

    parser = argparse.ArgumentParser("Applies fixes to XML files")
    parser.add_argument("-x", dest='xml', help='The input XML file')
    parser.add_argument("-p", dest='patch_xml', help='The patch XML file')
    parser.add_argument("-o", dest='output_xml', help='The output XML file')
    args = parser.parse_args()

    if not all([args.xml, args.patch_xml, args.output_xml]):
        parser.print_usage()
        sys.exit(-1)

    errors = []
    patch_num = 0
    patch_tree = etree.parse(args.patch_xml)
    patch_root = patch_tree.getroot()
    tree = etree.parse(args.xml)
    root = tree.getroot()

    for node in patch_root:
        if (node.tag is etree.PI) or (node.tag is etree.Comment) or \
           (node.tag == "targetFile"):
            continue

        patch_num = patch_num + 1

        xpath = node.get('xpath', None)
        patch_type = node.get('type', 'add')
        patch_key = node.get('key', None)
        delete_attrs(node, ['xpath', 'type', 'key'])

        print("Patch " + str(patch_num) + ":")

        try:
            if xpath is None:
                raise Exception("  E>  No XPath attribute found")

            target = tree.find(xpath)

            if target is None:
                raise Exception("  E>  Could not find XPath target " + xpath)

            if patch_type == "add":

                print("  Adding element " + target.tag + " to " + xpath)

                #The ServerWiz API is dependent on ordering for the
                #elements at the root node, so make sure they get appended
                #at the end.
                if (xpath == "./") or (xpath == "/"):
                    root.append(node)
                else:
                    target.append(node)

            elif patch_type == "remove":

                print("  Removing element " + xpath)
                parent = target.find("..")
                if parent is None:
                    raise Exception("  E>  Could not find parent of " + xpath +
                                    " so can't remove this element")
                parent.remove(target)

            elif patch_type == "replace":

                print("  Replacing element " + xpath)
                parent = target.find("..")
                if parent is None:
                    raise Exception("  E>  Could not find parent of " + xpath +
                                    " so can't replace this element")
                parent.remove(target)
                parent.append(node)

            elif patch_type == "add-child":

                for child in node:
                    print("  Adding a '" + child.tag + "' child element"
                          " to " + xpath)
                    target.append(child)

            elif patch_type == "replace-child":

                if patch_key is None:
                    raise Exception("  E>  Patch type is replace-child, but"
                                    " 'key' attribute isn't set")
                updates = []
                for child in node:
                    #Use the key to figure out which element to replace
                    key_element = child.find(patch_key)
                    for target_child in target:
                        for grandchild in target_child:
                            if (grandchild.tag == patch_key) and \
                               (grandchild.text == key_element.text):
                                update = {}
                                update['remove'] = target_child
                                update['add'] = child
                                updates.append(update)

                for update in updates:
                    print("  Replacing a '" + update['remove'].tag +
                          "' element in path " + xpath)
                    target.remove(update['remove'])
                    target.append(update['add'])

            else:
                raise Exception("  E>  Unknown patch type attribute found:  " +
                                patch_type)

        except Exception as e:
            print e
            errors.append(e)

    tree.write(args.output_xml)

    if errors:
        print("Exiting with " + str(len(errors)) + " total errors")
        sys.exit(-1)
OpenPOWER on IntegriCloud