excel2moodle 0.3.3__py3-none-any.whl → 0.3.4__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 +64 -55
- excel2moodle/__main__.py +5 -5
- excel2moodle/core/category.py +53 -33
- excel2moodle/core/dataStructure.py +82 -50
- excel2moodle/core/etHelpers.py +21 -16
- excel2moodle/core/exceptions.py +11 -4
- excel2moodle/core/globals.py +41 -28
- excel2moodle/core/numericMultiQ.py +18 -13
- excel2moodle/core/parser.py +171 -109
- excel2moodle/core/question.py +86 -57
- excel2moodle/core/questionValidator.py +34 -41
- excel2moodle/core/stringHelpers.py +35 -24
- excel2moodle/extra/__init__.py +1 -3
- excel2moodle/extra/equationVerification.py +40 -22
- excel2moodle/ui/appUi.py +91 -68
- excel2moodle/ui/dialogs.py +35 -18
- excel2moodle/ui/settings.py +44 -7
- {excel2moodle-0.3.3.dist-info → excel2moodle-0.3.4.dist-info}/METADATA +4 -3
- excel2moodle-0.3.4.dist-info/RECORD +32 -0
- {excel2moodle-0.3.3.dist-info → excel2moodle-0.3.4.dist-info}/WHEEL +1 -1
- excel2moodle-0.3.4.dist-info/entry_points.txt +2 -0
- excel2moodle-0.3.3.dist-info/RECORD +0 -31
- {excel2moodle-0.3.3.dist-info → excel2moodle-0.3.4.dist-info}/licenses/LICENSE +0 -0
- {excel2moodle-0.3.3.dist-info → excel2moodle-0.3.4.dist-info}/top_level.txt +0 -0
excel2moodle/core/parser.py
CHANGED
@@ -1,75 +1,89 @@
|
|
1
|
-
from asteval import Interpreter
|
2
|
-
import lxml.etree as ET
|
3
|
-
from pathlib import Path
|
4
|
-
import pandas as pd
|
5
1
|
import base64 as base64
|
6
2
|
import logging as logging
|
3
|
+
import re as re
|
4
|
+
from pathlib import Path
|
7
5
|
|
8
|
-
|
9
|
-
|
10
|
-
|
6
|
+
import lxml.etree as ET
|
7
|
+
import pandas as pd
|
8
|
+
from asteval import Interpreter
|
11
9
|
|
12
|
-
|
10
|
+
import excel2moodle.core.etHelpers as eth
|
11
|
+
from excel2moodle import settings
|
13
12
|
from excel2moodle.core import stringHelpers
|
13
|
+
from excel2moodle.core.exceptions import NanException, QNotParsedException
|
14
|
+
from excel2moodle.core.globals import (DFIndex, TextElements, XMLTags,
|
15
|
+
feedbackStr, feedBElements,
|
16
|
+
parserSettings, questionTypes)
|
14
17
|
from excel2moodle.core.question import Picture, Question
|
15
|
-
import re as re
|
16
18
|
|
17
|
-
|
19
|
+
logger = logging.getLogger(__name__)
|
18
20
|
|
19
21
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
svgFolder = settings.get("core/pictureFolder", default=f)
|
24
|
-
print(svgFolder)
|
25
|
-
|
26
|
-
class QuestionParser():
|
27
|
-
def __init__(self, question:Question, data:dict):
|
28
|
-
self.question:Question = question
|
22
|
+
class QuestionParser:
|
23
|
+
def __init__(self, question: Question, data: dict):
|
24
|
+
self.question: Question = question
|
29
25
|
self.rawInput = data
|
30
|
-
logger.debug(
|
31
|
-
|
26
|
+
logger.debug(
|
27
|
+
f"The following Data was provided for the question {
|
28
|
+
self.question.id}:\n {self.rawInput=}"
|
29
|
+
)
|
30
|
+
self.genFeedbacks: list[XMLTags] = []
|
32
31
|
|
33
|
-
def hasPicture(self)->bool:
|
34
|
-
"""Creates a ``Picture`` object inside ``question``,
|
32
|
+
def hasPicture(self) -> bool:
|
33
|
+
"""Creates a ``Picture`` object inside ``question``,
|
34
|
+
if the question needs a pic"""
|
35
35
|
|
36
36
|
picKey = self.rawInput[DFIndex.PICTURE]
|
37
|
-
|
38
|
-
|
37
|
+
svgFolder = settings.get(
|
38
|
+
"core/pictureFolder",
|
39
|
+
default=Path("../Fragensammlung/Abbildungen_SVG").resolve(),
|
40
|
+
)
|
41
|
+
if picKey != 0 and picKey != "nan":
|
42
|
+
if not hasattr(self.question, "picture"):
|
39
43
|
self.question.picture = Picture(picKey, svgFolder, self.question)
|
40
44
|
if self.question.picture.ready:
|
41
45
|
return True
|
42
46
|
return False
|
43
47
|
|
44
|
-
def setMainText(self)->None:
|
45
|
-
paragraphs:list[ET._Element]=[TextElements.PLEFT.create()]
|
46
|
-
ET.SubElement(paragraphs[0],"b").text = f"ID {self.question.id}"
|
48
|
+
def setMainText(self) -> None:
|
49
|
+
paragraphs: list[ET._Element] = [TextElements.PLEFT.create()]
|
50
|
+
ET.SubElement(paragraphs[0], "b").text = f"ID {self.question.id}"
|
47
51
|
text = self.rawInput[DFIndex.TEXT]
|
48
52
|
pcount = 0
|
49
53
|
for t in text:
|
50
54
|
if not pd.isna(t):
|
51
|
-
pcount +=1
|
55
|
+
pcount += 1
|
52
56
|
paragraphs.append(TextElements.PLEFT.create())
|
53
57
|
paragraphs[-1].text = t
|
54
58
|
self.question.qtextParagraphs = paragraphs
|
55
|
-
logger.debug(
|
59
|
+
logger.debug(
|
60
|
+
f"Created main Text {
|
61
|
+
self.question.id} with:{pcount} paragraphs"
|
62
|
+
)
|
56
63
|
return None
|
57
|
-
|
58
|
-
def setBPoints(self)->None:
|
64
|
+
|
65
|
+
def setBPoints(self) -> None:
|
59
66
|
"""If there bulletPoints are set in the Spreadsheet it creates an unordered List-Element in ``Question.bulletList``"""
|
60
67
|
if DFIndex.BPOINTS in self.rawInput:
|
61
|
-
bps:str = self.rawInput[DFIndex.BPOINTS]
|
68
|
+
bps: str = self.rawInput[DFIndex.BPOINTS]
|
62
69
|
try:
|
63
70
|
bulletList = self.formatBulletList(bps)
|
64
71
|
except IndexError as e:
|
65
|
-
raise QNotParsedException(
|
66
|
-
|
72
|
+
raise QNotParsedException(
|
73
|
+
f"konnt Bullet Liste {self.question.id} nicht generieren",
|
74
|
+
self.question.id,
|
75
|
+
exc_info=e,
|
76
|
+
)
|
77
|
+
logger.debug(
|
78
|
+
f"Generated BPoint List: \n {
|
79
|
+
ET.tostring(bulletList, encoding='unicode')}"
|
80
|
+
)
|
67
81
|
self.question.bulletList = bulletList
|
68
82
|
return None
|
69
83
|
|
70
|
-
def formatBulletList(self,bps:str)->ET.Element:
|
71
|
-
logger.debug(
|
72
|
-
li:list[str] = stringHelpers.stripWhitespace(
|
84
|
+
def formatBulletList(self, bps: str) -> ET.Element:
|
85
|
+
logger.debug("Formatting the bulletpoint list")
|
86
|
+
li: list[str] = stringHelpers.stripWhitespace(bps.split(";"))
|
73
87
|
name = []
|
74
88
|
var = []
|
75
89
|
quant = []
|
@@ -87,16 +101,20 @@ class QuestionParser():
|
|
87
101
|
num_s = quant[i]
|
88
102
|
else:
|
89
103
|
logger.debug(f"Got a normal bulletItem")
|
90
|
-
num = quant[i].split(
|
91
|
-
if len(num)==2:
|
104
|
+
num = quant[i].split(",")
|
105
|
+
if len(num) == 2:
|
92
106
|
num_s = f"{str(num[0])},\\!{str(num[1])}~"
|
93
|
-
else:
|
107
|
+
else:
|
108
|
+
num_s = f"{str(num[0])},\\!0~"
|
94
109
|
bullet = TextElements.LISTITEM.create()
|
95
|
-
bullet.text=
|
110
|
+
bullet.text = f"{name[i]}: \\( {var[i]} = {
|
111
|
+
num_s} \\mathrm{{ {unit[i]} }}\\)\n"
|
96
112
|
unorderedList.append(bullet)
|
97
113
|
return unorderedList
|
98
114
|
|
99
|
-
def appendToTmpEle(
|
115
|
+
def appendToTmpEle(
|
116
|
+
self, eleName: str, text: str | DFIndex, txtEle=False, **attribs
|
117
|
+
):
|
100
118
|
"""Appends the text to the temporary Element"""
|
101
119
|
t = self.rawInput[text] if isinstance(text, DFIndex) else text
|
102
120
|
if txtEle is False:
|
@@ -104,11 +122,11 @@ class QuestionParser():
|
|
104
122
|
elif txtEle is True:
|
105
123
|
self.tmpEle.append(eth.getTextElement(eleName, t, **attribs))
|
106
124
|
|
107
|
-
def appendFromSettings(self, key="standards")->None:
|
125
|
+
def appendFromSettings(self, key="standards") -> None:
|
108
126
|
"""Appends 1 to 1 mapped Elements defined in the parserSettings to the element"""
|
109
127
|
parser = ["Parser"]
|
110
128
|
if isinstance(self, MCQuestionParser):
|
111
|
-
|
129
|
+
parser.append("MCParser")
|
112
130
|
elif isinstance(self, NFQuestionParser):
|
113
131
|
parser.append("NFParser")
|
114
132
|
for p in parser:
|
@@ -116,25 +134,26 @@ class QuestionParser():
|
|
116
134
|
for k, v in parserSettings[p][key].items():
|
117
135
|
self.appendToTmpEle(k, text=v)
|
118
136
|
except KeyError as e:
|
119
|
-
msg = f"Invalider Input aus den Einstellungen Parser: {
|
137
|
+
msg = f"Invalider Input aus den Einstellungen Parser: {
|
138
|
+
type(p) =}"
|
120
139
|
logger.error(msg, exc_info=e)
|
121
140
|
raise QNotParsedException(msg, self.question.id, exc_info=e)
|
122
141
|
return None
|
123
142
|
|
124
|
-
def parse(self, xmlTree: ET._Element|None=None)->None:
|
143
|
+
def parse(self, xmlTree: ET._Element | None = None) -> None:
|
125
144
|
"""Parses the Question
|
126
|
-
|
145
|
+
|
127
146
|
Generates an new Question Element stored as ``self.tmpEle:ET.Element``
|
128
147
|
if no Exceptions are raised, ``self.tmpEle`` is passed to ``self.question.element``
|
129
148
|
"""
|
130
149
|
logger.info(f"Starting to parse {self.question.id}")
|
131
|
-
self.tmpEle = ET.Element(XMLTags.QUESTION, type
|
150
|
+
self.tmpEle = ET.Element(XMLTags.QUESTION, type=self.question.moodleType)
|
132
151
|
# self.tmpEle.set(XMLTags.TYPE, self.question.moodleType)
|
133
152
|
self.appendToTmpEle(XMLTags.NAME, text=DFIndex.NAME, txtEle=True)
|
134
153
|
self.appendToTmpEle(XMLTags.ID, text=self.question.id)
|
135
|
-
if self.hasPicture()
|
154
|
+
if self.hasPicture():
|
136
155
|
self.tmpEle.append(self.question.picture.element)
|
137
|
-
self.tmpEle.append(ET.Element(XMLTags.QTEXT, format
|
156
|
+
self.tmpEle.append(ET.Element(XMLTags.QTEXT, format="html"))
|
138
157
|
self.appendToTmpEle(XMLTags.POINTS, text=str(self.question.points))
|
139
158
|
self.appendToTmpEle(XMLTags.PENALTY, text="0.3333")
|
140
159
|
self.appendFromSettings()
|
@@ -152,10 +171,15 @@ class QuestionParser():
|
|
152
171
|
self.question.element = self.tmpEle
|
153
172
|
return None
|
154
173
|
|
155
|
-
def getFeedBEle(
|
174
|
+
def getFeedBEle(
|
175
|
+
self,
|
176
|
+
feedback: XMLTags,
|
177
|
+
text: str | None = None,
|
178
|
+
style: TextElements | None = None,
|
179
|
+
) -> ET.Element:
|
156
180
|
if style is None:
|
157
181
|
span = feedBElements[feedback]
|
158
|
-
else:
|
182
|
+
else:
|
159
183
|
span = style.create()
|
160
184
|
if text is None:
|
161
185
|
text = feedbackStr[feedback]
|
@@ -165,62 +189,86 @@ class QuestionParser():
|
|
165
189
|
par.append(span)
|
166
190
|
ele.append(eth.getCdatTxtElement(par))
|
167
191
|
return ele
|
168
|
-
|
169
|
-
def setAnswers(self)->list[ET.Element]|None:
|
192
|
+
|
193
|
+
def setAnswers(self) -> list[ET.Element] | None:
|
170
194
|
"""Needs to be implemented in the type-specific subclasses"""
|
171
195
|
return None
|
172
196
|
|
173
197
|
@staticmethod
|
174
|
-
def getNumericAnsElement(
|
175
|
-
|
176
|
-
|
177
|
-
|
198
|
+
def getNumericAnsElement(
|
199
|
+
result: int | float,
|
200
|
+
tolerance: int = 0,
|
201
|
+
fraction: int | float = 100,
|
202
|
+
format: str = "moodle_auto_format",
|
203
|
+
) -> ET.Element:
|
178
204
|
"""Returns an ``<answer/>`` Element specific for the numerical Question
|
179
205
|
The element contains those childs:
|
180
206
|
``<text/>`` which holds the value of the answer
|
181
|
-
``<tolerace/>`` with the *relative* tolerance for the result
|
207
|
+
``<tolerace/>`` with the *relative* tolerance for the result in percent
|
182
208
|
``<feedback/>`` with general feedback for a true answer
|
183
209
|
"""
|
184
210
|
|
185
|
-
ansEle:ET.Element = eth.getTextElement(
|
186
|
-
|
187
|
-
|
188
|
-
ansEle.append(
|
211
|
+
ansEle: ET.Element = eth.getTextElement(
|
212
|
+
XMLTags.ANSWER, text=str(result), fraction=str(fraction), format=format
|
213
|
+
)
|
214
|
+
ansEle.append(
|
215
|
+
eth.getFeedBEle(
|
216
|
+
XMLTags.ANSFEEDBACK,
|
217
|
+
feedbackStr["right1Percent"],
|
218
|
+
TextElements.SPANGREEN,
|
219
|
+
)
|
220
|
+
)
|
221
|
+
if tolerance == 0:
|
222
|
+
try:
|
223
|
+
tolerance = int(settings.value(
|
224
|
+
"parser/nf/tolerance"))
|
225
|
+
except ValueError as e:
|
226
|
+
logger.error(
|
227
|
+
f"The tolerance Setting is invalid {e} \n using 1% tolerance",
|
228
|
+
exc_info=e)
|
229
|
+
tolerance = 1
|
230
|
+
logger.debug(f"using tolerance of {tolerance} %")
|
231
|
+
tol = abs(round(result * tolerance, 3))
|
232
|
+
ansEle.append(eth.getElement(XMLTags.TOLERANCE, text=str(tol)))
|
189
233
|
return ansEle
|
190
234
|
|
235
|
+
|
191
236
|
class NFQuestionParser(QuestionParser):
|
192
|
-
def __init__(self, *args)->None:
|
237
|
+
def __init__(self, *args) -> None:
|
193
238
|
super().__init__(*args)
|
194
|
-
self.genFeedbacks=[XMLTags.GENFEEDB]
|
239
|
+
self.genFeedbacks = [XMLTags.GENFEEDB]
|
195
240
|
|
196
|
-
def setAnswers(self)->list[ET.Element]:
|
241
|
+
def setAnswers(self) -> list[ET.Element]:
|
197
242
|
result = self.rawInput[DFIndex.RESULT]
|
198
|
-
ansEle:list[ET.Element]=[]
|
199
|
-
ansEle.append(self.getNumericAnsElement(
|
243
|
+
ansEle: list[ET.Element] = []
|
244
|
+
ansEle.append(self.getNumericAnsElement(result=result))
|
200
245
|
return ansEle
|
201
246
|
|
247
|
+
|
202
248
|
class NFMQuestionParser(QuestionParser):
|
203
249
|
def __init__(self, *args):
|
204
250
|
super().__init__(*args)
|
205
|
-
self.genFeedbacks=[XMLTags.GENFEEDB]
|
251
|
+
self.genFeedbacks = [XMLTags.GENFEEDB]
|
206
252
|
self.astEval = Interpreter()
|
207
253
|
|
208
|
-
def setAnswers(self)->None:
|
254
|
+
def setAnswers(self) -> None:
|
209
255
|
equation = self.rawInput[DFIndex.RESULT]
|
210
|
-
bps = str(
|
211
|
-
ansElementsList:list[ET.Element]=[]
|
212
|
-
varNames:list[str]= self._getVarsList(bps)
|
256
|
+
bps = str(self.rawInput[DFIndex.BPOINTS])
|
257
|
+
ansElementsList: list[ET.Element] = []
|
258
|
+
varNames: list[str] = self._getVarsList(bps)
|
213
259
|
self.question.variables, number = self._getVariablesDict(varNames)
|
214
260
|
for n in range(number):
|
215
261
|
self._setupAstIntprt(self.question.variables, n)
|
216
262
|
result = self.astEval(equation)
|
217
263
|
if isinstance(result, float):
|
218
|
-
ansElementsList.append(
|
264
|
+
ansElementsList.append(
|
265
|
+
self.getNumericAnsElement(result=round(result, 3))
|
266
|
+
)
|
219
267
|
self.question.answerVariants = ansElementsList
|
220
268
|
self.setVariants(len(ansElementsList))
|
221
269
|
return None
|
222
270
|
|
223
|
-
def setVariants(self, number:int):
|
271
|
+
def setVariants(self, number: int):
|
224
272
|
self.question.variants = number
|
225
273
|
mvar = self.question.category.maxVariants
|
226
274
|
if mvar is None:
|
@@ -228,35 +276,34 @@ class NFMQuestionParser(QuestionParser):
|
|
228
276
|
else:
|
229
277
|
self.question.category.maxVariants = number if number <= mvar else mvar
|
230
278
|
|
231
|
-
|
232
|
-
def _setupAstIntprt(self, var:dict[str, list[float|int]], index:int)->None:
|
279
|
+
def _setupAstIntprt(self, var: dict[str, list[float | int]], index: int) -> None:
|
233
280
|
"""Ubergibt die Parameter mit entsprechenden Variablen-Namen an den asteval-Interpreter.
|
234
281
|
|
235
282
|
Dann kann dieser die equation lesen.
|
236
283
|
"""
|
237
|
-
for name,value in var.items():
|
284
|
+
for name, value in var.items():
|
238
285
|
self.astEval.symtable[name] = value[index]
|
239
286
|
return None
|
240
287
|
|
241
|
-
def _getVariablesDict(self, keyList: list)-> tuple[dict[str,list[float]],int]:
|
288
|
+
def _getVariablesDict(self, keyList: list) -> tuple[dict[str, list[float]], int]:
|
242
289
|
"""Liest alle Variablen-Listen deren Name in ``keyList`` ist aus dem DataFrame im Column[index]"""
|
243
|
-
dic:dict = {}
|
244
|
-
num:int = 0
|
290
|
+
dic: dict = {}
|
291
|
+
num: int = 0
|
245
292
|
for k in keyList:
|
246
293
|
val = self.rawInput[k]
|
247
|
-
if isinstance(val, str)
|
248
|
-
li =
|
294
|
+
if isinstance(val, str):
|
295
|
+
li = stringHelpers.stripWhitespace(val.split(";"))
|
249
296
|
num = len(li)
|
250
|
-
vars:list[float] =
|
297
|
+
vars: list[float] = [float(i.replace(",", ".")) for i in li]
|
251
298
|
dic[str(k)] = vars
|
252
|
-
else:
|
299
|
+
else:
|
253
300
|
dic[str(k)] = [str(val)]
|
254
301
|
num = 1
|
255
302
|
print(f"Folgende Variablen wurden gefunden:\n{dic}\n")
|
256
303
|
return dic, num
|
257
304
|
|
258
305
|
@staticmethod
|
259
|
-
def _getVarsList(bps: str|list[str])->list:
|
306
|
+
def _getVarsList(bps: str | list[str]) -> list:
|
260
307
|
"""
|
261
308
|
Durchsucht den bulletPoints String nach den Variablen, die als "{var}" gekennzeichnet sind
|
262
309
|
"""
|
@@ -266,49 +313,64 @@ class NFMQuestionParser(QuestionParser):
|
|
266
313
|
vars.extend(re.findall(r"\{\w\}", str(bps)))
|
267
314
|
else:
|
268
315
|
vars = re.findall(r"\{\w\}", str(bps))
|
269
|
-
variablen=[]
|
316
|
+
variablen = []
|
270
317
|
for v in vars:
|
271
318
|
variablen.append(v.strip("{}"))
|
272
319
|
return variablen
|
273
320
|
|
321
|
+
|
274
322
|
class MCQuestionParser(QuestionParser):
|
275
|
-
def __init__(self, *args)->None:
|
323
|
+
def __init__(self, *args) -> None:
|
276
324
|
super().__init__(*args)
|
277
|
-
self.genFeedbacks=[
|
325
|
+
self.genFeedbacks = [
|
278
326
|
XMLTags.CORFEEDB,
|
279
327
|
XMLTags.PCORFEEDB,
|
280
328
|
XMLTags.INCORFEEDB,
|
281
|
-
|
329
|
+
]
|
282
330
|
|
283
|
-
def getAnsElementsList(
|
331
|
+
def getAnsElementsList(
|
332
|
+
self, answerList: list, fraction: float = 50, format="html"
|
333
|
+
) -> list[ET.Element]:
|
284
334
|
elementList: list[ET.Element] = []
|
285
335
|
for ans in answerList:
|
286
336
|
p = TextElements.PLEFT.create()
|
287
337
|
p.text = str(ans)
|
288
338
|
text = eth.getCdatTxtElement(p)
|
289
|
-
elementList.append(
|
339
|
+
elementList.append(
|
340
|
+
ET.Element(XMLTags.ANSWER, fraction=str(fraction), format=format)
|
341
|
+
)
|
290
342
|
elementList[-1].append(text)
|
291
343
|
if fraction < 0:
|
292
|
-
elementList[-1].append(
|
293
|
-
|
294
|
-
|
344
|
+
elementList[-1].append(
|
345
|
+
eth.getFeedBEle(
|
346
|
+
XMLTags.ANSFEEDBACK,
|
347
|
+
text=feedbackStr["wrong"],
|
348
|
+
style=TextElements.SPANRED,
|
349
|
+
)
|
350
|
+
)
|
295
351
|
elif fraction > 0:
|
296
|
-
elementList[-1].append(
|
297
|
-
|
298
|
-
|
352
|
+
elementList[-1].append(
|
353
|
+
eth.getFeedBEle(
|
354
|
+
XMLTags.ANSFEEDBACK,
|
355
|
+
text=feedbackStr["right"],
|
356
|
+
style=TextElements.SPANGREEN,
|
357
|
+
)
|
358
|
+
)
|
299
359
|
return elementList
|
300
360
|
|
301
|
-
|
302
|
-
def setAnswers(self)->list[ET.Element]:
|
361
|
+
def setAnswers(self) -> list[ET.Element]:
|
303
362
|
ansStyle = self.rawInput[DFIndex.ANSTYPE]
|
304
|
-
true = stringHelpers.stripWhitespace(self.rawInput[DFIndex.TRUE].split(
|
363
|
+
true = stringHelpers.stripWhitespace(self.rawInput[DFIndex.TRUE].split(";"))
|
305
364
|
trueAnsList = stringHelpers.texWrapper(true, style=ansStyle)
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
365
|
+
logger.debug(f"got the following true answers \n {trueAnsList=}")
|
366
|
+
false = stringHelpers.stripWhitespace(self.rawInput[DFIndex.FALSE].split(";"))
|
367
|
+
falseAnsList = stringHelpers.texWrapper(false, style=ansStyle)
|
368
|
+
logger.debug(f"got the following false answers \n {falseAnsList=}")
|
369
|
+
truefrac = 1 / len(trueAnsList) * 100
|
370
|
+
falsefrac = 1 / len(trueAnsList) * (-100)
|
371
|
+
self.tmpEle.find(XMLTags.PENALTY).text = str(round(truefrac / 100, 4))
|
311
372
|
ansList = self.getAnsElementsList(trueAnsList, fraction=round(truefrac, 4))
|
312
|
-
ansList.extend(
|
373
|
+
ansList.extend(
|
374
|
+
self.getAnsElementsList(falseAnsList, fraction=round(falsefrac, 4))
|
375
|
+
)
|
313
376
|
return ansList
|
314
|
-
|