Newer
Older
import base64
import re
import zipfile
import lib.generic
class QTIConverter(lib.generic.Converter):
""" XLSConverter class (Currently raw xml input is not supported) """
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
accepted_files = ('.zip', '.xml')
def convert(self, filepath):
with zipfile.ZipFile(filepath) as archive:
data = dict(process_archive(archive))
return give_me_structure(data)
file_regex = re.compile(
r'(\d+)__(\d+)__(?P<data>results|qti|tst)_(?P<id>\d+).xml')
task_id_regex = re.compile(r'il_\d+_qst_(?P<task_id>\d+)')
tasks_path = ('./assessment/section')
users = './tst_active/row'
solutions = './tst_solutions/row[@question_fi="%s"]'
lecturer_xpath = ('./MetaData/Lifecycle/Contribute'
'[@Role="Author"]/Entity/text()')
types_xpath = ('./item/itemmetadata/qtimetadata/qtimetadatafield/'
'fieldlabel[text()="QUESTIONTYPE"]/../fieldentry/text()')
def process_qti(tree, only_of_type=('assSourceCode',), **kwargs):
tasks = tree.xpath(tasks_path)[0]
titles = tasks.xpath('./item/@title')
types = tasks.xpath(types_xpath)
ids = [re.search(task_id_regex, ident).group('task_id')
for ident in tasks.xpath('./item/@ident')]
texts = ['\n'.join(flow.xpath('./material/mattext/text()'))
for flow in tasks.xpath('./item/presentation/flow')]
return {id: {'title': title, 'text': text, 'type': type}
for id, type, title, text in zip(ids, types, titles, texts)
if not only_of_type or type in only_of_type}
def process_users(results_tree):
return [dict(row.attrib) for row in results_tree.xpath(users)]
def convert_code(text):
return base64.b64decode(text).decode('utf-8')
def process_solutions(results_tree, task_id):
return {row.attrib['active_fi']: convert_code(row.attrib['value1'])
for row in results_tree.xpath(solutions % task_id)}
def process_results(tree, qti=(), **kwargs):
questions = qti
users = process_users(tree)
id2user = {user['active_id']: user for user in users}
for user in users:
for question_key, question in questions.items():
solutions = process_solutions(tree, question_key)
for user_id, solution in solutions.items():
id2user[user_id]['submissions'].append({'type': question['title'],
'code': solution,
'tests': {}})
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
return users
def process_tst(tree):
title = tree.xpath('./MetaData/General/Title/text()')
lecturer = tree.xpath(lecturer_xpath)
return {'exam': title[0], 'author': lecturer[0]}
def eval_file(archive, match, cache):
funcname = 'process_' + match.group('data')
with archive.open(match.string) as datafile:
tree = etree.parse(datafile)
return globals()[funcname](tree, **cache)
def process_archive(archive):
files = {match.group('data'): match
for match in (re.search(file_regex, name)
for name in archive.NameToInfo)
if match}
order = ('tst', 'qti', 'results')
cache = {}
for key in order:
cache[key] = eval_file(archive, files[key], cache)
return cache
def add_meta(base, data):
base.update(data['tst'])
def add_tasks(base, data):
base['tasks'] = list(data['qti'].values())
ignore_user_fields = ("user_fi",
"active_id",
"usr_id",
"anonymous_id",
"test_fi",
"lastindex",
"tries",
"submitted",
"submittimestamp",
"tstamp",
"user_criteria",)
def add_users(base, data):
for userdata in data['results']:
userdata['identifier'] = userdata['user_fi']
userdata['username'] = userdata['user_fi']
for field in ignore_user_fields:
userdata.pop(field)
base['students'] = data['results']
def give_me_structure(data):
base = {}
add_meta(base, data)
add_tasks(base, data)
add_users(base, data)
return base