excel2moodle 0.3.1__py3-none-any.whl → 0.3.3__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.
- excel2moodle/__init__.py +18 -4
- excel2moodle/__main__.py +0 -10
- excel2moodle/core/category.py +4 -33
- excel2moodle/core/dataStructure.py +6 -2
- excel2moodle/core/parser.py +59 -67
- excel2moodle/core/question.py +48 -8
- excel2moodle/core/questionValidator.py +18 -0
- excel2moodle/core/stringHelpers.py +8 -1
- excel2moodle/ui/appUi.py +49 -46
- excel2moodle/ui/dialogs.py +2 -2
- excel2moodle/ui/settings.py +5 -4
- excel2moodle/ui/windowMain.py +414 -0
- {excel2moodle-0.3.1.dist-info → excel2moodle-0.3.3.dist-info}/METADATA +4 -4
- excel2moodle-0.3.3.dist-info/RECORD +31 -0
- {excel2moodle-0.3.1.dist-info → excel2moodle-0.3.3.dist-info}/WHEEL +1 -1
- excel2moodle-0.3.1.dist-info/RECORD +0 -30
- {excel2moodle-0.3.1.dist-info → excel2moodle-0.3.3.dist-info}/licenses/LICENSE +0 -0
- {excel2moodle-0.3.1.dist-info → excel2moodle-0.3.3.dist-info}/top_level.txt +0 -0
excel2moodle/__init__.py
CHANGED
@@ -17,12 +17,27 @@ Functionality
|
|
17
17
|
* Parse Numeric Questions, each into one XML file
|
18
18
|
* create single XML File from a selection of questions
|
19
19
|
"""
|
20
|
+
from importlib import metadata
|
20
21
|
from importlib.metadata import version
|
21
22
|
try:
|
22
23
|
__version__ = version("excel2moodle")
|
23
24
|
except Exception:
|
24
25
|
__version__ = "unknown"
|
25
26
|
|
27
|
+
|
28
|
+
if not __package__ == None:
|
29
|
+
meta = metadata.metadata(__package__)
|
30
|
+
e2mMetadata:dict = {
|
31
|
+
"version": __version__,
|
32
|
+
"name": meta['name'],
|
33
|
+
"description": meta['summary'],
|
34
|
+
"author": meta['author'],
|
35
|
+
"license": meta['license-expression'],
|
36
|
+
"documentation": "https://jbosse3.gitlab.io/excel2moodle",
|
37
|
+
"homepage": meta['project-url'].split()[1],
|
38
|
+
"issues": "https://gitlab.com/jbosse3/excel2moodle/issues",
|
39
|
+
}
|
40
|
+
|
26
41
|
# from excel2moodle.core import klausurGenerator
|
27
42
|
# from excel2moodle.core import numericMultiQ
|
28
43
|
# from excel2moodle.core import questionWriter
|
@@ -36,7 +51,6 @@ from excel2moodle.ui import settings
|
|
36
51
|
from PySide6.QtCore import QObject, Signal
|
37
52
|
import logging as logging
|
38
53
|
from logging import config as logConfig
|
39
|
-
from pathlib import Path
|
40
54
|
|
41
55
|
loggerConfig = {
|
42
56
|
'version': 1,
|
@@ -108,6 +122,6 @@ logging.config.dictConfig(config=loggerConfig)
|
|
108
122
|
qSignalLogger = LogHandler()
|
109
123
|
logger.addHandler(qSignalLogger)
|
110
124
|
|
111
|
-
|
112
|
-
|
113
|
-
|
125
|
+
|
126
|
+
for k,v in e2mMetadata.items():
|
127
|
+
print(f"{k}: \t {v}\n")
|
excel2moodle/__main__.py
CHANGED
@@ -1,21 +1,11 @@
|
|
1
1
|
"""Main Function to make the Package executable"""
|
2
2
|
|
3
|
-
from pathlib import Path
|
4
|
-
|
5
3
|
from PySide6 import QtWidgets, sys
|
6
|
-
import xml.etree.ElementTree as xmlET
|
7
4
|
|
8
5
|
from excel2moodle.core import dataStructure
|
9
6
|
from excel2moodle.ui import appUi as ui
|
10
7
|
from excel2moodle.ui.settings import Settings
|
11
8
|
|
12
|
-
import logging
|
13
|
-
|
14
|
-
katOutPath = None
|
15
|
-
excelFile = None
|
16
|
-
|
17
|
-
logger = logging.getLogger(__name__)
|
18
|
-
|
19
9
|
|
20
10
|
def main()->None:
|
21
11
|
app = QtWidgets.QApplication(sys.argv)
|
excel2moodle/core/category.py
CHANGED
@@ -39,48 +39,19 @@ class Category():
|
|
39
39
|
return self.NAME == other.NAME
|
40
40
|
return False
|
41
41
|
|
42
|
-
def
|
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:
|
42
|
+
def parseQ(self, q:Question, questionData:dict|None=None, xmlTree:ET._Element|None=None)->bool:
|
71
43
|
if q.element is not None:
|
72
44
|
logger.info(f"Question {q.id} is already parsed")
|
73
45
|
return True
|
74
46
|
else:
|
75
|
-
series = self.dataframe[q.number]
|
76
47
|
if q.qtype == "NF":
|
77
|
-
parser = NFQuestionParser( q,
|
48
|
+
parser = NFQuestionParser( q, questionData)
|
78
49
|
logger.debug(f"setup a new NF parser ")
|
79
50
|
elif q.qtype == "MC":
|
80
|
-
parser = MCQuestionParser( q,
|
51
|
+
parser = MCQuestionParser( q, questionData)
|
81
52
|
logger.debug(f"setup a new MC parser ")
|
82
53
|
elif q.qtype == "NFM":
|
83
|
-
parser = NFMQuestionParser( q,
|
54
|
+
parser = NFMQuestionParser( q, questionData)
|
84
55
|
logger.debug(f"setup a new NFM parser ")
|
85
56
|
else:
|
86
57
|
logger.error(f"ERROR, couldn't setup Parser")
|
@@ -46,6 +46,7 @@ class QuestionDB():
|
|
46
46
|
self.spreadSheetPath = sheet
|
47
47
|
self.svgFolder = (self.spreadSheetPath.parent / 'Abbildungen_SVG')
|
48
48
|
self.retrieveCategoriesData()
|
49
|
+
self.parseAll()
|
49
50
|
|
50
51
|
def retrieveCategoriesData(self)->None:
|
51
52
|
"""Scans through the sheet with the metadata for all the question categories
|
@@ -62,6 +63,7 @@ class QuestionDB():
|
|
62
63
|
index_col=0)
|
63
64
|
logger.info("Sucessfully read categoriesMetaData")
|
64
65
|
print(self.categoriesMetaData)
|
66
|
+
self.categories = {}
|
65
67
|
for sh in excelFile.sheet_names:
|
66
68
|
if sh.startswith("KAT"):
|
67
69
|
n = int(sh[4:])
|
@@ -97,7 +99,7 @@ class QuestionDB():
|
|
97
99
|
if check:
|
98
100
|
c.questions[q]=validator.question
|
99
101
|
try:
|
100
|
-
c.parseQ(c.questions[q])
|
102
|
+
c.parseQ(c.questions[q], validator.qdata)
|
101
103
|
except QNotParsedException as e:
|
102
104
|
logger.error(f"Frage {c.questions[q].id} konnte nicht erstellt werden", exc_info=e)
|
103
105
|
|
@@ -119,6 +121,7 @@ class QuestionDB():
|
|
119
121
|
def appendQElements(self,cat:Category, qList:list[Question], tree:ET.Element, includeHeader:bool=True)->None:
|
120
122
|
if includeHeader:
|
121
123
|
tree.append( cat.getCategoryHeader())
|
124
|
+
logger.debug(f"Appended a new category item {cat=}")
|
122
125
|
sameVariant = False
|
123
126
|
variant = 1
|
124
127
|
for q in qList:
|
@@ -132,7 +135,8 @@ class QuestionDB():
|
|
132
135
|
logger.debug(f"Die Fragen-Variante {variant} wurde gewählt")
|
133
136
|
q.assemble(variant)
|
134
137
|
else: print("skipping this question")
|
135
|
-
|
138
|
+
else:
|
139
|
+
q.assemble()
|
136
140
|
tree.append(q.element)
|
137
141
|
else: logger.warning(f"Frage {q} wurde nicht erstellt")
|
138
142
|
return None
|
excel2moodle/core/parser.py
CHANGED
@@ -1,12 +1,6 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
from unicodedata import category
|
4
1
|
from asteval import Interpreter
|
5
2
|
import lxml.etree as ET
|
6
|
-
# import xml.etree.ElementTree as ET
|
7
|
-
from typing import Union
|
8
3
|
from pathlib import Path
|
9
|
-
from numpy import isin
|
10
4
|
import pandas as pd
|
11
5
|
import base64 as base64
|
12
6
|
import logging as logging
|
@@ -20,21 +14,27 @@ from excel2moodle.core import stringHelpers
|
|
20
14
|
from excel2moodle.core.question import Picture, Question
|
21
15
|
import re as re
|
22
16
|
|
17
|
+
from excel2moodle import settings
|
18
|
+
|
23
19
|
|
24
20
|
logger = logging.getLogger(__name__)
|
25
|
-
|
21
|
+
f = Path("../Fragensammlung/Abbildungen_SVG/").resolve()
|
22
|
+
settings.set("core/pictureFolder", f)
|
23
|
+
svgFolder = settings.get("core/pictureFolder", default=f)
|
24
|
+
print(svgFolder)
|
26
25
|
|
27
26
|
class QuestionParser():
|
28
|
-
def __init__(self, question:Question,
|
27
|
+
def __init__(self, question:Question, data:dict):
|
29
28
|
self.question:Question = question
|
30
|
-
self.
|
29
|
+
self.rawInput = data
|
30
|
+
logger.debug(f"The following Data was provided for the question {self.question.id}:\n {self.rawInput =}")
|
31
31
|
self.genFeedbacks:list[XMLTags] = []
|
32
32
|
|
33
33
|
def hasPicture(self)->bool:
|
34
34
|
"""Creates a ``Picture`` object inside ``question``, if the question needs a pic"""
|
35
35
|
|
36
|
-
picKey = self.
|
37
|
-
if picKey != 0 and
|
36
|
+
picKey = self.rawInput[DFIndex.PICTURE]
|
37
|
+
if picKey != 0 and picKey != 'nan':
|
38
38
|
if not hasattr(self.question, 'picture'):
|
39
39
|
self.question.picture = Picture(picKey, svgFolder, self.question)
|
40
40
|
if self.question.picture.ready:
|
@@ -44,31 +44,32 @@ class QuestionParser():
|
|
44
44
|
def setMainText(self)->None:
|
45
45
|
paragraphs:list[ET._Element]=[TextElements.PLEFT.create()]
|
46
46
|
ET.SubElement(paragraphs[0],"b").text = f"ID {self.question.id}"
|
47
|
-
text = self.
|
47
|
+
text = self.rawInput[DFIndex.TEXT]
|
48
48
|
pcount = 0
|
49
49
|
for t in text:
|
50
50
|
if not pd.isna(t):
|
51
51
|
pcount +=1
|
52
52
|
paragraphs.append(TextElements.PLEFT.create())
|
53
53
|
paragraphs[-1].text = t
|
54
|
-
self.question.
|
54
|
+
self.question.qtextParagraphs = paragraphs
|
55
55
|
logger.debug(f"Created main Text {self.question.id} with:{pcount} paragraphs")
|
56
56
|
return None
|
57
|
-
|
57
|
+
|
58
58
|
def setBPoints(self)->None:
|
59
59
|
"""If there bulletPoints are set in the Spreadsheet it creates an unordered List-Element in ``Question.bulletList``"""
|
60
|
-
if DFIndex.BPOINTS in self.
|
61
|
-
bps = self.
|
60
|
+
if DFIndex.BPOINTS in self.rawInput:
|
61
|
+
bps:str = self.rawInput[DFIndex.BPOINTS]
|
62
62
|
try:
|
63
63
|
bulletList = self.formatBulletList(bps)
|
64
64
|
except IndexError as e:
|
65
65
|
raise QNotParsedException(f"konnt Bullet Liste {self.question.id} nicht generieren", self.question.id, exc_info=e)
|
66
|
-
|
67
|
-
|
66
|
+
logger.debug(f"Generated BPoint List: \n {ET.tostring(bulletList, encoding='unicode')}")
|
67
|
+
self.question.bulletList = bulletList
|
68
68
|
return None
|
69
69
|
|
70
70
|
def formatBulletList(self,bps:str)->ET.Element:
|
71
|
-
|
71
|
+
logger.debug(f"Formatting the bulletpoint list")
|
72
|
+
li:list[str] = stringHelpers.stripWhitespace( bps.split(';'))
|
72
73
|
name = []
|
73
74
|
var = []
|
74
75
|
quant = []
|
@@ -81,17 +82,23 @@ class QuestionParser():
|
|
81
82
|
quant.append(sc_split[3])
|
82
83
|
unit.append(sc_split[4])
|
83
84
|
for i in range(0, len(name)):
|
84
|
-
|
85
|
-
|
86
|
-
num_s =
|
87
|
-
else:
|
85
|
+
if re.fullmatch(r"{\w+}", quant[i]):
|
86
|
+
logger.debug(f"Got an variable bulletItem")
|
87
|
+
num_s = quant[i]
|
88
|
+
else:
|
89
|
+
logger.debug(f"Got a normal bulletItem")
|
90
|
+
num = quant[i].split(',')
|
91
|
+
if len(num)==2:
|
92
|
+
num_s = f"{str(num[0])},\\!{str(num[1])}~"
|
93
|
+
else: num_s = f"{str(num[0])},\\!0~"
|
88
94
|
bullet = TextElements.LISTITEM.create()
|
89
95
|
bullet.text=(f"{ name[i] }: \\( {var[i]} = {num_s} \\mathrm{{ {unit[i]} }}\\)\n")
|
90
96
|
unorderedList.append(bullet)
|
91
97
|
return unorderedList
|
92
98
|
|
93
|
-
def
|
94
|
-
|
99
|
+
def appendToTmpEle(self, eleName: str, text:str|DFIndex, txtEle=False, **attribs ):
|
100
|
+
"""Appends the text to the temporary Element"""
|
101
|
+
t = self.rawInput[text] if isinstance(text, DFIndex) else text
|
95
102
|
if txtEle is False:
|
96
103
|
self.tmpEle.append(eth.getElement(eleName, t, **attribs))
|
97
104
|
elif txtEle is True:
|
@@ -107,7 +114,7 @@ class QuestionParser():
|
|
107
114
|
for p in parser:
|
108
115
|
try:
|
109
116
|
for k, v in parserSettings[p][key].items():
|
110
|
-
self.
|
117
|
+
self.appendToTmpEle(k, text=v)
|
111
118
|
except KeyError as e:
|
112
119
|
msg = f"Invalider Input aus den Einstellungen Parser: {type(p) = }"
|
113
120
|
logger.error(msg, exc_info=e)
|
@@ -120,15 +127,16 @@ class QuestionParser():
|
|
120
127
|
Generates an new Question Element stored as ``self.tmpEle:ET.Element``
|
121
128
|
if no Exceptions are raised, ``self.tmpEle`` is passed to ``self.question.element``
|
122
129
|
"""
|
130
|
+
logger.info(f"Starting to parse {self.question.id}")
|
123
131
|
self.tmpEle = ET.Element(XMLTags.QUESTION, type = self.question.moodleType)
|
124
132
|
# self.tmpEle.set(XMLTags.TYPE, self.question.moodleType)
|
125
|
-
self.
|
126
|
-
self.
|
133
|
+
self.appendToTmpEle(XMLTags.NAME, text=DFIndex.NAME, txtEle=True)
|
134
|
+
self.appendToTmpEle(XMLTags.ID, text=self.question.id)
|
127
135
|
if self.hasPicture() :
|
128
136
|
self.tmpEle.append(self.question.picture.element)
|
129
137
|
self.tmpEle.append(ET.Element(XMLTags.QTEXT, format = "html"))
|
130
|
-
self.
|
131
|
-
self.
|
138
|
+
self.appendToTmpEle(XMLTags.POINTS, text=str(self.question.points))
|
139
|
+
self.appendToTmpEle(XMLTags.PENALTY, text="0.3333")
|
132
140
|
self.appendFromSettings()
|
133
141
|
for feedb in self.genFeedbacks:
|
134
142
|
self.tmpEle.append(eth.getFeedBEle(feedb))
|
@@ -186,34 +194,30 @@ class NFQuestionParser(QuestionParser):
|
|
186
194
|
self.genFeedbacks=[XMLTags.GENFEEDB]
|
187
195
|
|
188
196
|
def setAnswers(self)->list[ET.Element]:
|
189
|
-
result = self.
|
197
|
+
result = self.rawInput[DFIndex.RESULT]
|
190
198
|
ansEle:list[ET.Element]=[]
|
191
199
|
ansEle.append(self.getNumericAnsElement( result = result ))
|
192
200
|
return ansEle
|
193
201
|
|
194
202
|
class NFMQuestionParser(QuestionParser):
|
195
|
-
def __init__(self,
|
196
|
-
super().__init__(
|
203
|
+
def __init__(self, *args):
|
204
|
+
super().__init__(*args)
|
197
205
|
self.genFeedbacks=[XMLTags.GENFEEDB]
|
198
206
|
self.astEval = Interpreter()
|
199
207
|
|
200
208
|
def setAnswers(self)->None:
|
201
|
-
equation = self.
|
202
|
-
bps = self.
|
209
|
+
equation = self.rawInput[DFIndex.RESULT]
|
210
|
+
bps = str( self.rawInput[DFIndex.BPOINTS] )
|
203
211
|
ansElementsList:list[ET.Element]=[]
|
204
|
-
varNames:list[str]= self.
|
205
|
-
|
206
|
-
bulletPoints:list[ET.Element] = []
|
212
|
+
varNames:list[str]= self._getVarsList(bps)
|
213
|
+
self.question.variables, number = self._getVariablesDict(varNames)
|
207
214
|
for n in range(number):
|
208
|
-
self._setupAstIntprt(
|
215
|
+
self._setupAstIntprt(self.question.variables, n)
|
209
216
|
result = self.astEval(equation)
|
210
217
|
if isinstance(result, float):
|
211
218
|
ansElementsList.append(self.getNumericAnsElement( result = round(result,3) ))
|
212
|
-
bpli = self.insertVariablesToBPoints(varsDict, bps, n)
|
213
|
-
bulletPoints.append(self.formatBulletList(bpli))
|
214
219
|
self.question.answerVariants = ansElementsList
|
215
|
-
self.
|
216
|
-
self.setVariants(len(bulletPoints))
|
220
|
+
self.setVariants(len(ansElementsList))
|
217
221
|
return None
|
218
222
|
|
219
223
|
def setVariants(self, number:int):
|
@@ -225,38 +229,26 @@ class NFMQuestionParser(QuestionParser):
|
|
225
229
|
self.question.category.maxVariants = number if number <= mvar else mvar
|
226
230
|
|
227
231
|
|
228
|
-
|
229
|
-
def insertVariablesToBPoints(varDict: dict, bulletPoints: str, index: int)-> str:
|
230
|
-
"""
|
231
|
-
Für jeden Eintrag im varDict, wird im bulletPoints String der Substring "{key}" durch value[index] ersetzt
|
232
|
-
"""
|
233
|
-
for k, v in varDict.items():
|
234
|
-
s = r"{" + str(k) + r"}"
|
235
|
-
matcher = re.compile(s)
|
236
|
-
bulletPoints = matcher.sub(str(v[index]), bulletPoints)
|
237
|
-
return bulletPoints
|
238
|
-
|
239
|
-
def _setupAstIntprt(self, var:dict[str, list[str]], index:int)->None:
|
232
|
+
def _setupAstIntprt(self, var:dict[str, list[float|int]], index:int)->None:
|
240
233
|
"""Ubergibt die Parameter mit entsprechenden Variablen-Namen an den asteval-Interpreter.
|
241
234
|
|
242
235
|
Dann kann dieser die equation lesen.
|
243
236
|
"""
|
244
|
-
for
|
245
|
-
|
246
|
-
value = comma.sub(".",v[index])
|
247
|
-
self.astEval.symtable[k] = float(value)
|
237
|
+
for name,value in var.items():
|
238
|
+
self.astEval.symtable[name] = value[index]
|
248
239
|
return None
|
249
240
|
|
250
|
-
def
|
241
|
+
def _getVariablesDict(self, keyList: list)-> tuple[dict[str,list[float]],int]:
|
251
242
|
"""Liest alle Variablen-Listen deren Name in ``keyList`` ist aus dem DataFrame im Column[index]"""
|
252
243
|
dic:dict = {}
|
253
244
|
num:int = 0
|
254
245
|
for k in keyList:
|
255
|
-
val = self.
|
246
|
+
val = self.rawInput[k]
|
256
247
|
if isinstance(val, str) :
|
257
|
-
li =
|
248
|
+
li = stringHelpers.stripWhitespace(val.split(";"))
|
258
249
|
num = len(li)
|
259
|
-
|
250
|
+
vars:list[float] = [ float(i.replace(",",".")) for i in li ]
|
251
|
+
dic[str(k)] = vars
|
260
252
|
else:
|
261
253
|
dic[str(k)] = [str(val)]
|
262
254
|
num = 1
|
@@ -264,7 +256,7 @@ class NFMQuestionParser(QuestionParser):
|
|
264
256
|
return dic, num
|
265
257
|
|
266
258
|
@staticmethod
|
267
|
-
def
|
259
|
+
def _getVarsList(bps: str|list[str])->list:
|
268
260
|
"""
|
269
261
|
Durchsucht den bulletPoints String nach den Variablen, die als "{var}" gekennzeichnet sind
|
270
262
|
"""
|
@@ -308,10 +300,10 @@ class MCQuestionParser(QuestionParser):
|
|
308
300
|
|
309
301
|
|
310
302
|
def setAnswers(self)->list[ET.Element]:
|
311
|
-
ansStyle = self.
|
312
|
-
true = stringHelpers.stripWhitespace(self.
|
303
|
+
ansStyle = self.rawInput[DFIndex.ANSTYPE]
|
304
|
+
true = stringHelpers.stripWhitespace(self.rawInput[DFIndex.TRUE].split(';'))
|
313
305
|
trueAnsList = stringHelpers.texWrapper(true, style=ansStyle)
|
314
|
-
false = stringHelpers.stripWhitespace(self.
|
306
|
+
false = stringHelpers.stripWhitespace(self.rawInput[DFIndex.FALSE].split(';'))
|
315
307
|
falseAnsList= stringHelpers.texWrapper(false, style=ansStyle)
|
316
308
|
truefrac = 1/len(trueAnsList)*100
|
317
309
|
falsefrac = 1/len(trueAnsList)*(-100)
|
excel2moodle/core/question.py
CHANGED
@@ -6,6 +6,8 @@ import base64 as base64
|
|
6
6
|
from excel2moodle.core import category, etHelpers
|
7
7
|
from excel2moodle.core.globals import XMLTags, TextElements, DFIndex, questionTypes, parserSettings
|
8
8
|
from excel2moodle.core.exceptions import QNotParsedException
|
9
|
+
from typing import Match
|
10
|
+
import re as re
|
9
11
|
|
10
12
|
|
11
13
|
logger = logging.getLogger(__name__)
|
@@ -19,14 +21,15 @@ class Question():
|
|
19
21
|
self.parent = parent
|
20
22
|
self.qtype: str = qtype
|
21
23
|
self.moodleType = questionTypes[qtype]
|
22
|
-
self.points = ( points if points
|
24
|
+
self.points = ( points if points != 0 else self.category.points)
|
23
25
|
self.element: ET.Element|None=None
|
24
26
|
self.picture:Picture
|
25
27
|
self.id:str
|
26
|
-
self.
|
27
|
-
self.bulletList:
|
28
|
+
self.qtextParagraphs: list[ET.Element] = []
|
29
|
+
self.bulletList: ET.Element|None = None
|
28
30
|
self.answerVariants: list[ET.Element] = []
|
29
31
|
self.variants:int|None = None
|
32
|
+
self.variables: dict[str, list[float|int]] = {}
|
30
33
|
self.setID()
|
31
34
|
self.standardTags = {
|
32
35
|
"hidden":"false"
|
@@ -41,17 +44,32 @@ class Question():
|
|
41
44
|
return "\n".join(li)
|
42
45
|
|
43
46
|
def assemble(self, variant:int=1)->None:
|
47
|
+
textElements:list[ET.Element] = []
|
48
|
+
textElements.extend(self.qtextParagraphs)
|
49
|
+
logger.debug(f"Starting assembly of { self.id }")
|
44
50
|
if self.element is not None:
|
45
51
|
mainText = self.element.find(XMLTags.QTEXT)
|
52
|
+
logger.debug(f"found existing Text in element {mainText = }")
|
53
|
+
txtele = mainText.find("text")
|
54
|
+
if txtele is not None:
|
55
|
+
mainText.remove(txtele)
|
56
|
+
logger.debug(f"removed prevously existing questiontext")
|
46
57
|
else: raise QNotParsedException("Cant assamble, if element is none", self.id)
|
47
|
-
if
|
48
|
-
|
58
|
+
if self.variants is not None:
|
59
|
+
textElements.append(self.getBPointVariant(variant-1))
|
60
|
+
else:
|
61
|
+
textElements.append(self.bulletList)
|
49
62
|
if hasattr(self, "picture") and self.picture.ready:
|
50
|
-
|
63
|
+
textElements.append(self.picture.htmlTag)
|
51
64
|
mainText.append(self.picture.element)
|
52
|
-
mainText.append(etHelpers.getCdatTxtElement(
|
53
|
-
self.element.insert(3, mainText)
|
65
|
+
mainText.append(etHelpers.getCdatTxtElement(textElements))
|
66
|
+
# self.element.insert(3, mainText)
|
67
|
+
logger.debug(f"inserted MainText to question element")
|
54
68
|
if len( self.answerVariants ) > 0:
|
69
|
+
ans = self.element.find(XMLTags.ANSWER)
|
70
|
+
if ans is not None:
|
71
|
+
self.element.remove(ans)
|
72
|
+
logger.debug("removed previous answer element")
|
55
73
|
self.element.insert(5, self.answerVariants[variant-1])
|
56
74
|
return None
|
57
75
|
|
@@ -60,6 +78,28 @@ class Question():
|
|
60
78
|
self.id: str = f"{self.category.id}{self.number:02d}"
|
61
79
|
else: self.id:str = str(id)
|
62
80
|
|
81
|
+
def getBPointVariant(self, variant:int)->ET.Element:
|
82
|
+
if self.bulletList is None:
|
83
|
+
return None
|
84
|
+
varPlaceholder = re.compile(r"{(\w+)}") # matches {a}, {some_var}, etc.
|
85
|
+
|
86
|
+
def replaceMatch(match: Match[str])->str|int|float:
|
87
|
+
key = match.group(1)
|
88
|
+
if key in self.variables:
|
89
|
+
value = self.variables[key][variant]
|
90
|
+
return f"{value}".replace(".",",\\!")
|
91
|
+
return match.group(0) # keep original if no match
|
92
|
+
|
93
|
+
unorderedList = TextElements.ULIST.create()
|
94
|
+
for li in self.bulletList:
|
95
|
+
listItemText = li.text or ""
|
96
|
+
bullet = TextElements.LISTITEM.create()
|
97
|
+
bullet.text = varPlaceholder.sub(replaceMatch, listItemText)
|
98
|
+
logger.debug(f"Inserted Variables into List: {bullet}")
|
99
|
+
unorderedList.append(bullet)
|
100
|
+
return unorderedList
|
101
|
+
|
102
|
+
|
63
103
|
class Picture():
|
64
104
|
def __init__(self, picKey:str, imgFolder:Path, question:Question):
|
65
105
|
self.pic = picKey
|
@@ -17,6 +17,8 @@ from excel2moodle.core.exceptions import InvalidFieldException, NanException
|
|
17
17
|
import pandas as pd
|
18
18
|
import logging
|
19
19
|
|
20
|
+
logger = logging.getLogger(__name__)
|
21
|
+
|
20
22
|
|
21
23
|
class Validator():
|
22
24
|
def __init__(self, category) -> None:
|
@@ -76,8 +78,24 @@ class Validator():
|
|
76
78
|
if missing is not None:
|
77
79
|
raise InvalidFieldException(msg, id, missing)
|
78
80
|
self._getQuestion()
|
81
|
+
self._getData()
|
79
82
|
return True
|
80
83
|
|
84
|
+
def _getData(self)->None:
|
85
|
+
self.qdata:dict[str, str|float|int|list]={}
|
86
|
+
for idx, val in self.df.items():
|
87
|
+
if not isinstance(idx, str):
|
88
|
+
logger.debug(f"Got a non String key in the spreadsheet, skipping it")
|
89
|
+
continue
|
90
|
+
if idx in self.qdata:
|
91
|
+
if isinstance(self.qdata[idx], list):
|
92
|
+
self.qdata[idx].append(val)
|
93
|
+
else:
|
94
|
+
existing = self.qdata[idx]
|
95
|
+
self.qdata[idx] = [existing, val]
|
96
|
+
else:
|
97
|
+
self.qdata[idx]=val
|
98
|
+
|
81
99
|
def _mandatory(self)->tuple[bool,DFIndex|None]:
|
82
100
|
"""detects if all keys of mandatory are filled with values"""
|
83
101
|
checker = pd.Series.notna(self.df)
|
@@ -8,9 +8,16 @@ import base64 as base64
|
|
8
8
|
def stripWhitespace(stringList):
|
9
9
|
stripped = []
|
10
10
|
for i in stringList:
|
11
|
-
|
11
|
+
s = i.strip()
|
12
|
+
if s:
|
13
|
+
stripped.append(s)
|
12
14
|
return stripped
|
13
15
|
|
16
|
+
def stringToFloat(string:str)->float:
|
17
|
+
string.replace(",",".")
|
18
|
+
return float(string)
|
19
|
+
|
20
|
+
|
14
21
|
def get_bullet_string(s):
|
15
22
|
"""Formatiert die Angaben zum Statischen System hübsch"""
|
16
23
|
split = s.split(';')
|