Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(83)

Side by Side Diff: visual_studio/NativeClientVSAddIn/InstallerResources/xml_patch.py

Issue 10797040: PPAPI Patching System and Unit Tests (Closed) Base URL: https://nativeclient-sdk.googlecode.com/svn/trunk/src
Patch Set: Created 8 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 #!/usr/bin/env python
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
5
6 """ This module is a utility for applying an xml patch to an xml file.
7
8 The format of the patch is described in the documentation for
9 the patch_xml() function.
10 """
11
12 import collections
13 import copy
14 import xml.etree.ElementTree as ElementTree
15
16
17 def PatchXML(source_xml_tree, patch_xml_tree):
18 """Applies a patch to the source xml and returns a new merged xml tree.
19
20 Given a patch xml tree, it applies the patch to the source xml tree
21 and returns the resulting modified xml tree.
22
23 Patching is done by reading the patch_xml_tree for an element and then
24 finding the in-order matching element in the source_xml_tree. Both elements
25 are entered to look for matching sub-elements recursively.
26
27 Patching occurs when a <PatchRemove> or <PatchAdd> element is encountered
28 in the patch xml tree. For a remove operation, the first element in the
29 source_xml_tree from the current read position that matches the contents of
30 the <PatchRemove> element is removed. The read pointer is updated accordingly.
31 For an add operation, the contents of the <PatchAdd> element is added at the
32 current reading location.
33
34 If for example, an add operation needs to be done after certain elements,
35 the elements can be listed before the <PatchAdd> operation so that they are
36 matched first before the add operation.
37
38 Example:
39 Source file:
40 <a>
41 <b></b>
42 <c></c>
43 </a>
44
45 Patch file:
46 <a>
47 <b></b>
48 <PatchAdd><zzz></zzz></PatchAdd>
49 </a>
50
51 Result:
52 <a>
53 <b></b>
54 <zzz></zzz>
55 <c></c>
56 </a>
57
58
59 Args:
60 source_xml_tree: An ElementTree object with base xml to change.
61 patch_xml_tree: An ElementTree object with xml to apply. See above notes
62 for the xml structure of a patch.
63
64 Returns:
65 A new xml tree based on source with the patch applied.
66
67 Raises:
68 General Exception indicating a merge error has occured.
69 """
70 source = source_xml_tree.getroot()
71 patch = patch_xml_tree.getroot()
72 if not ElementMatch(source, patch):
73 raise Exception('Root nodes do not match, cannot merge')
74 return ElementTree.ElementTree(MergeElement(source, patch))
75
76
77 def MergeElement(source_elem, patch_elem):
78 """Applies a single patch element to a single source element.
79
80 The merge is applied recursively for sub-elements. See the documentation
81 for patch_xml() for a description of how patching is done.
82
83 Args:
84 source_elem: An Element object with xml to change.
85 patch_elem: An Element object with xml to apply.
86
87 Returns:
88 A new xml Element with the patch_elem applied to source_elem.
89
90 Raises:
91 General Exception indicating a merge error has occured.
92 """
93 assert ElementMatch(source_elem, patch_elem), 'Mismatched merge'
94
95 # Create a new element by copying tags from source. Below we will merge
96 # the subelements of source with the patch and put them in new_element.
97 new_element = ElementTree.Element(source_elem.tag, source_elem.attrib)
98
99 patch_children = patch_elem.getchildren()
100 patch_index = 0
101 remove_targets = collections.deque()
102 find_target = None
103 for source_child in source_elem.getchildren():
104 # If we have no current patch operation then read the next patch element.
105 while (len(remove_targets) == 0 and find_target is None and
106 patch_index < len(patch_children)):
107
108 # PatchAdd operation.
109 if IsPatchAddTag(patch_children[patch_index].tag):
110 for addition in patch_children[patch_index].getchildren():
111 new_element.append(copy.deepcopy(addition))
112
113 # Start a remove operation by creating a list of elements to skip adding.
114 elif IsPatchRemoveTag(patch_children[patch_index].tag):
115 remove_targets = collections.deque(
116 patch_children[patch_index].getchildren())
117
118 # Not an Add or Remove, must be a find target (find operation).
119 else:
120 find_target = patch_children[patch_index]
121 patch_index += 1
122
123 # A remove operation means skipping adding the element to new_element.
124 if (len(remove_targets) > 0 and
125 ElementMatch(source_child, remove_targets[0])):
126 remove_targets.popleft()
127
128 # A matching find target means we must merge the sub-elements.
129 elif find_target is not None and ElementMatch(source_child, find_target):
130 merge = MergeElement(source_child, find_target)
131 new_element.append(copy.deepcopy(merge))
132 find_target = None
133
134 # Otherwise this source element doesn't match any patch operations, add it.
135 else:
136 new_element.append(copy.deepcopy(source_child))
137
138 # Raise exceptions if find/remove didn't finish before the end of the source.
139 if find_target is not None:
140 raise Exception('Find operation never matched:' + find_target.tag)
141 elif len(remove_targets) != 0:
142 raise Exception('Remove operation never matched: ' + remove_targets)
143
144 # We may have more add operations after source has run empty:
145 while patch_index < len(patch_children):
146 if IsPatchAddTag(patch_children[patch_index].tag):
147 for addition in patch_children[patch_index].getchildren():
148 new_element.append(copy.deepcopy(addition))
149 patch_index += 1
150 else:
151 raise Exception('Non-add operation attempted after source end. ' +
152 'Tag: %s, Children %s' %
153 (patch_children[patch_index].tag,
154 patch_children[patch_index].get_children()))
155
156 return new_element
157
158
159 def ElementMatch(elem1, elem2):
160 return elem1.tag == elem2.tag and elem1.attrib == elem2.attrib
161
162
163 def IsPatchAddTag(tag):
164 # We look at the end of the tag because we need to ignore the namespace.
165 # Because the tag can be a sub-element of arbitrary elements it could inherit
166 # any default namespace.
167 return tag.endswith('PatchAdd')
168
169
170 def IsPatchRemoveTag(tag):
171 # We look at the end of the tag because we need to ignore the namespace.
172 # Because the tag can be a sub-element of arbitrary elements it could inherit
173 # any default namespace.
174 return tag.endswith('PatchRemove')
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698