1 |
torsten |
393 |
# -*- coding: utf-8 -*- |
2 |
|
|
#!/usr/bin/python |
3 |
|
|
"""\n |
4 |
|
|
Diffs to given formedtrees. Note that currently no checks on external choice |
5 |
|
|
lists are done. Furter checks on Repeatgroups are very simple for now. |
6 |
|
|
Warning: If a field with the same name is moved from master_tbl into a RG, it will not be |
7 |
|
|
noticed, as we do not diff strutural changes. |
8 |
|
|
|
9 |
|
|
Usage: diff_formed.py old new |
10 |
|
|
|
11 |
|
|
Options: |
12 |
|
|
-h / --help Print this message and exit. |
13 |
|
|
-e / --elements comma-seperated list of elements to check |
14 |
torsten |
395 |
-s / --subtree Define subtree as root for checks |
15 |
|
|
-f / --fields comma-seperated list of element-name to check |
16 |
torsten |
393 |
--check-attributes Check changes in attributes (changed descriptions, target...) |
17 |
|
|
|
18 |
|
|
""" |
19 |
|
|
# 20100810 Torsten Irlaender <[email protected]> |
20 |
|
|
# |
21 |
|
|
# This program is free software under the GNU GPL (>=v2) |
22 |
|
|
# Read the file COPYING coming with the software for details. |
23 |
|
|
|
24 |
|
|
|
25 |
|
|
import sys |
26 |
|
|
import getopt |
27 |
|
|
import logging |
28 |
|
|
|
29 |
|
|
from lxml import etree |
30 |
|
|
|
31 |
|
|
logging.basicConfig(level=logging.ERROR) |
32 |
|
|
log = logging.getLogger('diff_formed') |
33 |
|
|
|
34 |
|
|
class AttributeChangeException(Exception): |
35 |
|
|
def __init__(self, n, c, d): |
36 |
|
|
self.new = n |
37 |
|
|
self.changed = c |
38 |
|
|
self.deleted = d |
39 |
|
|
|
40 |
|
|
msg = [] |
41 |
|
|
if self.new: |
42 |
|
|
msg.append('New: %s' % ",".join(self.new)) |
43 |
|
|
if self.changed: |
44 |
|
|
msg.append('Changed: %s' % ",".join(self.changed)) |
45 |
|
|
if self.deleted: |
46 |
|
|
msg.append('Deleted: %s' % ",".join(self.deleted)) |
47 |
|
|
self.message = " | ".join(msg) |
48 |
|
|
|
49 |
|
|
class Inspector(): |
50 |
|
|
|
51 |
|
|
def __init__(self, filename_old, filename_new): |
52 |
|
|
|
53 |
|
|
self.old_xml_doc = None |
54 |
|
|
self.new_xml_doc = None |
55 |
|
|
self.elements_old = {} |
56 |
|
|
self.elements_new = {} |
57 |
|
|
self.elements = ['choice', 'text', 'textarea', 'repeat'] |
58 |
|
|
|
59 |
|
|
# Open files and load xml |
60 |
|
|
try: |
61 |
|
|
f1 = None |
62 |
|
|
f2 = None |
63 |
|
|
log.debug('Old: %s'% filename_old) |
64 |
|
|
f1 = open(filename_old, "r") |
65 |
|
|
log.debug('New: %s'% filename_new) |
66 |
|
|
f2 = open(filename_new, "r") |
67 |
|
|
self.old_xml_doc = etree.parse(f1) |
68 |
|
|
self.new_xml_doc = etree.parse(f2) |
69 |
|
|
except: |
70 |
|
|
log.exception('Error while opening file') |
71 |
|
|
finally: |
72 |
|
|
if f1 is not None: |
73 |
|
|
f1.close() |
74 |
|
|
if f2 is not None: |
75 |
|
|
f2.close() |
76 |
|
|
|
77 |
torsten |
394 |
def perform(self, elements=None, subtree=None, fields=None, check_attributes=False): |
78 |
|
|
log.info('Only checking %s elements' % ",".join(elements or ['all'])) |
79 |
torsten |
393 |
for element in self.elements: |
80 |
|
|
if elements is not None: |
81 |
|
|
if element not in elements: continue |
82 |
|
|
self.elements_old[element] = [] |
83 |
|
|
self.elements_new[element] = [] |
84 |
torsten |
394 |
if subtree: |
85 |
|
|
needle = '//*[@name="%s"]//%s' % (subtree, element) |
86 |
|
|
else: |
87 |
|
|
needle = '//%s' % (element) |
88 |
|
|
log.debug("Searching for %s" % needle) |
89 |
|
|
for e in self.old_xml_doc.findall(needle): |
90 |
|
|
if fields is not None: |
91 |
|
|
name = e.attrib.get('name') |
92 |
|
|
if str(name ) not in fields: |
93 |
|
|
log.debug('Old: Not found %s in %s' % (name, fields)) |
94 |
|
|
continue |
95 |
torsten |
393 |
self.elements_old[element].append(e) |
96 |
torsten |
394 |
for e in self.new_xml_doc.findall(needle): |
97 |
|
|
if fields is not None: |
98 |
|
|
name = e.attrib.get('name') |
99 |
|
|
if str(name )not in fields: |
100 |
|
|
log.debug('New: Not found %s in %s' % (name, fields)) |
101 |
|
|
continue |
102 |
torsten |
393 |
self.elements_new[element].append(e) |
103 |
|
|
self.diff_element(element, check_attributes) |
104 |
|
|
|
105 |
|
|
def diff_element(self, element, check_attributes): |
106 |
|
|
log.debug('Checking %s elements' % element) |
107 |
|
|
out = [] |
108 |
|
|
|
109 |
|
|
# Search for old field in new |
110 |
|
|
for o in self.elements_old.get(element): |
111 |
|
|
error = [] |
112 |
|
|
name = o.attrib.get('name') |
113 |
|
|
found = False |
114 |
|
|
for n in self.elements_new.get(element): |
115 |
|
|
if name == n.attrib.get('name'): |
116 |
|
|
found = True |
117 |
|
|
try: |
118 |
|
|
if check_attributes: |
119 |
|
|
self.diff_attributes(o, n) |
120 |
|
|
except AttributeChangeException, e: |
121 |
|
|
error.append("Attributes changed for '%s': %s" % (name, e.message)) |
122 |
|
|
if element == 'choice': |
123 |
|
|
error.extend(self.diff_choicelist(o, n, check_attributes)) |
124 |
|
|
break |
125 |
|
|
if not found: |
126 |
|
|
error.append("Deleted") |
127 |
|
|
|
128 |
|
|
if error: |
129 |
|
|
out.append("Field: %s (%s)" % (name, element)) |
130 |
|
|
out.append('---') |
131 |
|
|
out.append('Changes:') |
132 |
|
|
for num, err in enumerate(error): |
133 |
|
|
out.append('%s. %s' % (num, err)) |
134 |
|
|
out.append('===') |
135 |
|
|
|
136 |
|
|
# Searching for new fields in old |
137 |
|
|
for o in self.elements_new.get(element): |
138 |
|
|
error = [] |
139 |
|
|
name = o.attrib.get('name') |
140 |
|
|
# Search for element in new |
141 |
|
|
found = False |
142 |
|
|
for n in self.elements_old.get(element): |
143 |
|
|
if name == n.attrib.get('name'): |
144 |
|
|
found = True |
145 |
|
|
break |
146 |
|
|
if not found: |
147 |
|
|
error.append("New") |
148 |
torsten |
394 |
if element == 'choice': |
149 |
|
|
error.extend(self.diff_choicelist(None, o, check_attributes)) |
150 |
torsten |
393 |
|
151 |
|
|
if error: |
152 |
|
|
out.append("Field: %s (%s)" % (name, element)) |
153 |
|
|
out.append('---') |
154 |
|
|
out.append('Changes:') |
155 |
|
|
for num, err in enumerate(error): |
156 |
|
|
out.append('%s. %s' % (num, err)) |
157 |
|
|
out.append('===') |
158 |
|
|
|
159 |
|
|
for o in out: |
160 |
|
|
print unicode(o).encode("utf-8") |
161 |
|
|
|
162 |
|
|
def diff_choicelist(self, o, n, check_attributes): |
163 |
|
|
old = {} |
164 |
|
|
new = {} |
165 |
|
|
errors = [] |
166 |
torsten |
394 |
if o is not None: |
167 |
|
|
for c in o.findall('.//bool'): |
168 |
|
|
old[c.attrib.get('value')] = c |
169 |
|
|
if n is not None: |
170 |
|
|
for c in n.findall('.//bool'): |
171 |
|
|
new[c.attrib.get('value')] = c |
172 |
torsten |
393 |
|
173 |
|
|
for key in old.keys(): |
174 |
|
|
if new.has_key(key): |
175 |
|
|
try: |
176 |
|
|
if check_attributes: |
177 |
|
|
self.diff_attributes(old.get(key), new.get(key)) |
178 |
|
|
except AttributeChangeException, e: |
179 |
|
|
errors.append("Attributes changed for '%s': %s " % (key, e.message) ) |
180 |
|
|
else: |
181 |
|
|
errors.append("Deleted option: '%s' value: '%s'" % (old.get(key).attrib.get('description'), key)) |
182 |
|
|
for key in new.keys(): |
183 |
|
|
if not old.has_key(key): |
184 |
|
|
errors.append("New option: '%s' value: '%s'" % (new.get(key).attrib.get('description'), key)) |
185 |
|
|
|
186 |
|
|
return errors |
187 |
|
|
|
188 |
|
|
def diff_attributes(self, o, n): |
189 |
|
|
ok = False |
190 |
|
|
new = [] |
191 |
|
|
changed = [] |
192 |
|
|
deleted = [] |
193 |
|
|
for key, value in o.attrib.iteritems(): |
194 |
|
|
if n.attrib.has_key(key): |
195 |
|
|
if n.get(key) == value: |
196 |
|
|
pass #Ok |
197 |
|
|
else: |
198 |
|
|
changed.append(key) |
199 |
|
|
else: |
200 |
|
|
deleted.append(key) |
201 |
|
|
for key, value in n.attrib.iteritems(): |
202 |
|
|
if not o.attrib.has_key(key): |
203 |
|
|
new.append(key) |
204 |
|
|
|
205 |
|
|
if len(new) > 0 or len(changed) > 0 or len(deleted) > 0: |
206 |
|
|
raise AttributeChangeException(new, changed, deleted) |
207 |
|
|
|
208 |
|
|
def usage(code, msg=''): |
209 |
|
|
print >> sys.stderr, __doc__ |
210 |
|
|
if msg: |
211 |
|
|
print >> sys.stderr, msg |
212 |
|
|
sys.exit(code) |
213 |
|
|
|
214 |
|
|
def main(): |
215 |
|
|
try: |
216 |
torsten |
394 |
opts, args = getopt.getopt(sys.argv[1:], 'hvVe:s:f:', |
217 |
|
|
['elements=', 'check-attributes', 'subtree=', 'fields=']) |
218 |
torsten |
393 |
|
219 |
|
|
except getopt.error, msg: |
220 |
|
|
usage(1, msg) |
221 |
|
|
|
222 |
|
|
elements = None |
223 |
torsten |
394 |
subtree = None |
224 |
|
|
fields = None |
225 |
torsten |
393 |
check_attributes = False |
226 |
|
|
for opt, arg in opts: |
227 |
|
|
if opt in ('-v'): |
228 |
|
|
log.setLevel(logging.INFO) |
229 |
|
|
if opt in ('-V'): |
230 |
|
|
log.setLevel(logging.DEBUG) |
231 |
|
|
if opt in ('-h', '--help'): |
232 |
|
|
usage(0) |
233 |
|
|
if opt in ('-e', '--elements'): |
234 |
|
|
elements = arg.split(',') |
235 |
|
|
log.debug('Elements: %s' % elements) |
236 |
torsten |
394 |
if opt in ('-s', '--subtree'): |
237 |
|
|
subtree = arg |
238 |
|
|
log.debug('Subtree: %s' % subtree) |
239 |
|
|
if opt in ('-f', '--fields'): |
240 |
|
|
fields = arg.split(',') |
241 |
|
|
log.debug('Fields: %s' % fields) |
242 |
torsten |
393 |
if opt in ('--check-attributes'): |
243 |
|
|
check_attributes = True |
244 |
|
|
if len(args) < 2: |
245 |
|
|
usage(1) |
246 |
|
|
|
247 |
|
|
ins = Inspector(args[0], args[1]) |
248 |
torsten |
394 |
ins.perform(elements, subtree, fields, check_attributes) |
249 |
torsten |
393 |
|
250 |
|
|
if __name__ == '__main__': |
251 |
|
|
main() |