coding utf-8 import simplejson from itertools import islice from types

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
# -*- coding: utf-8 -*-
import simplejson
from itertools import islice
from types import DictType, StringTypes, ListType
DEFAULT_LANGUAGE="en"
class HierarchyNode(object):
"""initializes hierarchy node with i18ns if 'trans' kwarg is given
if trans kwargs is given it must be dict, mapping langs to unicode objs
Simple single-language node (DEFAULT_LANGUAGE=en):
>>> a = HierarchyNode(u"simple text")
>>> a
Node: {'en': u'simple text'}
>>> unicode(a)
u'simple text'
>>> a.trans('en')
u'simple text'
On unknown translation fallback to default
>>> a.trans('ru')
u'simple text'
Node with two translations:
>>> b = HierarchyNode({'en': 'asd', 'ru': unicode('асд','utf-8')})
>>> b
Node: {'ru': u'\u0430\u0441\u0434', 'en': 'asd'}
>>> unicode(b)
u'asd'
>>> b.trans('en')
u'asd'
>>> b.trans('ru')
u'\u0430\u0441\u0434'
"""
def __init__(self, value=''):
if type(value) is dict:
if DEFAULT_LANGUAGE not in value:
raise ValueError("Tried to initialize hierarchy node "\
"without default lang (%s). got: %s" , (
DEFAULT_LANGUAGE, value))
else:
self._trans = value
else:
self._trans = {DEFAULT_LANGUAGE: value}
def __unicode__(self):
return unicode(self._trans[DEFAULT_LANGUAGE])
def trans(self, lang):
if lang not in self._trans:
lang = DEFAULT_LANGUAGE
return unicode(self._trans[lang])
def __repr__(self):
return u"Node: %s" % (self._trans)
class Hierarchy(object):
r"""class representing the hierarchy of relations between nodes
data - actual data structure
>>> h = Hierarchy([ {'name': u'l1',
... 'items': [
... {'en': 'el 1', 'ru': unicode('эл 1', 'utf-8')},
... u'index 2',
... u'point 3']
... },
... {'name': {'en': 'level 2','ru': unicode('уровень 2','utf-8')},
... 'items': [{'en': 'item 1', 'ru': unicode('штука 1', 'utf-8')}]
... }
... ])
1>>> h
1Hierarchy: [{'items': [Node: {'ru': u'\u044d\u043b 1', 'en': 'el 1'}, Node: {'en': u'index 2'}, Node: {'en': u'point 3'}], 'name': Node: {'en': u'l1'}}, {'items': [Node: {'ru': u'\u0448\u0442\u0443\u043a\u0430 1', 'en': 'item 1'}], 'name': Node: {'ru': u'\u0443\u0440\u043e\u0432\u0435\u043d\u044c 2', 'en': 'level 2'}}]
>>> h['l1']
{'items': [Node: {'ru': u'\u044d\u043b 1', 'en': 'el 1'}, Node: {'en': u'index 2'}, Node: {'en': u'point 3'}], 'name': Node: {'en': u'l1'}}
>>> h['level 2']
{'items': [Node: {'ru': u'\u0448\u0442\u0443\u043a\u0430 1', 'en': 'item 1'}], 'name': Node: {'ru': u'\u0443\u0440\u043e\u0432\u0435\u043d\u044c 2', 'en': 'level 2'}}
>>> h['l3']
Traceback (most recent call last):
...
KeyError: "level l3 not found in hierarchy. choices are: [u'l1', u'level 2']"
>>> h.lang = 'ru'
>>> h['l1']
{'items': [Node: {'ru': u'\u044d\u043b 1', 'en': 'el 1'}, Node: {'en': u'index 2'}, Node: {'en': u'point 3'}], 'name': Node: {'en': u'l1'}}
>>> h['level 2']
Traceback (most recent call last):
...
KeyError: "level level 2 not found in hierarchy. choices are: [u'l1', u'\\u0443\\u0440\\u043e\\u0432\\u0435\\u043d\\u044c 2']"
>>> h[unicode('уровень 2','utf-8')]
{'items': [Node: {'ru': u'\u0448\u0442\u0443\u043a\u0430 1', 'en': 'item 1'}], 'name': Node: {'ru': u'\u0443\u0440\u043e\u0432\u0435\u043d\u044c 2', 'en': 'level 2'}}
"""
def __init__(self, data, lang=None):
"""initializes a new hierarchy object from the given struct
data - base structure for hierarchy in form:
data ::= [level]
level ::= {'name': node(), 'items': [node()]}
node ::= HierarchyNode() | HierarchyNode initializer
"""
lang = lang or DEFAULT_LANGUAGE
self.raw_data = data
self.lang = lang
@staticmethod
def normalize_data(data):
"""turns hierarchy struct's leves into HierarchyNodes"""
def normalize_item(item):
if isinstance(item, HierarchyNode):
return item
else:
return HierarchyNode(item)
new_data = []
for level in data:
new_level = level.copy()
new_level['name'] = normalize_item(level['name'])
new_level['items'] = map(normalize_item, level['items'])
new_data.append(new_level)
return new_data
def __getitem__(self, key):
"""get level by name"""
available_names = []
for level in self.levels:
name = level['name'].trans(self.lang)
available_names.append(name)
if name == key:
return level
raise KeyError, "level %s not found in hierarchy. choices are: %s" % (
key, available_names)
def get_raw_data(self):
return self._data
def set_raw_data(self, struct):
try:
if self.validate(struct):
self._data = self.normalize_data(struct)
except ValueError:
raise
raw_data = property(get_raw_data, set_raw_data)
@staticmethod
def validate(data):
"""validates input data for hierarchy structure
input structure is a list of dictionaties, containig 'name' and
'items' keys, where the latter is a list of HierarchyNode or
anything to initialize it
>>> Hierarchy.validate([])
True
>>> Hierarchy.validate('wrong')
Traceback (most recent call last):
...
ValueError: validation failed, expecting list got <type 'str'>
>>> Hierarchy.validate([{'name':'','items':[]}])
True
>>> Hierarchy.validate([ {'name': 'l1', 'items': ['item1', HierarchyNode('item2'), {'en': 'ssd', 'de': unicode('ßd','utf-8')}]} ])
True
"""
dt="""
>>> Hierarchy.validate([{'key':'value'}])
Traceback (most recent call last):
...
ValueError: level {'key': 'value'} doesn't have 'name' key
"""
try:
assert type(data) == ListType, 'validation failed, expecting list'\
' got %s' % type(data)
for level in data:
for attr_name in ['name', 'items']:
assert level.has_key(attr_name), "level %s doesn't have "\
"'%s' key " % (level, attr_name)
assert type(level['items']) == ListType, 'validation failed, '\
'expecting list for items got %s' % type(level['items'])
except AssertionError, e:
raise ValueError, e.message
else:
return True
@property
def levels(self):
"""returns iterator over levels of hierarchy
every result is iterator over level's items and has lvl's name set"""
for level in self.raw_data:
yield level
def __repr__(self):
return "Hierarchy: %s" % self._data
class QuestionRenderer(object):
"""abastract class for question renderer
has one method to be overloaded: render, which gets one argument
chain: a (path, alternatives) tuple, where
path - iterable of (level_name, level_item) pairs of HierarchyNode()
alternatives - pair of HierarchyNode()
"""
def render(self, chain):
raise NotImplementedError
def render_all(self, q_generator):
return (self.render(x) for x in q_generator)
class SimpleHtmlQuestionRenderer(QuestionRenderer):
template_main = u"Сравнить %s при: %s"
template_alts = u'%(level_name)s "%(alt1)s" и "%(alt2)s"'
template_limit = u'"%s: %s"'
def render(self, chain):
path, alternatives = chain
alternatives = self.template_alts % {
'level_name': unicode(path[-1]),
'alt1': unicode(alternatives[0]),
'alt2': unicode(alternatives[1]),
}
limits = ', '.join((self.template_limit % \
tuple(map(unicode,(step[0], step[1]))) for step in path[:-1]))
result = self.template_main % (alternatives, limits)
return result
class QuestionaryGenerator(object):
def __init__(self, hierarchy=None, renderer=None):
self.hierarchy = hierarchy
self.renderer = renderer
def __iter__(self):
for chain in QuestionaryGenerator.traverse( self.hierarchy.levels):
yield chain
@staticmethod
def traverse(levels, chain=None):
if chain is None:
chain = ()
try:
cur_level = levels.next()
except StopIteration:
raise
if len(cur_level['items']) > 1:
for pair in pairs(cur_level['items']):
yield ((chain + (cur_level['name'],)), pair)
for el in cur_level['items']:
for x in QuestionaryGenerator.traverse(levels,
chain + ((cur_level['name'],el),)):
yield x
def pairs(l):
return ((x,y) for i,x in enumerate(l) for y in l[i+1:])
def _test():
import doctest
doctest.testmod()
def get_test_structure():
return [ {
'name': u'Главная цель',
'items': [u'Выбор наиболее приоритетных проблем ж/д системы'],
},
{
'name': u'критерии',
'items': [
u'Скорость движения составов',
u'Оборачиваемость грузов',
# u'Экология',
# u'Социальная сфера',
]
},
{
'name': u'проблемы',
'items': [
u'Пути',
u'Локомотивы',
# u'Вагоны',
# u'Организация движения',
# u'Штат рабочих',
u'Информац. обеспечение',
]
},
]
def test_structure():
h = Hierarchy(get_test_structure())
q = QuestionaryGenerator(hierarchy=h)
r = SimpleHtmlQuestionRenderer()
for question in r.render_all(q):
print question + '\n'
if __name__ == '__main__':
_test()
test_structure()