excel2moodle 0.4.1__py3-none-any.whl → 0.4.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 +0 -7
- excel2moodle/__main__.py +2 -2
- excel2moodle/core/__init__.py +0 -10
- excel2moodle/core/category.py +4 -3
- excel2moodle/core/dataStructure.py +116 -61
- excel2moodle/core/etHelpers.py +2 -2
- excel2moodle/core/exceptions.py +2 -2
- excel2moodle/core/globals.py +10 -27
- excel2moodle/core/parser.py +24 -30
- excel2moodle/core/question.py +147 -63
- excel2moodle/core/settings.py +107 -111
- excel2moodle/core/validator.py +36 -55
- excel2moodle/logger.py +7 -4
- excel2moodle/question_types/__init__.py +2 -0
- excel2moodle/question_types/cloze.py +207 -0
- excel2moodle/question_types/mc.py +26 -16
- excel2moodle/question_types/nf.py +17 -3
- excel2moodle/question_types/nfm.py +60 -17
- excel2moodle/ui/{windowEquationChecker.py → UI_equationChecker.py} +98 -78
- excel2moodle/ui/{exportSettingsDialog.py → UI_exportSettingsDialog.py} +55 -4
- excel2moodle/ui/{windowMain.py → UI_mainWindow.py} +32 -39
- excel2moodle/ui/appUi.py +66 -86
- excel2moodle/ui/dialogs.py +40 -2
- excel2moodle/ui/equationChecker.py +70 -0
- excel2moodle/ui/treewidget.py +4 -4
- {excel2moodle-0.4.1.dist-info → excel2moodle-0.4.3.dist-info}/METADATA +2 -3
- excel2moodle-0.4.3.dist-info/RECORD +38 -0
- {excel2moodle-0.4.1.dist-info → excel2moodle-0.4.3.dist-info}/entry_points.txt +0 -3
- excel2moodle/ui/questionPreviewDialog.py +0 -115
- excel2moodle-0.4.1.dist-info/RECORD +0 -37
- /excel2moodle/ui/{variantDialog.py → UI_variantDialog.py} +0 -0
- {excel2moodle-0.4.1.dist-info → excel2moodle-0.4.3.dist-info}/WHEEL +0 -0
- {excel2moodle-0.4.1.dist-info → excel2moodle-0.4.3.dist-info}/licenses/LICENSE +0 -0
- {excel2moodle-0.4.1.dist-info → excel2moodle-0.4.3.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,207 @@
|
|
1
|
+
"""Implementation of the cloze question type.
|
2
|
+
|
3
|
+
This question type is like the NFM but supports multiple fields of answers.
|
4
|
+
All Answers are calculated off an equation using the same variables.
|
5
|
+
"""
|
6
|
+
|
7
|
+
import math
|
8
|
+
import re
|
9
|
+
|
10
|
+
import lxml.etree as ET
|
11
|
+
|
12
|
+
from excel2moodle.core.exceptions import QNotParsedException
|
13
|
+
from excel2moodle.core.globals import (
|
14
|
+
Tags,
|
15
|
+
TextElements,
|
16
|
+
)
|
17
|
+
from excel2moodle.core.question import ParametricQuestion
|
18
|
+
from excel2moodle.core.settings import Tags
|
19
|
+
from excel2moodle.question_types.nfm import NFMQuestionParser
|
20
|
+
|
21
|
+
|
22
|
+
class ClozeQuestion(ParametricQuestion):
|
23
|
+
"""Cloze Question Type."""
|
24
|
+
|
25
|
+
def __init__(self, *args, **kwargs) -> None:
|
26
|
+
super().__init__(*args, **kwargs)
|
27
|
+
self.answerVariants: dict[int, list[float]] = {}
|
28
|
+
self.answerStrings: dict[int, list[str]] = {}
|
29
|
+
self.answerTypes: dict[int, str] = {}
|
30
|
+
self.questionTexts: dict[int, list[ET.Element]] = {}
|
31
|
+
self.partsNum: int = 0
|
32
|
+
|
33
|
+
def _setAnswerElement(self, variant: int = 1) -> None:
|
34
|
+
for part, ans in self.answerVariants.items():
|
35
|
+
result = ans[variant - 1]
|
36
|
+
if self.answerTypes.get(part, None) == "MC":
|
37
|
+
ansStr = ClozeQuestionParser.getMCAnsStr(answers)
|
38
|
+
else:
|
39
|
+
ansStr = ClozeQuestionParser.getNumericAnsStr(
|
40
|
+
result,
|
41
|
+
self.rawData.get(Tags.TOLERANCE),
|
42
|
+
wrongSignCount=self.rawData.get(Tags.WRONGSIGNPERCENT),
|
43
|
+
)
|
44
|
+
ul = TextElements.ULIST.create()
|
45
|
+
item = TextElements.LISTITEM.create()
|
46
|
+
item.text = ansStr
|
47
|
+
ul.append(item)
|
48
|
+
self.questionTexts[part].append(ul)
|
49
|
+
self.logger.debug("Appended Question Parts %s to main text", part)
|
50
|
+
self.questionTexts[part].append(ET.Element("hr"))
|
51
|
+
|
52
|
+
def _assembleMainTextParts(self, variant=0) -> list[ET.Element]:
|
53
|
+
textParts = super()._assembleMainTextParts(variant=variant)
|
54
|
+
self.logger.debug("Appending QuestionParts to main text")
|
55
|
+
for paragraphs in self.questionTexts.values():
|
56
|
+
for par in paragraphs:
|
57
|
+
textParts.append(par)
|
58
|
+
return textParts
|
59
|
+
|
60
|
+
|
61
|
+
class ClozeQuestionParser(NFMQuestionParser):
|
62
|
+
"""Parser for the cloze question type."""
|
63
|
+
|
64
|
+
def __init__(self, *args, **kwargs) -> None:
|
65
|
+
super().__init__(*args, **kwargs)
|
66
|
+
self.question: ClozeQuestion
|
67
|
+
|
68
|
+
def setup(self, question: ClozeQuestion) -> None:
|
69
|
+
self.question: ClozeQuestion = question
|
70
|
+
super().setup(question)
|
71
|
+
|
72
|
+
def _parseAnswers(self) -> None:
|
73
|
+
self._parseAnswerParts()
|
74
|
+
self._parseQuestionParts()
|
75
|
+
|
76
|
+
def _parseAnswerParts(self) -> None:
|
77
|
+
"""Parse the numeric or MC result items."""
|
78
|
+
self.question.answerTypes: dict[int, str] = {
|
79
|
+
self.getPartNumber(key): self.rawInput[key]
|
80
|
+
for key in self.rawInput
|
81
|
+
if key.startswith(Tags.PARTTYPE)
|
82
|
+
}
|
83
|
+
equations: dict[int, str] = {
|
84
|
+
self.getPartNumber(key): self.rawInput[key]
|
85
|
+
for key in self.rawInput
|
86
|
+
if key.startswith(Tags.RESULT)
|
87
|
+
}
|
88
|
+
self.logger.debug("Got the following answers: %s", equations)
|
89
|
+
bps = str(self.rawInput[Tags.BPOINTS])
|
90
|
+
varNames: list[str] = self._getVarsList(bps)
|
91
|
+
numericAnswers: dict[int, list[float]] = {key: [] for key in equations}
|
92
|
+
self.question.variables, number = self._getVariablesDict(varNames)
|
93
|
+
for n in range(number):
|
94
|
+
self.setupAstIntprt(self.question.variables, n)
|
95
|
+
for ansNum, eq in equations.items():
|
96
|
+
result = self.astEval(eq)
|
97
|
+
if isinstance(result, float):
|
98
|
+
firstResult = self.rawInput.get(Tags.FIRSTRESULT)
|
99
|
+
if n == 0 and not math.isclose(result, firstResult, rel_tol=0.01):
|
100
|
+
self.logger.warning(
|
101
|
+
"The calculated result %s differs from given firstResult: %s",
|
102
|
+
result,
|
103
|
+
firstResult,
|
104
|
+
)
|
105
|
+
numericAnswers[ansNum].append(result)
|
106
|
+
else:
|
107
|
+
msg = f"The expression {eq} could not be evaluated."
|
108
|
+
raise QNotParsedException(msg, self.question.id)
|
109
|
+
|
110
|
+
self.question.answerVariants = numericAnswers
|
111
|
+
self._setVariants(number)
|
112
|
+
|
113
|
+
def _parseQuestionParts(self) -> None:
|
114
|
+
"""Generate the question parts aka the clozes."""
|
115
|
+
parts: dict[int, list[str]] = {
|
116
|
+
self.getPartNumber(key): self.rawInput[key]
|
117
|
+
for key in self.rawInput
|
118
|
+
if key.startswith(Tags.QUESTIONPART)
|
119
|
+
}
|
120
|
+
questionParts: dict[int, list[ET.Element]] = {}
|
121
|
+
for number, text in parts.items():
|
122
|
+
questionParts[number] = []
|
123
|
+
for t in text:
|
124
|
+
questionParts[number].append(TextElements.PLEFT.create())
|
125
|
+
questionParts[number][-1].text = t
|
126
|
+
self.logger.debug("The Question Parts are created:", questionParts)
|
127
|
+
self.question.questionTexts = questionParts
|
128
|
+
self.question.partsNum = len(questionParts)
|
129
|
+
self.logger.info("The Question has %s parts", self.question.partsNum)
|
130
|
+
|
131
|
+
# def setMainText(self) -> None:
|
132
|
+
# super().setMainText()
|
133
|
+
# self.question.qtextParagraphs
|
134
|
+
|
135
|
+
def getPartNumber(self, indexKey: str) -> int:
|
136
|
+
"""Return the number of the question Part.
|
137
|
+
|
138
|
+
The number should be given after the `@` sign.
|
139
|
+
This is number is used, to reference the question Text
|
140
|
+
and the expected answer fields together.
|
141
|
+
"""
|
142
|
+
try:
|
143
|
+
num = re.findall(r":(\d+)$", indexKey)[0]
|
144
|
+
except IndexError:
|
145
|
+
msg = f"No :i question Part value given for {indexKey}"
|
146
|
+
raise QNotParsedException(msg, self.question.id)
|
147
|
+
else:
|
148
|
+
return int(num)
|
149
|
+
|
150
|
+
@staticmethod
|
151
|
+
def getNumericAnsStr(
|
152
|
+
result: float,
|
153
|
+
tolerance: float,
|
154
|
+
weight: int = 1,
|
155
|
+
wrongSignCount: int = 50,
|
156
|
+
wrongSignFeedback: str = "your result has the wrong sign (+-)",
|
157
|
+
) -> str:
|
158
|
+
"""Generate the answer string from `result`.
|
159
|
+
|
160
|
+
Parameters.
|
161
|
+
----------
|
162
|
+
weight:
|
163
|
+
The weight of the answer relative to the other answer elements.
|
164
|
+
Of one answer has `weight=2` and two other answers `weight=1`,
|
165
|
+
this answer will be counted as 50% of the questions points.
|
166
|
+
The other two will counted as 25% of the questions points.
|
167
|
+
|
168
|
+
wrongSignCount:
|
169
|
+
If the wrong sign `+` or `-` is given, how much of the points should be given.
|
170
|
+
Interpreted as percent.
|
171
|
+
tolerance:
|
172
|
+
The relative tolerance, as fraction
|
173
|
+
|
174
|
+
"""
|
175
|
+
absTol = f":{round(result * tolerance, 3)}"
|
176
|
+
answerParts: list[str | float] = [
|
177
|
+
"{",
|
178
|
+
weight,
|
179
|
+
":NUMERICAL:=",
|
180
|
+
round(result, 3),
|
181
|
+
absTol,
|
182
|
+
"~%",
|
183
|
+
wrongSignCount,
|
184
|
+
"%",
|
185
|
+
round(result * (-1), 3),
|
186
|
+
absTol,
|
187
|
+
f"#{wrongSignFeedback}",
|
188
|
+
"}",
|
189
|
+
]
|
190
|
+
answerPStrings = [str(part) for part in answerParts]
|
191
|
+
return "".join(answerPStrings)
|
192
|
+
|
193
|
+
@staticmethod
|
194
|
+
def getMCAnsStr(
|
195
|
+
true: list[str],
|
196
|
+
false: list[str],
|
197
|
+
weight: int = 1,
|
198
|
+
) -> str:
|
199
|
+
"""Generate the answer string for the MC answers."""
|
200
|
+
answerParts: list[str | float] = [
|
201
|
+
"{",
|
202
|
+
weight,
|
203
|
+
":MC:",
|
204
|
+
"}",
|
205
|
+
]
|
206
|
+
answerPStrings = [str(part) for part in answerParts]
|
207
|
+
return "".join(answerPString)
|
@@ -1,21 +1,22 @@
|
|
1
1
|
"""Multiple choice Question implementation."""
|
2
2
|
|
3
|
+
from types import UnionType
|
3
4
|
from typing import ClassVar
|
4
5
|
|
5
6
|
import lxml.etree as ET
|
6
7
|
|
7
8
|
import excel2moodle.core.etHelpers as eth
|
8
9
|
from excel2moodle.core import stringHelpers
|
9
|
-
from excel2moodle.core.exceptions import InvalidFieldException
|
10
|
+
from excel2moodle.core.exceptions import InvalidFieldException, QNotParsedException
|
10
11
|
from excel2moodle.core.globals import (
|
11
|
-
|
12
|
+
Tags,
|
12
13
|
TextElements,
|
13
14
|
XMLTags,
|
14
15
|
feedbackStr,
|
15
16
|
)
|
16
17
|
from excel2moodle.core.parser import QuestionParser
|
17
18
|
from excel2moodle.core.question import Picture, Question
|
18
|
-
from excel2moodle.core.settings import
|
19
|
+
from excel2moodle.core.settings import Tags
|
19
20
|
|
20
21
|
|
21
22
|
class MCQuestion(Question):
|
@@ -28,6 +29,12 @@ class MCQuestion(Question):
|
|
28
29
|
"showstandardinstruction": "0",
|
29
30
|
"shownumcorrect": "",
|
30
31
|
}
|
32
|
+
mcOpt: ClassVar[dict[Tags, type | UnionType]] = {}
|
33
|
+
mcMand: ClassVar[dict[Tags, type | UnionType]] = {
|
34
|
+
Tags.TRUE: str,
|
35
|
+
Tags.FALSE: str,
|
36
|
+
Tags.ANSTYPE: str,
|
37
|
+
}
|
31
38
|
|
32
39
|
def __init__(self, *args, **kwargs) -> None:
|
33
40
|
super().__init__(*args, **kwargs)
|
@@ -89,32 +96,35 @@ class MCQuestionParser(QuestionParser):
|
|
89
96
|
elementList[-1].append(self.trueImgs[i].element)
|
90
97
|
return elementList
|
91
98
|
|
92
|
-
def
|
93
|
-
self.answerType = self.rawInput
|
94
|
-
true = stringHelpers.getListFromStr(self.rawInput[DFIndex.TRUE])
|
95
|
-
false = stringHelpers.getListFromStr(self.rawInput[DFIndex.FALSE])
|
99
|
+
def _parseAnswers(self) -> list[ET.Element]:
|
100
|
+
self.answerType = self.rawInput.get(Tags.ANSTYPE)
|
96
101
|
if self.answerType not in self.question.AnsStyles:
|
97
102
|
msg = f"The Answer style: {self.answerType} is not supported"
|
98
|
-
raise InvalidFieldException(msg, self.question.id,
|
103
|
+
raise InvalidFieldException(msg, self.question.id, Tags.ANSTYPE)
|
99
104
|
if self.answerType == "picture":
|
100
|
-
f = self.settings.get(
|
105
|
+
f = self.settings.get(Tags.PICTUREFOLDER)
|
101
106
|
imgFolder = (f / self.question.katName).resolve()
|
102
|
-
w = self.settings.get(
|
107
|
+
w = self.settings.get(Tags.ANSPICWIDTH)
|
103
108
|
self.trueImgs: list[Picture] = [
|
104
|
-
Picture(t, imgFolder, self.question.id, width=w)
|
109
|
+
Picture(t, imgFolder, self.question.id, width=w)
|
110
|
+
for t in self.rawInput.get(Tags.TRUE)
|
105
111
|
]
|
106
112
|
self.falseImgs: list[Picture] = [
|
107
|
-
Picture(t, imgFolder, self.question.id, width=w)
|
113
|
+
Picture(t, imgFolder, self.question.id, width=w)
|
114
|
+
for t in self.rawInput.get(Tags.FALSE)
|
108
115
|
]
|
109
|
-
trueAnsList: list[str] = [pic.htmlTag for pic in self.trueImgs]
|
110
|
-
falseAList: list[str] = [pic.htmlTag for pic in self.falseImgs]
|
116
|
+
trueAnsList: list[str] = [pic.htmlTag for pic in self.trueImgs if pic.ready]
|
117
|
+
falseAList: list[str] = [pic.htmlTag for pic in self.falseImgs if pic.ready]
|
118
|
+
if len(trueAnsList) == 0 or len(falseAList) == 0:
|
119
|
+
msg = "No Answer Pictures could be found"
|
120
|
+
raise QNotParsedException(msg, self.question.id)
|
111
121
|
else:
|
112
122
|
trueAnsList: list[str] = stringHelpers.texWrapper(
|
113
|
-
|
123
|
+
self.rawInput.get(Tags.TRUE), style=self.answerType
|
114
124
|
)
|
115
125
|
self.logger.debug(f"got the following true answers \n {trueAnsList=}")
|
116
126
|
falseAList: list[str] = stringHelpers.texWrapper(
|
117
|
-
|
127
|
+
self.rawInput.get(Tags.FALSE), style=self.answerType
|
118
128
|
)
|
119
129
|
self.logger.debug(f"got the following false answers \n {falseAList=}")
|
120
130
|
truefrac = 1 / len(trueAnsList) * 100
|
@@ -1,9 +1,12 @@
|
|
1
1
|
"""Numerical question implementation."""
|
2
2
|
|
3
|
+
from types import UnionType
|
4
|
+
from typing import ClassVar
|
5
|
+
|
3
6
|
import lxml.etree as ET
|
4
7
|
|
5
8
|
from excel2moodle.core.globals import (
|
6
|
-
|
9
|
+
Tags,
|
7
10
|
XMLTags,
|
8
11
|
)
|
9
12
|
from excel2moodle.core.parser import QuestionParser
|
@@ -14,6 +17,13 @@ class NFQuestion(Question):
|
|
14
17
|
def __init__(self, *args, **kwargs) -> None:
|
15
18
|
super().__init__(*args, **kwargs)
|
16
19
|
|
20
|
+
nfOpt: ClassVar[dict[Tags, type | UnionType]] = {
|
21
|
+
Tags.BPOINTS: str,
|
22
|
+
}
|
23
|
+
nfMand: ClassVar[dict[Tags, type | UnionType]] = {
|
24
|
+
Tags.RESULT: float | int,
|
25
|
+
}
|
26
|
+
|
17
27
|
|
18
28
|
class NFQuestionParser(QuestionParser):
|
19
29
|
"""Subclass for parsing numeric questions."""
|
@@ -22,8 +32,12 @@ class NFQuestionParser(QuestionParser):
|
|
22
32
|
super().__init__()
|
23
33
|
self.genFeedbacks = [XMLTags.GENFEEDB]
|
24
34
|
|
25
|
-
def
|
26
|
-
|
35
|
+
def setup(self, question: NFQuestion) -> None:
|
36
|
+
self.question: NFQuestion = question
|
37
|
+
super().setup(question)
|
38
|
+
|
39
|
+
def _parseAnswers(self) -> list[ET.Element]:
|
40
|
+
result: float = self.rawInput.get(Tags.RESULT)
|
27
41
|
ansEle: list[ET.Element] = []
|
28
42
|
ansEle.append(self.getNumericAnsElement(result=result))
|
29
43
|
return ansEle
|
@@ -1,51 +1,86 @@
|
|
1
1
|
"""Numerical question multi implementation."""
|
2
2
|
|
3
|
+
import math
|
3
4
|
import re
|
4
|
-
from
|
5
|
+
from types import UnionType
|
6
|
+
from typing import TYPE_CHECKING, ClassVar
|
5
7
|
|
6
|
-
import lxml.etree as ET
|
7
8
|
from asteval import Interpreter
|
8
9
|
|
9
10
|
from excel2moodle.core import stringHelpers
|
11
|
+
from excel2moodle.core.exceptions import QNotParsedException
|
10
12
|
from excel2moodle.core.globals import (
|
11
|
-
|
13
|
+
Tags,
|
12
14
|
XMLTags,
|
13
15
|
)
|
14
16
|
from excel2moodle.core.parser import QuestionParser
|
15
|
-
from excel2moodle.core.question import
|
17
|
+
from excel2moodle.core.question import ParametricQuestion
|
16
18
|
|
17
19
|
if TYPE_CHECKING:
|
18
20
|
import lxml.etree as ET
|
19
21
|
|
20
22
|
|
21
|
-
class NFMQuestion(
|
23
|
+
class NFMQuestion(ParametricQuestion):
|
24
|
+
nfmMand: ClassVar[dict[Tags, type | UnionType]] = {
|
25
|
+
Tags.RESULT: str,
|
26
|
+
Tags.BPOINTS: str,
|
27
|
+
}
|
28
|
+
|
22
29
|
def __init__(self, *args, **kwargs) -> None:
|
23
30
|
super().__init__(*args, **kwargs)
|
31
|
+
self.answerVariants: list[ET.Element]
|
32
|
+
|
33
|
+
def _setAnswerElement(self, variant: int = 1) -> None:
|
34
|
+
prevAnsElement = self.element.find(XMLTags.ANSWER)
|
35
|
+
if prevAnsElement is not None:
|
36
|
+
self.element.remove(prevAnsElement)
|
37
|
+
self.logger.debug("removed previous answer element")
|
38
|
+
self.element.insert(5, self.answerVariants[variant - 1])
|
24
39
|
|
25
40
|
|
26
41
|
class NFMQuestionParser(QuestionParser):
|
42
|
+
astEval = Interpreter(with_import=True)
|
43
|
+
|
27
44
|
def __init__(self) -> None:
|
28
45
|
super().__init__()
|
29
46
|
self.genFeedbacks = [XMLTags.GENFEEDB]
|
30
|
-
self.
|
47
|
+
self.question: NFMQuestion
|
48
|
+
module = self.settings.get(Tags.IMPORTMODULE)
|
49
|
+
if module and not type(self).astEval.symtable.get(module):
|
50
|
+
type(self).astEval(f"import {module}")
|
51
|
+
self.logger.warning("Imported '%s' to Asteval symtable", module)
|
52
|
+
|
53
|
+
def setup(self, question: NFMQuestion) -> None:
|
54
|
+
self.question: NFMQuestion = question
|
55
|
+
super().setup(question)
|
31
56
|
|
32
|
-
def
|
33
|
-
equation = self.rawInput
|
34
|
-
bps =
|
57
|
+
def _parseAnswers(self) -> None:
|
58
|
+
equation = self.rawInput.get(Tags.EQUATION)
|
59
|
+
bps = self.rawInput.get(Tags.BPOINTS)
|
35
60
|
ansElementsList: list[ET.Element] = []
|
36
61
|
varNames: list[str] = self._getVarsList(bps)
|
37
62
|
self.question.variables, number = self._getVariablesDict(varNames)
|
38
63
|
for n in range(number):
|
39
|
-
self.
|
40
|
-
result = self.astEval(equation)
|
64
|
+
type(self).setupAstIntprt(self.question.variables, n)
|
65
|
+
result = type(self).astEval(equation)
|
41
66
|
if isinstance(result, float):
|
67
|
+
firstResult = self.rawInput.get(Tags.FIRSTRESULT)
|
68
|
+
if n == 0 and not math.isclose(result, firstResult, rel_tol=0.01):
|
69
|
+
self.logger.warning(
|
70
|
+
"The calculated result %s differs from given firstResult: %s",
|
71
|
+
result,
|
72
|
+
firstResult,
|
73
|
+
)
|
42
74
|
ansElementsList.append(
|
43
75
|
self.getNumericAnsElement(result=round(result, 3)),
|
44
76
|
)
|
77
|
+
else:
|
78
|
+
msg = f"The expression {equation} could not be evaluated."
|
79
|
+
raise QNotParsedException(msg, self.question.id)
|
45
80
|
self.question.answerVariants = ansElementsList
|
46
|
-
self.
|
81
|
+
self._setVariants(len(ansElementsList))
|
47
82
|
|
48
|
-
def
|
83
|
+
def _setVariants(self, number: int) -> None:
|
49
84
|
self.question.variants = number
|
50
85
|
mvar = self.question.category.maxVariants
|
51
86
|
if mvar is None:
|
@@ -53,17 +88,25 @@ class NFMQuestionParser(QuestionParser):
|
|
53
88
|
else:
|
54
89
|
self.question.category.maxVariants = min(number, mvar)
|
55
90
|
|
56
|
-
|
91
|
+
@classmethod
|
92
|
+
def setupAstIntprt(cls, var: dict[str, list[float | int]], index: int) -> None:
|
57
93
|
"""Setup the asteval Interpreter with the variables."""
|
58
94
|
for name, value in var.items():
|
59
|
-
|
95
|
+
cls.astEval.symtable[name] = value[index]
|
60
96
|
|
61
97
|
def _getVariablesDict(self, keyList: list) -> tuple[dict[str, list[float]], int]:
|
62
|
-
"""
|
98
|
+
"""Read variabel values for vars in `keyList` from `question.rawData`.
|
99
|
+
|
100
|
+
Returns
|
101
|
+
-------
|
102
|
+
A dictionary containing a list of values for each variable
|
103
|
+
The number of values for each variable
|
104
|
+
|
105
|
+
"""
|
63
106
|
dic: dict = {}
|
64
107
|
num: int = 0
|
65
108
|
for k in keyList:
|
66
|
-
val = self.rawInput[k]
|
109
|
+
val = self.rawInput[k.lower()]
|
67
110
|
if isinstance(val, str):
|
68
111
|
li = stringHelpers.getListFromStr(val)
|
69
112
|
num = len(li)
|