1 |
# -*- 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 |
-s / --subtree Define subtree as root for checks |
15 |
-f / --fields comma-seperated list of element-name to check |
16 |
--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 |
def perform(self, elements=None, subtree=None, fields=None, check_attributes=False): |
78 |
log.info('Only checking %s elements' % ",".join(elements or ['all'])) |
79 |
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 |
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 |
self.elements_old[element].append(e) |
96 |
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 |
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 |
if element == 'choice': |
149 |
error.extend(self.diff_choicelist(None, o, check_attributes)) |
150 |
|
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 |
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 |
|
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 |
opts, args = getopt.getopt(sys.argv[1:], 'hvVe:s:f:', |
217 |
['elements=', 'check-attributes', 'subtree=', 'fields=']) |
218 |
|
219 |
except getopt.error, msg: |
220 |
usage(1, msg) |
221 |
|
222 |
elements = None |
223 |
subtree = None |
224 |
fields = None |
225 |
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 |
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 |
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 |
ins.perform(elements, subtree, fields, check_attributes) |
249 |
|
250 |
if __name__ == '__main__': |
251 |
main() |