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

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 xml.etree.ElementTree as ElementTree
14
15 def patch_xml(source_xml_tree, patch_xml_tree):
noelallen1 2012/07/20 21:46:01 PatchXML
tysand 2012/07/24 21:24:15 Done.
16 """Applies a patch to the source xml and returns a new merged xml tree.
17
18 Given a patch xml tree, it applies the patch to the source xml tree
19 and returns the resulting modified xml tree.
20
21 Patching is done by reading the patch_xml_tree for an element and then
22 finding the in-order matching element in the source_xml_tree. Both elements
23 are entered to look for matching sub-elements recursively.
24
25 Patching occurs when a <PatchRemove> or <PatchAdd> element is encountered
26 in the patch xml tree. For a remove operation, the first element in the
27 source_xml_tree from the current read position that matches the contents of
28 the <PatchRemove> element is removed. The read pointer is updated accordingly.
29 For an add operation, the contents of the <PatchAdd> element is added at the
30 current reading location.
31
32 If for example, an add operation needs to be done after certain elements,
33 the elements can be listed before the <PatchAdd> operation so that they are
34 matched first before the add operation.
35
36 Example:
37 Source file:
38 <a>
39 <b></b>
40 <c></c>
41 </a>
42
43 Patch file:
44 <a>
45 <b></b>
46 <PatchAdd><zzz></zzz></PatchAdd>
47 </a>
48
49 Result:
50 <a>
51 <b></b>
52 <zzz></zzz>
53 <c></c>
54 </a>
55
56
57 Args:
58 source_xml_tree: An ElementTree object with base xml to change.
59 patch_xml_tree: An ElementTree object with xml to apply. See above notes
60 for the xml structure of a patch.
61
62 Returns:
63 A new xml tree based on source with the patch applied.
64
65 Raises:
66 General Exception indicating a merge error has occured.
67 """
68 source = source_xml_tree.getroot()
69 patch = patch_xml_tree.getroot()
70 if not element_match(source, patch):
71 raise Exception('Root nodes do not match, cannot merge')
72 return ElementTree.ElementTree(merge_element(source, patch))
73
noelallen1 2012/07/20 21:46:01 two lines
tysand 2012/07/24 21:24:15 Done.
74 def merge_element(source_elem, patch_elem):
75 """Applies a single patch element to a single source element.
76
77 The merge is applied recursively for sub-elements. See the documentation
78 for patch_xml() for a description of how patching is done.
79
80 Args:
81 source_elem: An Element object with xml to change.
82 patch_elem: An Element object with xml to apply.
83
84 Returns:
85 A new xml Element with the patch_elem applied to source_elem.
86
87 Raises:
88 General Exception indicating a merge error has occured.
89 """
90 assert element_match(source_elem, patch_elem), 'Mismatched merge'
91
92 # Create a new element by copying tags from source. Below we will merge
93 # the subelements of source with the patch and put them in new_element.
94 new_element = ElementTree.Element(source_elem.tag, source_elem.attrib)
95
96 patch_children = patch_elem.getchildren()
97 patch_index = 0
98 remove_targets = collections.deque()
99 find_target = None
100 for source_child in source_elem.getchildren():
101 # If we have no current patch operation then read the next patch line.
binji 2012/07/20 22:54:49 s/line/element/ here and elsewhere
tysand 2012/07/24 21:24:15 Done.
102 while (len(remove_targets) is 0 and find_target is None and
binji 2012/07/20 22:54:49 s/is 0/== 0/
tysand 2012/07/24 21:24:15 Done.
103 patch_index < len(patch_children)):
binji 2012/07/20 22:54:49 nit: 4 space indent
tysand 2012/07/24 21:24:15 Done.
104
105 # PatchAdd operation.
106 if is_patch_add_tag(patch_children[patch_index].tag):
107 for addition in patch_children[patch_index].getchildren():
108 new_element.append(addition)
binji 2012/07/20 22:54:49 does this clone the addition element or is it shar
tysand 2012/07/24 21:24:15 I think it is a shallow copy. Although this doesn'
109 patch_index += 1
binji 2012/07/20 22:54:49 extract patch_index += 1, add below this if/elif/e
tysand 2012/07/24 21:24:15 Done.
110
111 # Start a remove operation by creating a list of lines to skip adding.
112 elif is_patch_remove_tag(patch_children[patch_index].tag):
113 remove_targets = collections.deque(
114 patch_children[patch_index].getchildren())
115 patch_index += 1
116
117 # Not an Add or Remove, must be a find target (find operation).
118 else:
119 find_target = patch_children[patch_index]
120 patch_index += 1
121
122 # A remove operation means skipping adding the line to new_element.
123 if (len(remove_targets) > 0 and
124 element_match(source_child, remove_targets[0])):
125 remove_targets.popleft()
126
127 # A matching find target means we must merge the sub-elements.
128 elif find_target is not None and element_match(source_child, find_target):
129 merge = merge_element(source_child, find_target)
130 new_element.append(merge)
131 find_target = None
132 patch_index += 1
133
134 # Otherwise this source line doesn't match any patch operations, add it.
135 else:
136 new_element.append(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) is not 0:
binji 2012/07/20 22:54:49 != 0
tysand 2012/07/24 21:24:15 Done.
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 is_patch_add_tag(patch_children[patch_index].tag):
147 for addition in patch_children[patch_index].getchildren():
148 new_element.append(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 def element_match(elem1, elem2):
159 return elem1.tag == elem2.tag and elem1.attrib == elem2.attrib
160
161 def is_patch_add_tag(tag):
162 return tag[-8:] == 'PatchAdd'
binji 2012/07/20 22:54:49 why is this not tag == 'PatchAdd'?
tysand 2012/07/24 21:24:15 I've added a comment explaining this. Basically be
binji 2012/07/24 22:13:08 OK, use tag.endswith('PatchAdd')
163
164 def is_patch_remove_tag(tag):
165 return tag[-11:] == 'PatchRemove'
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698