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 |
--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() |