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 |
|
|
--check-attributes Check changes in attributes (changed descriptions, target...) |
15 |
|
|
|
16 |
|
|
""" |
17 |
|
|
# 20100810 Torsten Irlaender <[email protected]> |
18 |
|
|
# |
19 |
|
|
# This program is free software under the GNU GPL (>=v2) |
20 |
|
|
# Read the file COPYING coming with the software for details. |
21 |
|
|
|
22 |
|
|
|
23 |
|
|
import sys |
24 |
|
|
import getopt |
25 |
|
|
import logging |
26 |
|
|
|
27 |
|
|
from lxml import etree |
28 |
|
|
|
29 |
|
|
logging.basicConfig(level=logging.ERROR) |
30 |
|
|
log = logging.getLogger('diff_formed') |
31 |
|
|
|
32 |
|
|
class AttributeChangeException(Exception): |
33 |
|
|
def __init__(self, n, c, d): |
34 |
|
|
self.new = n |
35 |
|
|
self.changed = c |
36 |
|
|
self.deleted = d |
37 |
|
|
|
38 |
|
|
msg = [] |
39 |
|
|
if self.new: |
40 |
|
|
msg.append('New: %s' % ",".join(self.new)) |
41 |
|
|
if self.changed: |
42 |
|
|
msg.append('Changed: %s' % ",".join(self.changed)) |
43 |
|
|
if self.deleted: |
44 |
|
|
msg.append('Deleted: %s' % ",".join(self.deleted)) |
45 |
|
|
self.message = " | ".join(msg) |
46 |
|
|
|
47 |
|
|
class Inspector(): |
48 |
|
|
|
49 |
|
|
def __init__(self, filename_old, filename_new): |
50 |
|
|
|
51 |
|
|
self.old_xml_doc = None |
52 |
|
|
self.new_xml_doc = None |
53 |
|
|
self.elements_old = {} |
54 |
|
|
self.elements_new = {} |
55 |
|
|
self.elements = ['choice', 'text', 'textarea', 'repeat'] |
56 |
|
|
|
57 |
|
|
# Open files and load xml |
58 |
|
|
try: |
59 |
|
|
f1 = None |
60 |
|
|
f2 = None |
61 |
|
|
log.debug('Old: %s'% filename_old) |
62 |
|
|
f1 = open(filename_old, "r") |
63 |
|
|
log.debug('New: %s'% filename_new) |
64 |
|
|
f2 = open(filename_new, "r") |
65 |
|
|
self.old_xml_doc = etree.parse(f1) |
66 |
|
|
self.new_xml_doc = etree.parse(f2) |
67 |
|
|
except: |
68 |
|
|
log.exception('Error while opening file') |
69 |
|
|
finally: |
70 |
|
|
if f1 is not None: |
71 |
|
|
f1.close() |
72 |
|
|
if f2 is not None: |
73 |
|
|
f2.close() |
74 |
|
|
|
75 |
|
|
def perform(self, elements=None, check_attributes=False): |
76 |
|
|
log.info('Only checking %s elements' % ",".join(elements)) |
77 |
|
|
for element in self.elements: |
78 |
|
|
if elements is not None: |
79 |
|
|
if element not in elements: continue |
80 |
|
|
self.elements_old[element] = [] |
81 |
|
|
self.elements_new[element] = [] |
82 |
|
|
for e in self.old_xml_doc.findall("//%s" % element): |
83 |
|
|
self.elements_old[element].append(e) |
84 |
|
|
for e in self.new_xml_doc.findall("//%s" % element): |
85 |
|
|
self.elements_new[element].append(e) |
86 |
|
|
self.diff_element(element, check_attributes) |
87 |
|
|
|
88 |
|
|
def diff_element(self, element, check_attributes): |
89 |
|
|
log.debug('Checking %s elements' % element) |
90 |
|
|
out = [] |
91 |
|
|
|
92 |
|
|
# Search for old field in new |
93 |
|
|
for o in self.elements_old.get(element): |
94 |
|
|
error = [] |
95 |
|
|
name = o.attrib.get('name') |
96 |
|
|
found = False |
97 |
|
|
for n in self.elements_new.get(element): |
98 |
|
|
if name == n.attrib.get('name'): |
99 |
|
|
found = True |
100 |
|
|
try: |
101 |
|
|
if check_attributes: |
102 |
|
|
self.diff_attributes(o, n) |
103 |
|
|
except AttributeChangeException, e: |
104 |
|
|
error.append("Attributes changed for '%s': %s" % (name, e.message)) |
105 |
|
|
if element == 'choice': |
106 |
|
|
error.extend(self.diff_choicelist(o, n, check_attributes)) |
107 |
|
|
break |
108 |
|
|
if not found: |
109 |
|
|
error.append("Deleted") |
110 |
|
|
|
111 |
|
|
if error: |
112 |
|
|
out.append("Field: %s (%s)" % (name, element)) |
113 |
|
|
out.append('---') |
114 |
|
|
out.append('Changes:') |
115 |
|
|
for num, err in enumerate(error): |
116 |
|
|
out.append('%s. %s' % (num, err)) |
117 |
|
|
out.append('===') |
118 |
|
|
|
119 |
|
|
# Searching for new fields in old |
120 |
|
|
for o in self.elements_new.get(element): |
121 |
|
|
error = [] |
122 |
|
|
name = o.attrib.get('name') |
123 |
|
|
# Search for element in new |
124 |
|
|
found = False |
125 |
|
|
for n in self.elements_old.get(element): |
126 |
|
|
if name == n.attrib.get('name'): |
127 |
|
|
found = True |
128 |
|
|
break |
129 |
|
|
if not found: |
130 |
|
|
error.append("New") |
131 |
|
|
|
132 |
|
|
if error: |
133 |
|
|
out.append("Field: %s (%s)" % (name, element)) |
134 |
|
|
out.append('---') |
135 |
|
|
out.append('Changes:') |
136 |
|
|
for num, err in enumerate(error): |
137 |
|
|
out.append('%s. %s' % (num, err)) |
138 |
|
|
out.append('===') |
139 |
|
|
|
140 |
|
|
for o in out: |
141 |
|
|
print unicode(o).encode("utf-8") |
142 |
|
|
|
143 |
|
|
def diff_choicelist(self, o, n, check_attributes): |
144 |
|
|
old = {} |
145 |
|
|
new = {} |
146 |
|
|
errors = [] |
147 |
|
|
for c in o.getchildren(): |
148 |
|
|
old[c.attrib.get('value')] = c |
149 |
|
|
for c in n.getchildren(): |
150 |
|
|
new[c.attrib.get('value')] = c |
151 |
|
|
|
152 |
|
|
for key in old.keys(): |
153 |
|
|
if new.has_key(key): |
154 |
|
|
try: |
155 |
|
|
if check_attributes: |
156 |
|
|
self.diff_attributes(old.get(key), new.get(key)) |
157 |
|
|
except AttributeChangeException, e: |
158 |
|
|
errors.append("Attributes changed for '%s': %s " % (key, e.message) ) |
159 |
|
|
else: |
160 |
|
|
errors.append("Deleted option: '%s' value: '%s'" % (old.get(key).attrib.get('description'), key)) |
161 |
|
|
for key in new.keys(): |
162 |
|
|
if not old.has_key(key): |
163 |
|
|
errors.append("New option: '%s' value: '%s'" % (new.get(key).attrib.get('description'), key)) |
164 |
|
|
|
165 |
|
|
return errors |
166 |
|
|
|
167 |
|
|
def diff_attributes(self, o, n): |
168 |
|
|
ok = False |
169 |
|
|
new = [] |
170 |
|
|
changed = [] |
171 |
|
|
deleted = [] |
172 |
|
|
for key, value in o.attrib.iteritems(): |
173 |
|
|
if n.attrib.has_key(key): |
174 |
|
|
if n.get(key) == value: |
175 |
|
|
pass #Ok |
176 |
|
|
else: |
177 |
|
|
changed.append(key) |
178 |
|
|
else: |
179 |
|
|
deleted.append(key) |
180 |
|
|
for key, value in n.attrib.iteritems(): |
181 |
|
|
if not o.attrib.has_key(key): |
182 |
|
|
new.append(key) |
183 |
|
|
|
184 |
|
|
if len(new) > 0 or len(changed) > 0 or len(deleted) > 0: |
185 |
|
|
raise AttributeChangeException(new, changed, deleted) |
186 |
|
|
|
187 |
|
|
def usage(code, msg=''): |
188 |
|
|
print >> sys.stderr, __doc__ |
189 |
|
|
if msg: |
190 |
|
|
print >> sys.stderr, msg |
191 |
|
|
sys.exit(code) |
192 |
|
|
|
193 |
|
|
def main(): |
194 |
|
|
try: |
195 |
|
|
opts, args = getopt.getopt(sys.argv[1:], 'hvVe:', |
196 |
|
|
['elements=', 'check-attributes']) |
197 |
|
|
|
198 |
|
|
except getopt.error, msg: |
199 |
|
|
usage(1, msg) |
200 |
|
|
|
201 |
|
|
elements = None |
202 |
|
|
check_attributes = False |
203 |
|
|
for opt, arg in opts: |
204 |
|
|
if opt in ('-v'): |
205 |
|
|
log.setLevel(logging.INFO) |
206 |
|
|
if opt in ('-V'): |
207 |
|
|
log.setLevel(logging.DEBUG) |
208 |
|
|
if opt in ('-h', '--help'): |
209 |
|
|
usage(0) |
210 |
|
|
if opt in ('-e', '--elements'): |
211 |
|
|
elements = arg.split(',') |
212 |
|
|
log.debug('Elements: %s' % elements) |
213 |
|
|
if opt in ('--check-attributes'): |
214 |
|
|
check_attributes = True |
215 |
|
|
if len(args) < 2: |
216 |
|
|
usage(1) |
217 |
|
|
|
218 |
|
|
ins = Inspector(args[0], args[1]) |
219 |
|
|
ins.perform(elements, check_attributes) |
220 |
|
|
|
221 |
|
|
if __name__ == '__main__': |
222 |
|
|
main() |