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
excel2moodle/__init__.py
CHANGED
excel2moodle/__main__.py
CHANGED
@@ -9,7 +9,7 @@ from PySide6 import QtWidgets, sys
|
|
9
9
|
import excel2moodle
|
10
10
|
from excel2moodle import e2mMetadata, mainLogger
|
11
11
|
from excel2moodle.core import dataStructure
|
12
|
-
from excel2moodle.core.settings import Settings,
|
12
|
+
from excel2moodle.core.settings import Settings, Tags
|
13
13
|
from excel2moodle.logger import loggerConfig
|
14
14
|
from excel2moodle.ui import appUi as ui
|
15
15
|
|
@@ -17,7 +17,7 @@ from excel2moodle.ui import appUi as ui
|
|
17
17
|
def main() -> None:
|
18
18
|
excel2moodle.isMain = True
|
19
19
|
settings = Settings()
|
20
|
-
logfile = Path(settings.get(
|
20
|
+
logfile = Path(settings.get(Tags.LOGFILE)).resolve()
|
21
21
|
e2mMetadata["logfile"] = logfile
|
22
22
|
if logfile.exists() and logfile.is_file():
|
23
23
|
logfile.replace(f"{logfile}.old")
|
excel2moodle/core/__init__.py
CHANGED
@@ -1,11 +1 @@
|
|
1
1
|
"""These Modules are the heart of the excel2moodle Package."""
|
2
|
-
|
3
|
-
# from pandas._config import config
|
4
|
-
# from excel2moodle.core import questionWriter
|
5
|
-
# from excel2moodle.core import stringHelpers
|
6
|
-
# from excel2moodle.core import numericMultiQ
|
7
|
-
# from excel2moodle.core import globals
|
8
|
-
# from PySide6.QtCore import QObject, Signal
|
9
|
-
# import logging as logging
|
10
|
-
# from logging import config as logConfig
|
11
|
-
#
|
excel2moodle/core/category.py
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
import logging
|
2
|
+
import re
|
2
3
|
from typing import TYPE_CHECKING
|
3
4
|
|
4
5
|
import lxml.etree as ET
|
@@ -15,7 +16,6 @@ class Category:
|
|
15
16
|
|
16
17
|
def __init__(
|
17
18
|
self,
|
18
|
-
n: int,
|
19
19
|
name: str,
|
20
20
|
description: str,
|
21
21
|
dataframe: pd.DataFrame,
|
@@ -23,8 +23,9 @@ class Category:
|
|
23
23
|
version: int = 0,
|
24
24
|
) -> None:
|
25
25
|
"""Instantiate a new Category object."""
|
26
|
-
self.n = n
|
27
26
|
self.NAME = name
|
27
|
+
match = re.search(r"\d+$", self.NAME)
|
28
|
+
self.n: int = int(match.group(0)) if match else 99
|
28
29
|
self.desc = str(description)
|
29
30
|
self.dataframe: pd.DataFrame = dataframe
|
30
31
|
self.points = points
|
@@ -56,6 +57,6 @@ class Category:
|
|
56
57
|
info = ET.SubElement(header, "info", format="html")
|
57
58
|
ET.SubElement(cat, "text").text = f"$module$/top/{self.NAME}"
|
58
59
|
ET.SubElement(info, "text").text = str(self.desc)
|
59
|
-
ET.SubElement(header, "idnumber").text =
|
60
|
+
ET.SubElement(header, "idnumber").text = self.id
|
60
61
|
ET.indent(header)
|
61
62
|
return header
|
@@ -4,6 +4,7 @@ At the heart is the class ``xmlTest``
|
|
4
4
|
"""
|
5
5
|
|
6
6
|
import logging
|
7
|
+
import sys
|
7
8
|
from concurrent.futures import ProcessPoolExecutor, as_completed
|
8
9
|
from pathlib import Path
|
9
10
|
from typing import TYPE_CHECKING
|
@@ -16,12 +17,13 @@ from PySide6.QtCore import QObject, Signal
|
|
16
17
|
from excel2moodle.core import stringHelpers
|
17
18
|
from excel2moodle.core.category import Category
|
18
19
|
from excel2moodle.core.exceptions import InvalidFieldException, QNotParsedException
|
19
|
-
from excel2moodle.core.globals import
|
20
|
+
from excel2moodle.core.globals import Tags
|
20
21
|
from excel2moodle.core.question import Question
|
21
|
-
from excel2moodle.core.settings import Settings
|
22
|
+
from excel2moodle.core.settings import Settings
|
22
23
|
from excel2moodle.core.validator import Validator
|
23
24
|
from excel2moodle.logger import LogAdapterQuestionID
|
24
25
|
from excel2moodle.question_types import QuestionTypeMapping
|
26
|
+
from excel2moodle.question_types.cloze import ClozeQuestion, ClozeQuestionParser
|
25
27
|
from excel2moodle.question_types.mc import MCQuestion, MCQuestionParser
|
26
28
|
from excel2moodle.question_types.nf import NFQuestion, NFQuestionParser
|
27
29
|
from excel2moodle.question_types.nfm import NFMQuestion, NFMQuestionParser
|
@@ -49,6 +51,7 @@ def processSheet(sheetPath: str, categoryName: str) -> pd.DataFrame:
|
|
49
51
|
sheet_name=str(categoryName),
|
50
52
|
index_col=0,
|
51
53
|
header=None,
|
54
|
+
engine="calamine",
|
52
55
|
)
|
53
56
|
|
54
57
|
|
@@ -64,6 +67,7 @@ class QuestionDB:
|
|
64
67
|
nfParser: NFQuestionParser = NFQuestionParser()
|
65
68
|
nfmParser: NFMQuestionParser = NFMQuestionParser()
|
66
69
|
mcParser: MCQuestionParser = MCQuestionParser()
|
70
|
+
clozeParser: ClozeQuestionParser = ClozeQuestionParser()
|
67
71
|
|
68
72
|
def __init__(self, settings: Settings) -> None:
|
69
73
|
self.settings = settings
|
@@ -72,7 +76,18 @@ class QuestionDB:
|
|
72
76
|
self.categoriesMetaData: pd.DataFrame
|
73
77
|
self.categories: dict[str, Category]
|
74
78
|
|
75
|
-
|
79
|
+
@property
|
80
|
+
def spreadsheet(self) -> Path:
|
81
|
+
return self._spreadsheet
|
82
|
+
|
83
|
+
@spreadsheet.setter
|
84
|
+
def spreadsheet(self, sheet) -> None:
|
85
|
+
self.settings.clear()
|
86
|
+
self._spreadsheet = sheet
|
87
|
+
self.settings.set(Tags.SPREADSHEETPATH, sheet)
|
88
|
+
logger.info("saved new spreadsheet %s", sheet)
|
89
|
+
|
90
|
+
def readCategoriesMetadata(self, sheetPath: Path | None = None) -> pd.DataFrame:
|
76
91
|
"""Read the metadata and questions from the spreadsheet.
|
77
92
|
|
78
93
|
Get the category data from the spreadsheet and stores it in the
|
@@ -80,31 +95,56 @@ class QuestionDB:
|
|
80
95
|
Setup the categories and store them in ``self.categories = {}``
|
81
96
|
Pass the question data to the categories.
|
82
97
|
"""
|
98
|
+
sheetPath = sheetPath if sheetPath else self.spreadsheet
|
83
99
|
logger.info("Start Parsing the Excel Metadata Sheet\n")
|
84
100
|
with Path(sheetPath).open("rb") as f:
|
85
101
|
settingDf = pd.read_excel(
|
86
102
|
f,
|
87
103
|
sheet_name="settings",
|
88
104
|
index_col=0,
|
105
|
+
engine="calamine",
|
89
106
|
)
|
90
107
|
logger.debug("Found the settings: \n\t%s", settingDf)
|
91
|
-
self._setProjectSettings(settingDf)
|
108
|
+
self._setProjectSettings(settingDf, mainPath=sheetPath.parent)
|
92
109
|
with Path(sheetPath).open("rb") as f:
|
93
110
|
self.categoriesMetaData = pd.read_excel(
|
94
111
|
f,
|
95
|
-
sheet_name=self.settings.get(
|
112
|
+
sheet_name=self.settings.get(Tags.CATEGORIESSHEET),
|
96
113
|
index_col=0,
|
114
|
+
engine="calamine",
|
97
115
|
)
|
98
116
|
logger.info(
|
99
117
|
"Sucessfully read categoriesMetaData \n %s", self.categoriesMetaData
|
100
118
|
)
|
119
|
+
return self.categoriesMetaData
|
101
120
|
|
102
|
-
def _setProjectSettings(
|
121
|
+
def _setProjectSettings(
|
122
|
+
self, settings: pd.DataFrame, mainPath: Path | None = None
|
123
|
+
) -> None:
|
124
|
+
settings = self.harmonizeDFIndex(settings)
|
103
125
|
for tag, value in settings.iterrows():
|
104
|
-
|
126
|
+
value = value.iloc[0]
|
127
|
+
if tag == Tags.TOLERANCE:
|
128
|
+
tol = value if value <= 1 else value / 100
|
129
|
+
self.settings.set(Tags.TOLERANCE, tol)
|
130
|
+
else:
|
131
|
+
self.settings.set(tag, value)
|
132
|
+
if Tags.IMPORTMODULE in settings.index:
|
133
|
+
logger.warning(
|
134
|
+
"Appending: %s to sys.path. All names defined by it will be usable",
|
135
|
+
mainPath,
|
136
|
+
)
|
137
|
+
sys.path.append(str(mainPath))
|
138
|
+
imgFolder = self.settings.get(Tags.SPREADSHEETPATH).parent / self.settings.get(
|
139
|
+
Tags.PICTURESUBFOLDER
|
140
|
+
)
|
141
|
+
if Tags.PICTURESUBFOLDER not in settings.index:
|
142
|
+
logger.warning("You didn't specify an image Folder. This may cause errors.")
|
143
|
+
self.settings.set(Tags.PICTUREFOLDER, imgFolder.resolve())
|
105
144
|
|
106
|
-
def initAllCategories(self, sheetPath: Path) -> None:
|
145
|
+
def initAllCategories(self, sheetPath: Path | None = None) -> None:
|
107
146
|
"""Read all category sheets and initialize all Categories."""
|
147
|
+
sheetPath = sheetPath if sheetPath else self.spreadsheet
|
108
148
|
if not hasattr(self, "categoriesMetaData"):
|
109
149
|
logger.error("Can't process the Categories without Metadata")
|
110
150
|
return
|
@@ -112,19 +152,19 @@ class QuestionDB:
|
|
112
152
|
self.categories.clear()
|
113
153
|
else:
|
114
154
|
self.categories: dict[str, Category] = {}
|
115
|
-
with
|
116
|
-
excelFile = pd.ExcelFile(f)
|
155
|
+
with pd.ExcelFile(sheetPath, engine="calamine") as excelFile:
|
117
156
|
for categoryName in excelFile.sheet_names:
|
118
157
|
logger.debug("Starting to read category %s", categoryName)
|
119
|
-
if categoryName.
|
120
|
-
self.initCategory(
|
158
|
+
if categoryName in self.categoriesMetaData.index:
|
159
|
+
self.initCategory(categoryName, sheetPath=sheetPath)
|
121
160
|
|
122
|
-
def asyncInitAllCategories(self, sheetPath: Path) -> None:
|
161
|
+
def asyncInitAllCategories(self, sheetPath: Path | None = None) -> None:
|
123
162
|
"""Read all category sheets asynchron and initialize all Categories.
|
124
163
|
|
125
164
|
It does the same as `initAllCategories` but the parsing of the excelfile
|
126
165
|
is done asynchron via `concurrent.futures.ProcessPoolExecutor`
|
127
166
|
"""
|
167
|
+
sheetPath = sheetPath if sheetPath else self.spreadsheet
|
128
168
|
if not hasattr(self, "categoriesMetaData"):
|
129
169
|
logger.error("Can't process the Categories without Metadata")
|
130
170
|
return
|
@@ -132,71 +172,86 @@ class QuestionDB:
|
|
132
172
|
self.categories.clear()
|
133
173
|
else:
|
134
174
|
self.categories: dict[str, Category] = {}
|
135
|
-
|
136
|
-
with
|
137
|
-
|
138
|
-
|
139
|
-
|
175
|
+
sheetNames = []
|
176
|
+
with pd.ExcelFile(sheetPath, engine="calamine") as excelFile:
|
177
|
+
sheetNames = [
|
178
|
+
name
|
179
|
+
for name in excelFile.sheet_names
|
180
|
+
if name in self.categoriesMetaData.index
|
140
181
|
]
|
141
|
-
logger.debug("found those
|
182
|
+
logger.debug("found those category sheets: \n %s ", sheetNames)
|
142
183
|
with ProcessPoolExecutor() as executor:
|
143
184
|
futures = {
|
144
185
|
executor.submit(processSheet, str(sheetPath), sheet): sheet
|
145
|
-
for sheet in
|
186
|
+
for sheet in sheetNames
|
146
187
|
}
|
147
188
|
for future in as_completed(futures):
|
148
189
|
categoryName = futures[future]
|
149
190
|
try:
|
150
191
|
categoryDataF = future.result()
|
151
|
-
|
152
|
-
self._setupCategory(categoryDataF, categoryName, categoryNumber)
|
192
|
+
self._setupCategory(categoryDataF, categoryName)
|
153
193
|
logger.debug("Finished processing %s", categoryName)
|
154
194
|
except Exception as e:
|
155
195
|
logger.exception("Error processing sheet %s: %s", categoryName, e)
|
156
196
|
logger.debug("Future exception: %s", future.exception())
|
157
197
|
|
158
|
-
def initCategory(
|
159
|
-
|
160
|
-
|
198
|
+
def initCategory(
|
199
|
+
self, categoryName: str, sheetPath: Path | None = None
|
200
|
+
) -> bool | Category:
|
201
|
+
"""Read `categoryName` from the ``sheetPath`` and initialize the category.
|
202
|
+
Returns the Category and appends it to `self.categories`.
|
203
|
+
"""
|
204
|
+
sheetPath = sheetPath if sheetPath else self.spreadsheet
|
161
205
|
katDf = pd.read_excel(
|
162
206
|
sheetPath,
|
163
207
|
sheet_name=str(categoryName),
|
164
208
|
index_col=0,
|
165
209
|
header=None,
|
210
|
+
engine="calamine",
|
166
211
|
)
|
167
212
|
if not katDf.empty:
|
168
213
|
logger.debug("Sucessfully read the Dataframe for cat %s", categoryName)
|
169
|
-
self._setupCategory(katDf, categoryName
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
) ->
|
214
|
+
return self._setupCategory(katDf, categoryName)
|
215
|
+
return False
|
216
|
+
|
217
|
+
@staticmethod
|
218
|
+
def harmonizeDFIndex(dataframe: pd.DataFrame) -> pd.DataFrame:
|
219
|
+
"""Convert the index strings to lowercase without whitespace."""
|
220
|
+
index = dataframe.index.str.lower()
|
221
|
+
harmonizedIdx = ["".join(i.split()) if pd.notna(i) else i for i in index]
|
222
|
+
dataframe.index = pd.Index(harmonizedIdx)
|
223
|
+
return dataframe
|
224
|
+
|
225
|
+
def _setupCategory(self, categoryDf: pd.DataFrame, categoryName: str) -> Category:
|
174
226
|
"""Setup the category from the ``dataframe``.
|
227
|
+
|
175
228
|
:emits: categoryReady(self) Signal.
|
176
|
-
"""
|
229
|
+
"""
|
230
|
+
categoryDf = self.harmonizeDFIndex(categoryDf)
|
177
231
|
points = (
|
178
|
-
self.categoriesMetaData["points"].
|
232
|
+
self.categoriesMetaData["points"].loc[categoryName]
|
179
233
|
if "points" in self.categoriesMetaData
|
180
|
-
and not pd.isna(self.categoriesMetaData["points"]).
|
181
|
-
else self.settings.get(
|
234
|
+
and not pd.isna(self.categoriesMetaData["points"]).loc[categoryName]
|
235
|
+
else self.settings.get(Tags.POINTS)
|
182
236
|
)
|
183
237
|
version = (
|
184
|
-
self.categoriesMetaData["version"].
|
238
|
+
self.categoriesMetaData["version"].loc[categoryName]
|
185
239
|
if "version" in self.categoriesMetaData
|
186
|
-
and not pd.isna(self.categoriesMetaData["version"].
|
187
|
-
else self.settings.get(
|
240
|
+
and not pd.isna(self.categoriesMetaData["version"].loc[categoryName])
|
241
|
+
else self.settings.get(Tags.VERSION)
|
188
242
|
)
|
189
243
|
category = Category(
|
190
|
-
categoryNumber,
|
191
244
|
categoryName,
|
192
|
-
self.categoriesMetaData["description"].
|
245
|
+
self.categoriesMetaData["description"].loc[categoryName],
|
193
246
|
dataframe=categoryDf,
|
194
247
|
points=points,
|
195
248
|
version=version,
|
196
249
|
)
|
197
|
-
self
|
250
|
+
if hasattr(self, "categories"):
|
251
|
+
self.categories[categoryName] = category
|
252
|
+
self.signals.categoryReady.emit(category)
|
198
253
|
logger.debug("Category %s is initialized", categoryName)
|
199
|
-
|
254
|
+
return category
|
200
255
|
|
201
256
|
def parseAllQuestions(self) -> None:
|
202
257
|
"""Parse all question from all categories.
|
@@ -214,13 +269,12 @@ class QuestionDB:
|
|
214
269
|
for qNum in category.dataframe.columns:
|
215
270
|
try:
|
216
271
|
self.setupAndParseQuestion(category, qNum)
|
217
|
-
except (InvalidFieldException, QNotParsedException
|
272
|
+
except (InvalidFieldException, QNotParsedException):
|
218
273
|
logger.exception(
|
219
274
|
"Question %s%02d couldn't be parsed. The Question Data: \n %s",
|
220
275
|
category.id,
|
221
276
|
qNum,
|
222
277
|
category.dataframe[qNum],
|
223
|
-
exc_info=e,
|
224
278
|
)
|
225
279
|
self.signals.categoryQuestionsReady.emit(category)
|
226
280
|
|
@@ -255,30 +309,33 @@ class QuestionDB:
|
|
255
309
|
raise QNotParsedException(msg, f"{category.id}{qNumber}")
|
256
310
|
cls.validator.setup(qdat, qNumber)
|
257
311
|
cls.validator.validate()
|
258
|
-
validData = cls.validator.
|
259
|
-
qtype: str =
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
question = category.questions[qNumber]
|
264
|
-
if question.element is not None:
|
312
|
+
validData = cls.validator.getQuestionData()
|
313
|
+
qtype: str = validData.get(Tags.TYPE)
|
314
|
+
logger.debug("Question type is: %s", qtype)
|
315
|
+
question = QuestionTypeMapping[qtype].create(category, validData)
|
316
|
+
if question.isParsed:
|
265
317
|
locallogger.info("Question already parsed")
|
266
318
|
return
|
267
319
|
if isinstance(question, NFQuestion):
|
268
320
|
cls.nfParser.setup(question)
|
269
|
-
cls.nfParser.parse()
|
270
321
|
locallogger.debug("setup a new NF parser ")
|
322
|
+
cls.nfParser.parse()
|
271
323
|
elif isinstance(question, MCQuestion):
|
272
324
|
cls.mcParser.setup(question)
|
273
|
-
cls.mcParser.parse()
|
274
325
|
locallogger.debug("setup a new MC parser ")
|
326
|
+
cls.mcParser.parse()
|
275
327
|
elif isinstance(question, NFMQuestion):
|
276
328
|
cls.nfmParser.setup(question)
|
277
|
-
cls.nfmParser.parse()
|
278
329
|
locallogger.debug("setup a new NFM parser ")
|
330
|
+
cls.nfmParser.parse()
|
331
|
+
elif isinstance(question, ClozeQuestion):
|
332
|
+
cls.clozeParser.setup(question)
|
333
|
+
locallogger.debug("setup a new CLOZE parser")
|
334
|
+
cls.clozeParser.parse()
|
279
335
|
else:
|
280
336
|
msg = "couldn't setup Parser"
|
281
337
|
raise QNotParsedException(msg, question.id)
|
338
|
+
category.questions[qNumber] = question
|
282
339
|
|
283
340
|
def appendQuestions(
|
284
341
|
self, questions: list[QuestionItem], file: Path | None = None
|
@@ -293,15 +350,15 @@ class QuestionDB:
|
|
293
350
|
catdict[cat] = []
|
294
351
|
catdict[cat].append(q.getQuestion())
|
295
352
|
for cat, qlist in catdict.items():
|
296
|
-
self.
|
353
|
+
self._appendQElements(
|
297
354
|
cat,
|
298
355
|
qlist,
|
299
356
|
tree=tree,
|
300
|
-
includeHeader=self.settings.get(
|
357
|
+
includeHeader=self.settings.get(Tags.INCLUDEINCATS),
|
301
358
|
)
|
302
359
|
stringHelpers.printDom(tree, file=file)
|
303
360
|
|
304
|
-
def
|
361
|
+
def _appendQElements(
|
305
362
|
self,
|
306
363
|
cat: Category,
|
307
364
|
qList: list[Question],
|
@@ -311,9 +368,9 @@ class QuestionDB:
|
|
311
368
|
if includeHeader:
|
312
369
|
tree.append(cat.getCategoryHeader())
|
313
370
|
logger.debug(f"Appended a new category item {cat=}")
|
314
|
-
variant: int = self.settings.get(
|
371
|
+
variant: int = self.settings.get(Tags.QUESTIONVARIANT)
|
315
372
|
for q in qList:
|
316
|
-
if q.variants is not None:
|
373
|
+
if hasattr(q, "variants") and q.variants is not None:
|
317
374
|
if variant == 0 or variant > q.variants:
|
318
375
|
dialog = QuestionVariantDialog(self.window, q)
|
319
376
|
if dialog.exec() == QtWidgets.QDialog.Accepted:
|
@@ -321,7 +378,5 @@ class QuestionDB:
|
|
321
378
|
logger.debug("Die Fragen-Variante %s wurde gewählt", variant)
|
322
379
|
else:
|
323
380
|
logger.warning("Keine Fragenvariante wurde gewählt.")
|
324
|
-
|
325
|
-
else:
|
326
|
-
q.assemble()
|
381
|
+
q.assemble(variant=variant)
|
327
382
|
tree.append(q.element)
|
excel2moodle/core/etHelpers.py
CHANGED
@@ -8,7 +8,7 @@ import lxml.etree as ET
|
|
8
8
|
import excel2moodle.core.etHelpers as eth
|
9
9
|
from excel2moodle.core.globals import TextElements, feedbackStr, feedBElements
|
10
10
|
|
11
|
-
from .globals import
|
11
|
+
from .globals import Tags, XMLTags
|
12
12
|
|
13
13
|
|
14
14
|
def getElement(eleName: str, text: str, **attribs) -> ET.Element:
|
@@ -27,7 +27,7 @@ def getElement(eleName: str, text: str, **attribs) -> ET.Element:
|
|
27
27
|
return toEle
|
28
28
|
|
29
29
|
|
30
|
-
def getTextElement(eleName: str, text: str |
|
30
|
+
def getTextElement(eleName: str, text: str | Tags, **attribs) -> ET.Element:
|
31
31
|
"""Creates two nested elements: ``eleName`` with child ``text`` which holds the text."""
|
32
32
|
toEle = ET.Element(eleName, **attribs)
|
33
33
|
child = getElement("text", text)
|
excel2moodle/core/exceptions.py
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
from excel2moodle.core.globals import
|
1
|
+
from excel2moodle.core.globals import Tags
|
2
2
|
|
3
3
|
|
4
4
|
class QNotParsedException(Exception):
|
@@ -18,7 +18,7 @@ class InvalidFieldException(Exception):
|
|
18
18
|
self,
|
19
19
|
message: str,
|
20
20
|
qID: str,
|
21
|
-
field:
|
21
|
+
field: Tags | list[Tags],
|
22
22
|
*args: object,
|
23
23
|
**kwargs,
|
24
24
|
) -> None:
|
excel2moodle/core/globals.py
CHANGED
@@ -2,33 +2,16 @@ from enum import Enum, StrEnum
|
|
2
2
|
|
3
3
|
import lxml.etree as ET
|
4
4
|
|
5
|
-
|
5
|
+
from excel2moodle.core.settings import Tags
|
6
|
+
|
7
|
+
QUESTION_TYPES = {
|
6
8
|
"NF": "numerical",
|
7
9
|
"NFM": "numerical",
|
8
10
|
"MC": "multichoice",
|
11
|
+
"CLOZE": "cloze",
|
9
12
|
}
|
10
13
|
|
11
14
|
|
12
|
-
class DFIndex(StrEnum):
|
13
|
-
"""The identifier string for for the spreadsheet and the string for the xml-tag.
|
14
|
-
|
15
|
-
Each enum corresponds to a list of two values.
|
16
|
-
The first Value is the index in the spreadsheet, the second is the name of the xml-tag
|
17
|
-
"""
|
18
|
-
|
19
|
-
TEXT = "text"
|
20
|
-
BPOINTS = "bulletPoint"
|
21
|
-
TRUE = "true"
|
22
|
-
FALSE = "false"
|
23
|
-
TYPE = "type"
|
24
|
-
NAME = "name"
|
25
|
-
RESULT = "result"
|
26
|
-
PICTURE = "picture"
|
27
|
-
NUMBER = "number"
|
28
|
-
ANSTYPE = "answerType"
|
29
|
-
TOLERANCE = "tolerance"
|
30
|
-
|
31
|
-
|
32
15
|
class TextElements(Enum):
|
33
16
|
PLEFT = "p", "text-align: left;"
|
34
17
|
SPANRED = "span", "color: rgb(239, 69, 64)"
|
@@ -58,15 +41,15 @@ class TextElements(Enum):
|
|
58
41
|
|
59
42
|
|
60
43
|
class XMLTags(StrEnum):
|
61
|
-
def __new__(cls, value: str, dfkey:
|
44
|
+
def __new__(cls, value: str, dfkey: Tags | None = None):
|
62
45
|
obj = str.__new__(cls, value)
|
63
46
|
obj._value_ = value
|
64
47
|
if dfkey is not None:
|
65
48
|
obj._dfkey_ = dfkey
|
66
49
|
return obj
|
67
50
|
|
68
|
-
def __init__(self, _: str, dfkey:
|
69
|
-
if isinstance(dfkey,
|
51
|
+
def __init__(self, _: str, dfkey: Tags | None = None, getEle=None) -> None:
|
52
|
+
if isinstance(dfkey, Tags):
|
70
53
|
self._dfkey_: str = dfkey
|
71
54
|
if getEle:
|
72
55
|
self._getEle_: object = getEle
|
@@ -85,11 +68,11 @@ class XMLTags(StrEnum):
|
|
85
68
|
msg.append(f"Df Key {self.dfkey=}")
|
86
69
|
return "\n".join(msg)
|
87
70
|
|
88
|
-
NAME = "name",
|
89
|
-
QTEXT = "questiontext",
|
71
|
+
NAME = "name", Tags.NAME
|
72
|
+
QTEXT = "questiontext", Tags.TEXT
|
90
73
|
QUESTION = "question"
|
91
74
|
TEXT = "text"
|
92
|
-
PICTURE = "file",
|
75
|
+
PICTURE = "file", Tags.PICTURE
|
93
76
|
GENFEEDB = "generalfeedback"
|
94
77
|
CORFEEDB = "correctfeedback"
|
95
78
|
PCORFEEDB = "partialcorrectfeedback"
|