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