From f9a604d502b2fea74d0b5362297fe9b10d2dbc22 Mon Sep 17 00:00:00 2001 From: Jan Maximilian Michal <j.michal@stud.uni-goettingen.de> Date: Wed, 2 Nov 2016 22:59:13 +0000 Subject: [PATCH] Questions are now single python classes and file generation is moore abstract --- README.md | 1 - gen.py | 9 +- hallgrim/IliasXMLCreator/multi.py | 242 +++++++----------- hallgrim/IliasXMLCreator/packer.py | 21 ++ hallgrim/IliasXMLCreator/single.py | 26 +- hallgrim/IliasXMLCreator/xmlBuildingBlocks.py | 63 +++++ scripts/multi_proto.py | 3 +- 7 files changed, 202 insertions(+), 163 deletions(-) create mode 100644 hallgrim/IliasXMLCreator/packer.py create mode 100644 hallgrim/IliasXMLCreator/xmlBuildingBlocks.py diff --git a/README.md b/README.md index 42e3e3f..c865f07 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,6 @@ question. * Add more functionality (gap, alignment, etc.) * Make parsers more robust. * reverse ILIAS authentication mechanism for automated upload. -* Enable LaTeX through custom lexer in mistune. ### Notes diff --git a/gen.py b/gen.py index 039570c..e0520d3 100755 --- a/gen.py +++ b/gen.py @@ -6,7 +6,7 @@ import os import sys # local import -from hallgrim.IliasXMLCreator import multi, single +from hallgrim.IliasXMLCreator import packer from hallgrim.messages import * from hallgrim.parser import * @@ -17,9 +17,9 @@ def filename_to_module(name): def type_selector(type): if 'multiple' in type: - return multi + return 'MULTIPLE CHOICE QUESTION' if 'single' in type: - return single + return 'SINGLE CHOICE QUESTION' def parseme(): @@ -44,6 +44,7 @@ def main(): output, script_name = parseme() script = importlib.import_module(filename_to_module(script_name)) data = { + 'type': type_selector(script.meta['type']), 'description': "_description", 'question_text': markdown(script.task), 'author': script.meta['author'], @@ -55,7 +56,7 @@ def main(): output = os.path.join( 'output', script.meta['title']) + '.xml' if not output else output - type_selector(script.meta['type']).convert_and_print(data, output) + packer.convert_and_print(data, output) info('Processed "{}" and wrote xml to "{}".'.format(script_name, output)) if __name__ == '__main__': diff --git a/hallgrim/IliasXMLCreator/multi.py b/hallgrim/IliasXMLCreator/multi.py index f4a4a21..85a1b44 100644 --- a/hallgrim/IliasXMLCreator/multi.py +++ b/hallgrim/IliasXMLCreator/multi.py @@ -1,147 +1,99 @@ import xml.etree.ElementTree as et - -def simple_elemet(name, text=None, attrib={}): - if not text: - return et.Element(name, attrib=attrib) - node = et.Element(name, attrib=attrib) - node.text = text - return node - - -def qtimetadatafield(label, entry): - root = et.Element('qtimetadatafield') - root.append(simple_elemet('fieldlabel', text=label)) - root.append(simple_elemet('fieldentry', text=entry)) - return root - - -def itemmetadata(type, author, feedback_setting=1): - subroot = et.Element('qtimetadata') - subroot.append(qtimetadatafield('ILIAS_VERSION', '5.1.8 2016-08-03')) - subroot.append(qtimetadatafield('QUESTIONTYPE', type)) - subroot.append(qtimetadatafield('AUTHOR', author)) - subroot.append(qtimetadatafield('additional_cont_edit_mode', 'default')) - subroot.append(qtimetadatafield('externalId', '99.99')) - subroot.append(qtimetadatafield('thumb_size', None)) - subroot.append(qtimetadatafield('feedback_setting', str(feedback_setting))) - root = et.Element('itemmetadata') - root.append(subroot) - return root - -########################################################################## -def material(content): - material = et.Element('material') - material.append(simple_elemet( - 'mattext', - text=content, - attrib={'texttype': 'text/xhtml'} - )) - return material - - -def response_label(content, count): - response_label = et.Element('response_label', attrib={'ident': str(count)}) - response_label.append(material(content)) - return response_label - - -def presentation(title, question_text, questions, shuffle=True): - root = et.Element('presentation', attrib={'label': title}) - flow = et.Element('flow') - response_lid = et.Element( - 'response_lid', attrib={'ident': 'MCMR', 'rcardinality': 'Multiple'}) - render_choice = et.Element( - 'render_choice', attrib={'shuffle': 'Yes' if shuffle else 'No'}) - for i, (answer, _, _) in enumerate(questions): - render_choice.append(response_label(answer, i)) - - root.append(flow) - flow.append(material(question_text)) - flow.append(response_lid) - response_lid.append(render_choice) - return root - -########################################################################## -def respcondition(points, count, correct=True): - root = et.Element('respcondition', attrib={'continue': 'Yes'}) - conditionvar = et.Element('conditionvar') - varequal = simple_elemet( - 'varequal', - text=str(count), - attrib={'respident': 'MCMR'} - ) - - if correct: - conditionvar.append(varequal) - else: - _not = et.Element('not') - _not.append(varequal) - conditionvar.append(_not) - - root.append(conditionvar) - - setvar = simple_elemet( - 'setvar', - text=str(points), - attrib={'action': 'Add'} - ) - root.append(setvar) - - if correct: - displayfeedback = et.Element( - 'displayfeedback', - attrib={'feedbacktype': 'Response', - 'linkrefid': 'response_{}'.format(count)}) - root.append(displayfeedback) - return root - - -def resprocessing(questions): - root = et.Element('resprocessing') - outcomes = et.Element('outcomes') - outcomes.append(simple_elemet('decvar')) - root.append(outcomes) - for i, (_, correct, points) in enumerate(questions): - root.append(respcondition(points if correct else 0, i, True)) - root.append(respcondition(points if not correct else 0, i, False)) - return root - - -########################################################################## -def itemfeedback(count): - root = et.Element( - 'itemfeedback', - attrib={'ident': 'response_{}'.format(count), 'view': 'All'} - ) - flow_mat = et.Element('flow_mat') - flow_mat.append(material('NONE')) - root.append(flow_mat) - return root -########################################################################## - - -def create_xml_tree(type, description, question_text, author, title, maxattempts, shuffle, questions): - root = et.Element('questestinterop') - tree = et.ElementTree(root) - item = et.Element('item', attrib={ - 'ident': 'undefined', - 'title': title, - 'maxattempts': maxattempts - }) - - item.append(simple_elemet('description', text=description)) - item.append(simple_elemet('duration', text='P0Y0M0DT0H30M0S')) - item.append(itemmetadata(type, author)) - item.append(presentation(title, question_text, questions, shuffle)) - item.append(resprocessing(questions)) - for i, _ in enumerate(questions): - item.append(itemfeedback(i)) - root.append(item) - - return tree - - -def convert_and_print(data, output): - tree = create_xml_tree('MULTIPLE CHOICE QUESTION', **data) - tree.write(output, encoding="utf-8", xml_declaration=True) +from hallgrim.IliasXMLCreator.xmlBuildingBlocks import * + +class MultipleChoiceQuestion: + """docstring for MultipleChoiceQuestion""" + def __init__(self, type, description, question_text, author, title, maxattempts, questions, shuffle=True): + self.type = type + self.description = description + self.question_text = question_text + self.author = author + self.title = title + self.maxattempts = maxattempts + self.shuffle = shuffle + self.questions = questions + + self.itemmetadata = self.itemmetadata(feedback_setting=1) + self.presentation = self.presentation() + self.resprocessing = self.resprocessing() + self.xml_representation = self.create_item() + + def __call__(self): + return self.xml_representation + + def is_single(self): + return self.type == 'SINGLE CHOICE QUESTION' + + def itemmetadata(self, feedback_setting=1): + subroot = et.Element('qtimetadata') + subroot.append(qtimetadatafield('ILIAS_VERSION', '5.1.8 2016-08-03')) + subroot.append(qtimetadatafield('QUESTIONTYPE', self.type)) + subroot.append(qtimetadatafield('AUTHOR', self.author)) + subroot.append(qtimetadatafield('additional_cont_edit_mode', 'default')) + subroot.append(qtimetadatafield('externalId', '99.99')) + subroot.append(qtimetadatafield('thumb_size', None)) + subroot.append(qtimetadatafield('feedback_setting', str(feedback_setting))) + root = et.Element('itemmetadata') + root.append(subroot) + return root + + ############################################################################ + def presentation(self): + root = et.Element('presentation', attrib={'label': self.title}) + flow = et.Element('flow') + response_lid = et.Element('response_lid', attrib={ + 'ident': 'MCMR', + 'rcardinality': 'Multiple' if not self.is_single() else 'Single' + }) + render_choice = et.Element( + 'render_choice', attrib={'shuffle': 'Yes' if self.shuffle else 'No'}) + for i, (answer, _, _) in enumerate(self.questions): + render_choice.append(response_label(answer, i)) + + root.append(flow) + flow.append(material(self.question_text)) + flow.append(response_lid) + response_lid.append(render_choice) + return root + + ############################################################################ + def resprocessing(self): + root = et.Element('resprocessing') + outcomes = et.Element('outcomes') + outcomes.append(simple_elemet('decvar')) + root.append(outcomes) + for i, (_, correct, points) in enumerate(self.questions): + root.append(respcondition(points if correct else 0, i, True)) + root.append(respcondition(points if not correct else 0, i, False)) + return root + + ############################################################################ + @staticmethod + def itemfeedback(count): + root = et.Element( + 'itemfeedback', + attrib={'ident': 'response_{}'.format(count), 'view': 'All'} + ) + flow_mat = et.Element('flow_mat') + flow_mat.append(material('NONE')) + root.append(flow_mat) + return root + + ### returns the final object ############################################### + def create_item(self): + """ This method stacks all the previously created structures together""" + item = et.Element('item', attrib={ + 'ident': 'undefined', + 'title': self.title, + 'maxattempts': self.maxattempts + }) + + item.append(simple_elemet('description', text=self.description)) + item.append(simple_elemet('duration', text='P0Y0M0DT0H30M0S')) # 30 min + item.append(self.itemmetadata) + item.append(self.presentation) + item.append(self.resprocessing) + for i, _ in enumerate(self.questions): + item.append(self.itemfeedback(i)) + return item diff --git a/hallgrim/IliasXMLCreator/packer.py b/hallgrim/IliasXMLCreator/packer.py new file mode 100644 index 0000000..baae302 --- /dev/null +++ b/hallgrim/IliasXMLCreator/packer.py @@ -0,0 +1,21 @@ +import xml.etree.ElementTree as et + +from hallgrim.IliasXMLCreator import multi, single + + +def create_xml_tree(item_list): + root = et.Element('questestinterop') + tree = et.ElementTree(root) + for item in item_list: + root.append(item) + return tree + + +def convert_and_print(data, output): + if data['type'] == 'MULTIPLE CHOICE QUESTION': + item = multi.MultipleChoiceQuestion(**data)() + if data['type'] == 'SINGLE CHOICE QUESTION': + item = single.SingleChoiceQuestion(**data)() + + tree = create_xml_tree([item]) + tree.write(output, encoding="utf-8", xml_declaration=True) diff --git a/hallgrim/IliasXMLCreator/single.py b/hallgrim/IliasXMLCreator/single.py index 7d1be39..d9bef1e 100644 --- a/hallgrim/IliasXMLCreator/single.py +++ b/hallgrim/IliasXMLCreator/single.py @@ -1,14 +1,16 @@ -from .multi import * +import xml.etree.ElementTree as et -def resprocessing(questions): - root = et.Element('resprocessing') - outcomes = et.Element('outcomes') - outcomes.append(simple_elemet('decvar')) - root.append(outcomes) - for i, (_, correct, points) in enumerate(questions): - root.append(respcondition(points if correct else 0, i, True)) - return root +from hallgrim.IliasXMLCreator.multi import * -def convert_and_print(data, output): - tree = create_xml_tree('SINGLE CHOICE QUESTION', **data) - tree.write(output, encoding="utf-8", xml_declaration=True) \ No newline at end of file +class SingleChoiceQuestion(MultipleChoiceQuestion): + """ is just a subclass of multi with the exception of this method. + Some other minor differences exists but they are handled in the + parent since they only concert irrelevant fields. """ + def resprocessing(self): + root = et.Element('resprocessing') + outcomes = et.Element('outcomes') + outcomes.append(simple_elemet('decvar')) + root.append(outcomes) + for i, (_, correct, points) in enumerate(self.questions): + root.append(respcondition(points if correct else 0, i, True)) + return root diff --git a/hallgrim/IliasXMLCreator/xmlBuildingBlocks.py b/hallgrim/IliasXMLCreator/xmlBuildingBlocks.py new file mode 100644 index 0000000..ddf2baf --- /dev/null +++ b/hallgrim/IliasXMLCreator/xmlBuildingBlocks.py @@ -0,0 +1,63 @@ +import xml.etree.ElementTree as et + +### static methods ############################################################# +def simple_elemet(name, text=None, attrib={}): + if not text: + return et.Element(name, attrib=attrib) + node = et.Element(name, attrib=attrib) + node.text = text + return node + + +def qtimetadatafield(label, entry): + root = et.Element('qtimetadatafield') + root.append(simple_elemet('fieldlabel', text=label)) + root.append(simple_elemet('fieldentry', text=entry)) + return root + +def material(content): + material = et.Element('material') + material.append(simple_elemet( + 'mattext', + text=content, + attrib={'texttype': 'text/xhtml'} + )) + return material + +def response_label(content, count): + response_label = et.Element('response_label', attrib={'ident': str(count)}) + response_label.append(material(content)) + return response_label + +def respcondition(points, count, correct=True): + root = et.Element('respcondition', attrib={'continue': 'Yes'}) + conditionvar = et.Element('conditionvar') + varequal = simple_elemet( + 'varequal', + text=str(count), + attrib={'respident': 'MCMR'} + ) + + if correct: + conditionvar.append(varequal) + else: + _not = et.Element('not') + _not.append(varequal) + conditionvar.append(_not) + + root.append(conditionvar) + + setvar = simple_elemet( + 'setvar', + text=str(points), + attrib={'action': 'Add'} + ) + root.append(setvar) + + if correct: + displayfeedback = et.Element( + 'displayfeedback', + attrib={'feedbacktype': 'Response', + 'linkrefid': 'response_{}'.format(count)}) + root.append(displayfeedback) + return root diff --git a/scripts/multi_proto.py b/scripts/multi_proto.py index d952973..35b925b 100644 --- a/scripts/multi_proto.py +++ b/scripts/multi_proto.py @@ -5,7 +5,8 @@ meta = { 'points': 0.5, # per correct choice } -task = """ Hier der Anfang der Datei `punkte.csv`, die Komma-getrennte Angaben +task = """ +Hier der Anfang der Datei `punkte.csv`, die Komma-getrennte Angaben zu erreichten Übungspunkten enthält [[Example Formula \\sum_{i=0}^n i]]: ``` -- GitLab