excel2moodle 0.3.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,113 @@
1
+ """This Python program helps to create Moodle questions in less time.
2
+
3
+ The aim is to put alle the information for the questions into a spreadsheet file, and then parse it, to generate Moodle compliant XML-Files.
4
+ Furthermore this program lets you create a single ``.xml``-File with a selection of questions, that then can be imported to a Moodle-Test.
5
+
6
+ Concept
7
+ =========
8
+ The concept is, to store the different questions into categories of similar types and difficulties of questions, for each of which, a separated sheet in the Spreadsheet document should be created.
9
+
10
+ There Should be a sheet called *"Kategorien"*, where an overview over the different categories is stored.
11
+ This sheet stores The names and descriptions, for all categories. The name have to be the same as the actual sheet names with the questions.
12
+ Furthermore the points used for grading, are set in the "Kategorien" sheet
13
+
14
+ Functionality
15
+ ===============
16
+ * Parse multiple Choice Questions, each into one XML file
17
+ * Parse Numeric Questions, each into one XML file
18
+ * create single XML File from a selection of questions
19
+ """
20
+ from importlib.metadata import version
21
+ try:
22
+ __version__ = version("excel2moodle")
23
+ except Exception:
24
+ __version__ = "unknown"
25
+
26
+ # from excel2moodle.core import klausurGenerator
27
+ # from excel2moodle.core import numericMultiQ
28
+ # from excel2moodle.core import questionWriter
29
+ # from excel2moodle.core import questionParser
30
+ # from excel2moodle.core import stringHelpers
31
+ # from excel2moodle.core import globals
32
+ #
33
+ # from excel2moodle.ui import kGeneratorQt
34
+ from excel2moodle.ui import settings
35
+
36
+ from PySide6.QtCore import QObject, Signal
37
+ import logging as logging
38
+ from logging import config as logConfig
39
+ from pathlib import Path
40
+
41
+ loggerConfig = {
42
+ 'version': 1,
43
+ 'disable_existing_loggers': False,
44
+ 'formatters': {
45
+ 'standard': {
46
+ 'format': '%(asctime)s [%(levelname)s] %(name)s: %(message)s'
47
+ },
48
+ },
49
+ 'handlers': {
50
+ 'default': {
51
+ 'level': 'DEBUG',
52
+ 'formatter': 'standard',
53
+ 'class': 'logging.StreamHandler',
54
+ 'stream': 'ext://sys.stdout', # Default is stderr
55
+ },
56
+ },
57
+ 'loggers': {
58
+ '': { # root logger
59
+ 'handlers': ['default'],
60
+ 'level': 'DEBUG',
61
+ 'propagate': True
62
+ },
63
+ 'excel2moodle.questionParser': {
64
+ 'handlers': ['default'],
65
+ 'level': 'DEBUG',
66
+ 'propagate': True
67
+ },
68
+ '__main__': { # if __name__ == '__main__'
69
+ 'handlers': ['default'],
70
+ 'level': 'DEBUG',
71
+ 'propagate': True
72
+ },
73
+ }
74
+ }
75
+
76
+ class QSignaler(QObject):
77
+ signal = Signal(str)
78
+
79
+ class LogHandler(logging.Handler):
80
+ def __init__(self, *args, **kwargs)->None:
81
+ super().__init__(*args,**kwargs)
82
+ self.emitter = QSignaler()
83
+ # Define a formatter with log level and module
84
+ log_format = "[%(levelname)s] %(module)s: %(message)s"
85
+ self.formatter = logging.Formatter(log_format)
86
+ self.setFormatter(self.formatter)
87
+ self.logLevelColors = {
88
+ 'DEBUG': 'gray',
89
+ 'INFO': 'green',
90
+ 'WARNING': 'orange',
91
+ 'ERROR': 'red',
92
+ 'CRITICAL': 'pink'
93
+ }
94
+
95
+ def emit(self, record)->None:
96
+ log_message = self.format(record)
97
+ color = self.logLevelColors.get(record.levelname, 'black')
98
+ prettyMessage = f'<span style="color:{color};">{log_message}</span>'
99
+ self.emitter.signal.emit(prettyMessage)
100
+ return None
101
+
102
+
103
+ settings = settings.Settings()
104
+
105
+ logger = logging.getLogger(__name__)
106
+ logging.config.dictConfig(config=loggerConfig)
107
+
108
+ qSignalLogger = LogHandler()
109
+ logger.addHandler(qSignalLogger)
110
+
111
+ p = Path(__file__).parent
112
+ dirProjectRoot = p.parent.resolve()
113
+ dirDocumentation = (dirProjectRoot / "docs/_build/html")
@@ -0,0 +1,31 @@
1
+ """Main Function to make the Package executable"""
2
+
3
+ from pathlib import Path
4
+
5
+ from PySide6 import QtWidgets, sys
6
+ import xml.etree.ElementTree as xmlET
7
+
8
+ from excel2moodle.core import dataStructure
9
+ from excel2moodle.ui import appUi as ui
10
+ from excel2moodle.ui.settings import Settings
11
+
12
+ import logging
13
+
14
+ katOutPath = None
15
+ excelFile = None
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+
20
+ def main()->None:
21
+ app = QtWidgets.QApplication(sys.argv)
22
+ settings = Settings()
23
+ database:dataStructure.QuestionDB = dataStructure.QuestionDB(settings)
24
+ window = ui.MainWindow(settings, database)
25
+ database.window = window
26
+ window.show()
27
+ sys.exit(app.exec())
28
+
29
+ if __name__ =="__main__":
30
+ main()
31
+
@@ -0,0 +1,13 @@
1
+ """
2
+ These Modules are the heart of the excel2moodle Package
3
+ """
4
+
5
+ # from pandas._config import config
6
+ # from excel2moodle.core import questionWriter
7
+ # from excel2moodle.core import stringHelpers
8
+ # from excel2moodle.core import numericMultiQ
9
+ # from excel2moodle.core import globals
10
+ # from PySide6.QtCore import QObject, Signal
11
+ # import logging as logging
12
+ # from logging import config as logConfig
13
+ #
@@ -0,0 +1,108 @@
1
+
2
+ import logging
3
+ import pandas as pd
4
+ import lxml.etree as ET
5
+
6
+ from excel2moodle.core.exceptions import InvalidFieldException
7
+ from excel2moodle.core.questionValidator import Validator
8
+ logger = logging.getLogger(__name__)
9
+ from excel2moodle.core.question import Question
10
+
11
+ from excel2moodle.core.globals import XMLTags, TextElements, DFIndex, questionTypes, parserSettings
12
+ from excel2moodle.core.parser import NFMQuestionParser, NFQuestionParser, MCQuestionParser, QNotParsedException
13
+
14
+ class Category():
15
+ def __init__(self, n:int, name:str, description:str, dataframe: pd.DataFrame, points:float=0, version:int=0)->None:
16
+ self.n = n
17
+ self.NAME = name
18
+ self.desc = str(description)
19
+ self.dataframe:pd.DataFrame = dataframe
20
+ self.points = points
21
+ self.version = int(version)
22
+ self.questions:dict[int,Question] = {}
23
+ self.maxVariants:int|None = None
24
+ logger.info(f"initializing {self.NAME =}")
25
+
26
+ @property
27
+ def name(self):
28
+ return self.NAME
29
+
30
+ @property
31
+ def id(self):
32
+ return f"{self.version}{self.n:02d}"
33
+
34
+ def __hash__(self) -> int:
35
+ return hash(self.NAME)
36
+
37
+ def __eq__(self, other: object, /) -> bool:
38
+ if isinstance(other, Category):
39
+ return self.NAME == other.NAME
40
+ return False
41
+
42
+ def _getQuestions(self)->None:
43
+ self.questions:dict[int,Question] = {}
44
+ validator = Validator(self)
45
+ for q in self.dataframe.columns:
46
+ logger.debug(f"Starting to check Validity of {q}")
47
+ qdat = self.dataframe[q]
48
+ if isinstance(qdat, pd.Series):
49
+ validator.setup(qdat, q)
50
+ check = False
51
+ try:
52
+ check = validator.validate()
53
+ except InvalidFieldException as e:
54
+ logger.error(f"Frage {self.id}{q:02d} ist invalid.", exc_info=e)
55
+ if check:
56
+ self.questions[q]=validator.question
57
+ try:
58
+ self.parseQ(self.questions[q])
59
+ except QNotParsedException as e:
60
+ logger.error(f"Frage {self.questions[q].id} konnte nicht erstellt werden", exc_info=e)
61
+ return None
62
+
63
+ def parsAll(self, tree: ET.Element = None)->None:
64
+ if tree is None:
65
+ tree = ET.Element("quiz")
66
+ tree.append(self.getCategoryHeader())
67
+ for q in self.questions.values():
68
+ self.parseQ(q, tree)
69
+
70
+ def parseQ(self, q:Question, xmlTree:ET._Element|None=None)->bool:
71
+ if q.element is not None:
72
+ logger.info(f"Question {q.id} is already parsed")
73
+ return True
74
+ else:
75
+ series = self.dataframe[q.number]
76
+ if q.qtype == "NF":
77
+ parser = NFQuestionParser( q, series)
78
+ logger.debug(f"setup a new NF parser ")
79
+ elif q.qtype == "MC":
80
+ parser = MCQuestionParser( q, series)
81
+ logger.debug(f"setup a new MC parser ")
82
+ elif q.qtype == "NFM":
83
+ parser = NFMQuestionParser( q, series)
84
+ logger.debug(f"setup a new NFM parser ")
85
+ else:
86
+ logger.error(f"ERROR, couldn't setup Parser")
87
+ return False
88
+ try:
89
+ parser.parse(xmlTree=xmlTree)
90
+ return True
91
+ except QNotParsedException as e:
92
+ logger.critical(f"The Question {q.id} couldn't be parsed", exc_info=e, stack_info=True)
93
+ return False
94
+ finally:
95
+ del parser
96
+
97
+ def getCategoryHeader(self)->ET.Element:
98
+ """vor den Fragen einer Kategorie wird ein <question type='category'> eingefügt mit Name und Beschreibung"""
99
+ header = ET.Element('question', type='category')
100
+ cat = ET.SubElement(header,"category")
101
+ info = ET.SubElement(header,"info", format='html')
102
+ ET.SubElement(cat,"text").text = f"$module$/top/{self.NAME}"
103
+ ET.SubElement(info,"text").text = str(self.desc)
104
+ ET.SubElement(header,"idnumber").text = str(self.n)
105
+ ET.indent(header)
106
+ return header
107
+
108
+
@@ -0,0 +1,140 @@
1
+ """Main Module which does the heavy lifting
2
+
3
+ At the heart is the class ``xmlTest``
4
+ """
5
+
6
+ from pathlib import Path
7
+ from PySide6 import QtCore
8
+ from PySide6 import QtWidgets
9
+ from PySide6.QtCore import Signal
10
+ from PySide6.QtWidgets import QMainWindow, QMessageBox, QTreeWidget
11
+ import pandas as pd
12
+ import lxml.etree as ET
13
+ import logging
14
+
15
+ from excel2moodle import QSignaler
16
+ from excel2moodle.core import stringHelpers
17
+ from excel2moodle.core.category import Category
18
+ from excel2moodle.ui.dialogs import QuestionVariantDialog
19
+ from excel2moodle.ui.treewidget import QuestionItem, CategoryItem
20
+ from excel2moodle.core.questionValidator import Validator
21
+ from excel2moodle.ui.settings import Settings
22
+ from excel2moodle.core.question import Question
23
+ from excel2moodle.core.exceptions import InvalidFieldException, QNotParsedException
24
+
25
+
26
+ logger = logging.getLogger(__name__)
27
+
28
+
29
+ class QuestionDB():
30
+ """oberste Klasse für den Test"""
31
+ dataChanged = QSignaler()
32
+
33
+ def __init__(self, settings:Settings):
34
+ self.settings = settings
35
+ self.spreadSheetPath = Path()
36
+ self.mainPath = Path()
37
+ self.window:QMainWindow|None = None
38
+ self.version = None
39
+ self.categoriesMetaData = pd.DataFrame()
40
+ self.categories:dict[str,Category]={}
41
+ self.settings.shPathChanged.connect(self.onSheetPathChanged)
42
+
43
+ @QtCore.Slot(Path)
44
+ def onSheetPathChanged(self, sheet:Path)->None:
45
+ logger.debug("Slot, new Spreadsheet triggered")
46
+ self.spreadSheetPath = sheet
47
+ self.svgFolder = (self.spreadSheetPath.parent / 'Abbildungen_SVG')
48
+ self.retrieveCategoriesData()
49
+
50
+ def retrieveCategoriesData(self)->None:
51
+ """Scans through the sheet with the metadata for all the question categories
52
+
53
+ The information that will be shown in the UI like description and points is retrieved from one spreadsheet sheet.
54
+ This method gathers this information and stores it in the ``categoriesMetaData`` dataframe
55
+ """
56
+
57
+ logger.info("Start Parsing the Excel Metadata Sheet\n")
58
+ with open(self.spreadSheetPath, 'rb') as f:
59
+ excelFile = pd.ExcelFile(f)
60
+ self.categoriesMetaData = pd.read_excel(f, sheet_name="Kategorien",
61
+ usecols=["Kategorie", "Beschreibung", "Punkte", "Version"],
62
+ index_col=0)
63
+ logger.info("Sucessfully read categoriesMetaData")
64
+ print(self.categoriesMetaData)
65
+ for sh in excelFile.sheet_names:
66
+ if sh.startswith("KAT"):
67
+ n = int(sh[4:])
68
+ katDf = pd.read_excel(f, sheet_name=str(sh), index_col=0, header=None)
69
+ if not katDf.empty:
70
+ p = self.categoriesMetaData["Punkte"].iloc[n-1]
71
+ points = (p if not pd.isna(p) else 1)
72
+ v = self.categoriesMetaData["Version"].iloc[n-1]
73
+ version = (v if not pd.isna(v) else 0)
74
+ self.categories[sh] = Category(n,sh,
75
+ self.categoriesMetaData["Beschreibung"].iloc[n-1],
76
+ dataframe=katDf,
77
+ points=points,
78
+ version=version)
79
+ self.dataChanged.signal.emit("whoo")
80
+ return None
81
+
82
+
83
+ def parseAll(self):
84
+ self.mainTree = ET.Element("quiz")
85
+ for c in self.categories.values():
86
+ validator = Validator(c)
87
+ for q in c.dataframe.columns:
88
+ logger.debug(f"Starting to check Validity of {q}")
89
+ qdat = c.dataframe[q]
90
+ if isinstance(qdat, pd.Series):
91
+ validator.setup(qdat, q)
92
+ check = False
93
+ try:
94
+ check = validator.validate()
95
+ except InvalidFieldException as e:
96
+ logger.error(f"Question {c.id}{q:02d} is invalid.", exc_info=e)
97
+ if check:
98
+ c.questions[q]=validator.question
99
+ try:
100
+ c.parseQ(c.questions[q])
101
+ except QNotParsedException as e:
102
+ logger.error(f"Frage {c.questions[q].id} konnte nicht erstellt werden", exc_info=e)
103
+
104
+ def appendQuestions(self, questions:list[QuestionItem], file:Path|None=None):
105
+ tree = ET.Element("quiz")
106
+ catdict:dict[Category,list[Question]]={}
107
+ for q in questions:
108
+ logger.debug(f"got a question to append {q=}")
109
+ cat = q.parent().getCategory()
110
+ if cat not in catdict:
111
+ catdict[cat] = []
112
+ print(f"Category is parent of Q {cat=}")
113
+ catdict[cat].append(q.getQuestion())
114
+ for cat, qlist in catdict.items():
115
+ print(f"{cat = }, mit fragen {qlist = }")
116
+ self.appendQElements(cat, qlist, tree=tree, includeHeader=self.settings.value("testGen/includeCats"))
117
+ stringHelpers.printDom(tree, file=file)
118
+
119
+ def appendQElements(self,cat:Category, qList:list[Question], tree:ET.Element, includeHeader:bool=True)->None:
120
+ if includeHeader:
121
+ tree.append( cat.getCategoryHeader())
122
+ sameVariant = False
123
+ variant = 1
124
+ for q in qList:
125
+ if cat.parseQ(q):
126
+ if q.variants is not None:
127
+ if sameVariant is False:
128
+ dialog = QuestionVariantDialog(self.window, q)
129
+ if dialog.exec() == QtWidgets.QDialog.Accepted:
130
+ variant = dialog.variant
131
+ sameVariant = dialog.categoryWide
132
+ logger.debug(f"Die Fragen-Variante {variant} wurde gewählt")
133
+ q.assemble(variant)
134
+ else: print("skipping this question")
135
+ q.assemble
136
+ tree.append(q.element)
137
+ else: logger.warning(f"Frage {q} wurde nicht erstellt")
138
+ return None
139
+
140
+
@@ -0,0 +1,67 @@
1
+ """Helper Module which aids in creating XML-Elements for the Questions
2
+
3
+ This module host different functions. All of them will return an ``lxml.etree.Element``
4
+ """
5
+
6
+ import lxml.etree as ET
7
+ from .globals import DFIndex, XMLTags
8
+ import pandas as pd
9
+ from excel2moodle.core.globals import feedbackStr, TextElements, feedBElements
10
+ import excel2moodle.core.etHelpers as eth
11
+
12
+ def getElement( eleName : str, text: str, **attribs)->ET.Element:
13
+ """Creates an XML-Element with text
14
+
15
+ If ``type(text)``is a ``QuestionFields``, the specific field is directly read.
16
+ Otherwise it will include whatever is ``text`` as a string
17
+ :param **kwargs: are treated as attributes for the Element
18
+ raises:
19
+ NanException if the spreadsheet cell of text:QuestionFields is ``nan``
20
+ """
21
+
22
+ toEle = ET.Element(eleName)
23
+ toEle.text = str(text)
24
+ for k, v in attribs.items():
25
+ toEle.set(k,v)
26
+ return toEle
27
+
28
+
29
+ def getTextElement( eleName:str, text:str|DFIndex, **attribs)->ET.Element:
30
+ """Creates two nested elements: ``eleName`` with child ``text`` which holds the text"""
31
+
32
+ toEle = ET.Element(eleName, **attribs)
33
+ child = getElement("text", text)
34
+ toEle.append(child)
35
+ return toEle
36
+
37
+ def getCdatTxtElement(subEle: ET._Element|list[ET._Element])->ET.Element:
38
+ """Puts all ``subEle`` as ``str`` into a ``<text><![CDATA[...subEle...]]</text>`` element"""
39
+
40
+ textEle = ET.Element(XMLTags.TEXT)
41
+ if isinstance(subEle, list):
42
+ elementString : list= []
43
+ for i in subEle:
44
+ elementString.append(ET.tostring(i, encoding="unicode", pretty_print=True))
45
+ textEle.text = ET.CDATA("".join(elementString))
46
+ return textEle
47
+ else:
48
+ textEle.text = ET.CDATA(ET.tostring(subEle, encoding="unicode", pretty_print=True))
49
+ return textEle
50
+
51
+
52
+ def getFeedBEle(feedback:XMLTags,
53
+ text:str|None=None, style: TextElements | None = None
54
+ )->ET.Element:
55
+ """Gets ET Elements with the feedback for the question."""
56
+ if style is None:
57
+ span = feedBElements[feedback]
58
+ else:
59
+ span = style.create()
60
+ if text is None:
61
+ text = feedbackStr[feedback]
62
+ ele = ET.Element(feedback, format="html")
63
+ par = TextElements.PLEFT.create()
64
+ span.text = text
65
+ par.append(span)
66
+ ele.append(eth.getCdatTxtElement(par))
67
+ return ele
@@ -0,0 +1,20 @@
1
+
2
+ from excel2moodle.core.globals import DFIndex
3
+
4
+
5
+ class QNotParsedException(Exception):
6
+ def __init__(self, message:str, qID:str|int, *args, **kwargs)->None:
7
+ super().__init__(message, *args, **kwargs)
8
+ self.qID = qID
9
+
10
+ class NanException(QNotParsedException):
11
+ def __init__(self, message, qID, field, *args, **kwargs):
12
+ super().__init__(message, qID, *args, **kwargs)
13
+ self.field = field
14
+
15
+ class InvalidFieldException(Exception):
16
+ def __init__(self, message:str, qID:str, field:DFIndex|list[DFIndex], *args: object, **kwargs) -> None:
17
+ super().__init__(message, *args, **kwargs)
18
+ self.field = field
19
+ self.index = qID
20
+
@@ -0,0 +1,128 @@
1
+ from enum import Enum, StrEnum
2
+ import lxml.etree as ET
3
+
4
+ questionTypes = {
5
+ "NF": "numerical",
6
+ "NFM": "numerical",
7
+ "MC": "multichoice",
8
+ }
9
+
10
+ class DFIndex(StrEnum):
11
+ """This Enum holds the identifier string for for the spreadsheet and the string for the xml-tag
12
+
13
+ Each enum corresponds to a list of two values.
14
+ The first Value is the index in the spreadsheet, the second is the name of the xml-tag
15
+ """
16
+
17
+ TEXT = "text"
18
+ BPOINTS = "bulletPoints"
19
+ TRUE = "true"
20
+ FALSE = "false"
21
+ TYPE = "type"
22
+ NAME = "name"
23
+ RESULT = "result"
24
+ PICTURE = "picture"
25
+ NUMBER = "number"
26
+ ANSTYPE = "answerType"
27
+
28
+ class TextElements(Enum):
29
+ PLEFT ="p","text-align: left;"
30
+ SPANRED = "span", "color: rgb(239, 69, 64)"
31
+ SPANGREEN = "span", "color: rgb(152, 202, 62)"
32
+ SPANORANGE = "span","color: rgb(152, 100, 100)"
33
+ ULIST = "ul", "",
34
+ LISTITEM = "li", "text-align: left;",
35
+
36
+ def create(self, tag:str|None=None):
37
+ if tag is None:
38
+ tag, style = self.value
39
+ else: style = self.value[1]
40
+ return ET.Element(tag, dir="ltr", style=style)
41
+
42
+ @property
43
+ def style(self,)->str:
44
+ return self.value[1]
45
+
46
+ class XMLTags(StrEnum):
47
+ def __new__(cls, value:str, dfkey: DFIndex|None = None):
48
+ obj = str.__new__(cls, value)
49
+ obj._value_ = value
50
+ if dfkey is not None:
51
+ obj._dfkey_ = dfkey
52
+ return obj
53
+
54
+ def __init__(self,_:str, dfkey:DFIndex|None =None, getEle=None )->None:
55
+ if isinstance(dfkey, DFIndex):
56
+ self._dfkey_:str=dfkey
57
+ if getEle:
58
+ self._getEle_:object=getEle
59
+
60
+ @property
61
+ def dfkey(self)->str:
62
+ return self._dfkey_
63
+
64
+ def set(self,getEle)->None:
65
+ self._getEle_ = getEle
66
+
67
+ def __repr__(self) -> str:
68
+ msg = []
69
+ msg.append(f"XML Tag {self.value = }")
70
+ if hasattr(self, "_dfkey_"):
71
+ msg.append(f"Df Key {self.dfkey =}")
72
+ return "\n".join(msg)
73
+
74
+ NAME = "name", DFIndex.NAME
75
+ QTEXT = "questiontext", DFIndex.TEXT
76
+ QUESTION = "question"
77
+ TEXT = "text"
78
+ PICTURE = "file", DFIndex.PICTURE
79
+ GENFEEDB = "generalfeedback"
80
+ CORFEEDB = "correctfeedback"
81
+ PCORFEEDB = "partialcorrectfeedback"
82
+ INCORFEEDB = "incorrectfeedback"
83
+ ANSFEEDBACK = "feedback"
84
+ POINTS = "defaultgrade"
85
+ PENALTY = "penalty"
86
+ HIDE = "hidden"
87
+ ID = "idnumber"
88
+ TYPE = "type"
89
+ ANSWER = "answer"
90
+ TOLERANCE = "tolerance"
91
+
92
+ feedBElements = {
93
+ XMLTags.CORFEEDB:TextElements.SPANGREEN.create(),
94
+ XMLTags.PCORFEEDB: TextElements.SPANORANGE.create(),
95
+ XMLTags.INCORFEEDB: TextElements.SPANRED.create(),
96
+ XMLTags.ANSFEEDBACK: TextElements.SPANGREEN.create(),
97
+ XMLTags.GENFEEDB: TextElements.SPANGREEN.create(),
98
+ }
99
+ feedbackStr = {
100
+ XMLTags.CORFEEDB:"Die Frage wurde richtig beantwortet",
101
+ XMLTags.PCORFEEDB: "Die Frage wurde teilweise richtig beantwortet",
102
+ XMLTags.INCORFEEDB: "Die Frage wurde Falsch beantwortet",
103
+ XMLTags.GENFEEDB: "Sie haben eine Antwort abgegeben",
104
+ "right": "richtig",
105
+ "wrong": "falsch",
106
+ "right1Percent": "Gratultaion, die Frage wurde im Rahmen der Toleranz richtig beantwortet"
107
+ }
108
+
109
+ parserSettings = {
110
+ "Parser" : {
111
+ "standards": {
112
+ "hidden": 0,
113
+ },
114
+ },
115
+ "MCParser" : {
116
+ "standards" : {
117
+ "single": "false",
118
+ "shuffleanswers": "true",
119
+ "answernumbering": "abc",
120
+ "showstandardinstruction": "0",
121
+ "shownumcorrect": "",
122
+ },
123
+ },
124
+ "NFParser" : {
125
+ "standards": {},
126
+ },
127
+ }
128
+