excel2moodle 0.4.1__py3-none-any.whl → 0.4.2__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.
Files changed (33) hide show
  1. excel2moodle/__main__.py +2 -2
  2. excel2moodle/core/__init__.py +0 -10
  3. excel2moodle/core/category.py +4 -3
  4. excel2moodle/core/dataStructure.py +85 -57
  5. excel2moodle/core/etHelpers.py +2 -2
  6. excel2moodle/core/exceptions.py +2 -2
  7. excel2moodle/core/globals.py +10 -27
  8. excel2moodle/core/parser.py +24 -30
  9. excel2moodle/core/question.py +147 -63
  10. excel2moodle/core/settings.py +73 -45
  11. excel2moodle/core/validator.py +36 -55
  12. excel2moodle/logger.py +3 -3
  13. excel2moodle/question_types/__init__.py +2 -0
  14. excel2moodle/question_types/cloze.py +207 -0
  15. excel2moodle/question_types/mc.py +26 -16
  16. excel2moodle/question_types/nf.py +17 -3
  17. excel2moodle/question_types/nfm.py +60 -17
  18. excel2moodle/ui/{windowEquationChecker.py → UI_equationChecker.py} +98 -78
  19. excel2moodle/ui/{exportSettingsDialog.py → UI_exportSettingsDialog.py} +55 -4
  20. excel2moodle/ui/{windowMain.py → UI_mainWindow.py} +32 -39
  21. excel2moodle/ui/appUi.py +35 -66
  22. excel2moodle/ui/dialogs.py +40 -2
  23. excel2moodle/ui/equationChecker.py +70 -0
  24. excel2moodle/ui/treewidget.py +4 -4
  25. {excel2moodle-0.4.1.dist-info → excel2moodle-0.4.2.dist-info}/METADATA +2 -3
  26. excel2moodle-0.4.2.dist-info/RECORD +38 -0
  27. {excel2moodle-0.4.1.dist-info → excel2moodle-0.4.2.dist-info}/entry_points.txt +0 -3
  28. excel2moodle/ui/questionPreviewDialog.py +0 -115
  29. excel2moodle-0.4.1.dist-info/RECORD +0 -37
  30. /excel2moodle/ui/{variantDialog.py → UI_variantDialog.py} +0 -0
  31. {excel2moodle-0.4.1.dist-info → excel2moodle-0.4.2.dist-info}/WHEEL +0 -0
  32. {excel2moodle-0.4.1.dist-info → excel2moodle-0.4.2.dist-info}/licenses/LICENSE +0 -0
  33. {excel2moodle-0.4.1.dist-info → excel2moodle-0.4.2.dist-info}/top_level.txt +0 -0
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, SettingsKey
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(SettingsKey.LOGFILE)).resolve()
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")
@@ -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
- #
@@ -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 = str(self.n)
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 DFIndex
20
+ from excel2moodle.core.globals import Tags
20
21
  from excel2moodle.core.question import Question
21
- from excel2moodle.core.settings import Settings, SettingsKey
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,7 @@ class QuestionDB:
72
76
  self.categoriesMetaData: pd.DataFrame
73
77
  self.categories: dict[str, Category]
74
78
 
75
- def readCategoriesMetadata(self, sheetPath: Path) -> None:
79
+ def readCategoriesMetadata(self, sheetPath: Path) -> pd.DataFrame:
76
80
  """Read the metadata and questions from the spreadsheet.
77
81
 
78
82
  Get the category data from the spreadsheet and stores it in the
@@ -86,22 +90,35 @@ class QuestionDB:
86
90
  f,
87
91
  sheet_name="settings",
88
92
  index_col=0,
93
+ engine="calamine",
89
94
  )
90
95
  logger.debug("Found the settings: \n\t%s", settingDf)
91
- self._setProjectSettings(settingDf)
96
+ self._setProjectSettings(settingDf, mainPath=sheetPath.parent)
92
97
  with Path(sheetPath).open("rb") as f:
93
98
  self.categoriesMetaData = pd.read_excel(
94
99
  f,
95
- sheet_name=self.settings.get(SettingsKey.CATEGORIESSHEET),
100
+ sheet_name=self.settings.get(Tags.CATEGORIESSHEET),
96
101
  index_col=0,
102
+ engine="calamine",
97
103
  )
98
104
  logger.info(
99
105
  "Sucessfully read categoriesMetaData \n %s", self.categoriesMetaData
100
106
  )
107
+ return self.categoriesMetaData
101
108
 
102
- def _setProjectSettings(self, settings: pd.DataFrame) -> None:
109
+ def _setProjectSettings(
110
+ self, settings: pd.DataFrame, mainPath: Path | None = None
111
+ ) -> None:
112
+ settings = self.harmonizeDFIndex(settings)
103
113
  for tag, value in settings.iterrows():
104
114
  self.settings.set(tag, value.iloc[0], local=True)
115
+ extraModule = self.settings.get(Tags.IMPORTMODULE)
116
+ if extraModule:
117
+ logger.warning(
118
+ "Appending: %s to sys.path. All names defined by it will be usable",
119
+ mainPath,
120
+ )
121
+ sys.path.append(str(mainPath))
105
122
 
106
123
  def initAllCategories(self, sheetPath: Path) -> None:
107
124
  """Read all category sheets and initialize all Categories."""
@@ -112,11 +129,10 @@ class QuestionDB:
112
129
  self.categories.clear()
113
130
  else:
114
131
  self.categories: dict[str, Category] = {}
115
- with Path(sheetPath).open("rb") as f:
116
- excelFile = pd.ExcelFile(f)
132
+ with pd.ExcelFile(sheetPath, engine="calamine") as excelFile:
117
133
  for categoryName in excelFile.sheet_names:
118
134
  logger.debug("Starting to read category %s", categoryName)
119
- if categoryName.startswith("KAT"):
135
+ if categoryName in self.categoriesMetaData.index:
120
136
  self.initCategory(sheetPath, categoryName)
121
137
 
122
138
  def asyncInitAllCategories(self, sheetPath: Path) -> None:
@@ -132,71 +148,83 @@ class QuestionDB:
132
148
  self.categories.clear()
133
149
  else:
134
150
  self.categories: dict[str, Category] = {}
135
- sheet_names = []
136
- with Path(sheetPath).open("rb") as f:
137
- excel_file = pd.ExcelFile(f)
138
- sheet_names = [
139
- name for name in excel_file.sheet_names if name.startswith("KAT_")
151
+ sheetNames = []
152
+ with pd.ExcelFile(sheetPath, engine="calamine") as excelFile:
153
+ sheetNames = [
154
+ name
155
+ for name in excelFile.sheet_names
156
+ if name in self.categoriesMetaData.index
140
157
  ]
141
- logger.debug("found those caetegory sheets: \n %s ", sheet_names)
158
+ logger.debug("found those category sheets: \n %s ", sheetNames)
142
159
  with ProcessPoolExecutor() as executor:
143
160
  futures = {
144
161
  executor.submit(processSheet, str(sheetPath), sheet): sheet
145
- for sheet in sheet_names
162
+ for sheet in sheetNames
146
163
  }
147
164
  for future in as_completed(futures):
148
165
  categoryName = futures[future]
149
166
  try:
150
167
  categoryDataF = future.result()
151
- categoryNumber = int(categoryName[4:])
152
- self._setupCategory(categoryDataF, categoryName, categoryNumber)
168
+ self._setupCategory(categoryDataF, categoryName)
153
169
  logger.debug("Finished processing %s", categoryName)
154
170
  except Exception as e:
155
171
  logger.exception("Error processing sheet %s: %s", categoryName, e)
156
172
  logger.debug("Future exception: %s", future.exception())
157
173
 
158
- def initCategory(self, sheetPath: Path, categoryName: str) -> None:
159
- """Read `categoryName` from the file ``sheetPath`` and initialize the category."""
160
- categoryNumber = int(categoryName[4:])
174
+ def initCategory(self, sheetPath: Path, categoryName: str) -> bool | Category:
175
+ """Read `categoryName` from the ``sheetPath`` and initialize the category.
176
+ Returns the Category and appends it to `self.categories`.
177
+ """
161
178
  katDf = pd.read_excel(
162
179
  sheetPath,
163
180
  sheet_name=str(categoryName),
164
181
  index_col=0,
165
182
  header=None,
183
+ engine="calamine",
166
184
  )
167
185
  if not katDf.empty:
168
186
  logger.debug("Sucessfully read the Dataframe for cat %s", categoryName)
169
- self._setupCategory(katDf, categoryName, categoryNumber)
170
-
171
- def _setupCategory(
172
- self, categoryDf: pd.DataFrame, categoryName: str, categoryNumber: int
173
- ) -> None:
187
+ return self._setupCategory(katDf, categoryName)
188
+ return False
189
+
190
+ @staticmethod
191
+ def harmonizeDFIndex(dataframe: pd.DataFrame) -> pd.DataFrame:
192
+ """Convert the index strings to lowercase without whitespace."""
193
+ index = dataframe.index.str.lower()
194
+ harmonizedIdx = ["".join(i.split()) if pd.notna(i) else i for i in index]
195
+ dataframe.index = pd.Index(harmonizedIdx)
196
+ return dataframe
197
+
198
+ def _setupCategory(self, categoryDf: pd.DataFrame, categoryName: str) -> Category:
174
199
  """Setup the category from the ``dataframe``.
200
+
175
201
  :emits: categoryReady(self) Signal.
176
- """ # noqa: D401
202
+ """
203
+ categoryDf = self.harmonizeDFIndex(categoryDf)
177
204
  points = (
178
- self.categoriesMetaData["points"].iloc[categoryNumber - 1]
205
+ self.categoriesMetaData["points"].loc[categoryName]
179
206
  if "points" in self.categoriesMetaData
180
- and not pd.isna(self.categoriesMetaData["points"]).iloc[categoryNumber - 1]
181
- else self.settings.get(SettingsKey.POINTS)
207
+ and not pd.isna(self.categoriesMetaData["points"]).loc[categoryName]
208
+ else self.settings.get(Tags.POINTS)
182
209
  )
183
210
  version = (
184
- self.categoriesMetaData["version"].iloc[categoryNumber - 1]
211
+ self.categoriesMetaData["version"].loc[categoryName]
185
212
  if "version" in self.categoriesMetaData
186
- and not pd.isna(self.categoriesMetaData["version"].iloc[categoryNumber - 1])
187
- else self.settings.get(SettingsKey.VERSION)
213
+ and not pd.isna(self.categoriesMetaData["version"].loc[categoryName])
214
+ else self.settings.get(Tags.VERSION)
188
215
  )
189
216
  category = Category(
190
- categoryNumber,
191
217
  categoryName,
192
- self.categoriesMetaData["description"].iloc[categoryNumber - 1],
218
+ self.categoriesMetaData["description"].loc[categoryName],
193
219
  dataframe=categoryDf,
194
220
  points=points,
195
221
  version=version,
196
222
  )
197
- self.categories[categoryName] = category
223
+ if hasattr(self, "categories"):
224
+ self.categories[categoryName] = category
225
+ self.signals.categoryReady.emit(category)
198
226
  logger.debug("Category %s is initialized", categoryName)
199
- self.signals.categoryReady.emit(category)
227
+ return category
200
228
 
201
229
  def parseAllQuestions(self) -> None:
202
230
  """Parse all question from all categories.
@@ -214,13 +242,12 @@ class QuestionDB:
214
242
  for qNum in category.dataframe.columns:
215
243
  try:
216
244
  self.setupAndParseQuestion(category, qNum)
217
- except (InvalidFieldException, QNotParsedException, ValueError) as e:
245
+ except (InvalidFieldException, QNotParsedException):
218
246
  logger.exception(
219
247
  "Question %s%02d couldn't be parsed. The Question Data: \n %s",
220
248
  category.id,
221
249
  qNum,
222
250
  category.dataframe[qNum],
223
- exc_info=e,
224
251
  )
225
252
  self.signals.categoryQuestionsReady.emit(category)
226
253
 
@@ -255,30 +282,33 @@ class QuestionDB:
255
282
  raise QNotParsedException(msg, f"{category.id}{qNumber}")
256
283
  cls.validator.setup(qdat, qNumber)
257
284
  cls.validator.validate()
258
- validData = cls.validator.getQuestionRawData()
259
- qtype: str = str(validData.get(DFIndex.TYPE))
260
- category.questions[qNumber] = QuestionTypeMapping[qtype].create(
261
- category, validData
262
- )
263
- question = category.questions[qNumber]
264
- if question.element is not None:
285
+ validData = cls.validator.getQuestionData()
286
+ qtype: str = validData.get(Tags.TYPE)
287
+ logger.debug("Question type is: %s", qtype)
288
+ question = QuestionTypeMapping[qtype].create(category, validData)
289
+ if question.isParsed:
265
290
  locallogger.info("Question already parsed")
266
291
  return
267
292
  if isinstance(question, NFQuestion):
268
293
  cls.nfParser.setup(question)
269
- cls.nfParser.parse()
270
294
  locallogger.debug("setup a new NF parser ")
295
+ cls.nfParser.parse()
271
296
  elif isinstance(question, MCQuestion):
272
297
  cls.mcParser.setup(question)
273
- cls.mcParser.parse()
274
298
  locallogger.debug("setup a new MC parser ")
299
+ cls.mcParser.parse()
275
300
  elif isinstance(question, NFMQuestion):
276
301
  cls.nfmParser.setup(question)
277
- cls.nfmParser.parse()
278
302
  locallogger.debug("setup a new NFM parser ")
303
+ cls.nfmParser.parse()
304
+ elif isinstance(question, ClozeQuestion):
305
+ cls.clozeParser.setup(question)
306
+ locallogger.debug("setup a new CLOZE parser")
307
+ cls.clozeParser.parse()
279
308
  else:
280
309
  msg = "couldn't setup Parser"
281
310
  raise QNotParsedException(msg, question.id)
311
+ category.questions[qNumber] = question
282
312
 
283
313
  def appendQuestions(
284
314
  self, questions: list[QuestionItem], file: Path | None = None
@@ -293,15 +323,15 @@ class QuestionDB:
293
323
  catdict[cat] = []
294
324
  catdict[cat].append(q.getQuestion())
295
325
  for cat, qlist in catdict.items():
296
- self.appendQElements(
326
+ self._appendQElements(
297
327
  cat,
298
328
  qlist,
299
329
  tree=tree,
300
- includeHeader=self.settings.get(SettingsKey.INCLUDEINCATS),
330
+ includeHeader=self.settings.get(Tags.INCLUDEINCATS),
301
331
  )
302
332
  stringHelpers.printDom(tree, file=file)
303
333
 
304
- def appendQElements(
334
+ def _appendQElements(
305
335
  self,
306
336
  cat: Category,
307
337
  qList: list[Question],
@@ -311,9 +341,9 @@ class QuestionDB:
311
341
  if includeHeader:
312
342
  tree.append(cat.getCategoryHeader())
313
343
  logger.debug(f"Appended a new category item {cat=}")
314
- variant: int = self.settings.get(SettingsKey.QUESTIONVARIANT)
344
+ variant: int = self.settings.get(Tags.QUESTIONVARIANT)
315
345
  for q in qList:
316
- if q.variants is not None:
346
+ if hasattr(q, "variants") and q.variants is not None:
317
347
  if variant == 0 or variant > q.variants:
318
348
  dialog = QuestionVariantDialog(self.window, q)
319
349
  if dialog.exec() == QtWidgets.QDialog.Accepted:
@@ -321,7 +351,5 @@ class QuestionDB:
321
351
  logger.debug("Die Fragen-Variante %s wurde gewählt", variant)
322
352
  else:
323
353
  logger.warning("Keine Fragenvariante wurde gewählt.")
324
- q.assemble(variant)
325
- else:
326
- q.assemble()
354
+ q.assemble(variant=variant)
327
355
  tree.append(q.element)
@@ -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 DFIndex, XMLTags
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 | DFIndex, **attribs) -> ET.Element:
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)
@@ -1,4 +1,4 @@
1
- from excel2moodle.core.globals import DFIndex
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: DFIndex | list[DFIndex],
21
+ field: Tags | list[Tags],
22
22
  *args: object,
23
23
  **kwargs,
24
24
  ) -> None:
@@ -2,33 +2,16 @@ from enum import Enum, StrEnum
2
2
 
3
3
  import lxml.etree as ET
4
4
 
5
- questionTypes = {
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: DFIndex | None = None):
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: DFIndex | None = None, getEle=None) -> None:
69
- if isinstance(dfkey, DFIndex):
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", DFIndex.NAME
89
- QTEXT = "questiontext", DFIndex.TEXT
71
+ NAME = "name", Tags.NAME
72
+ QTEXT = "questiontext", Tags.TEXT
90
73
  QUESTION = "question"
91
74
  TEXT = "text"
92
- PICTURE = "file", DFIndex.PICTURE
75
+ PICTURE = "file", Tags.PICTURE
93
76
  GENFEEDB = "generalfeedback"
94
77
  CORFEEDB = "correctfeedback"
95
78
  PCORFEEDB = "partialcorrectfeedback"
@@ -4,17 +4,16 @@ import re
4
4
  import lxml.etree as ET
5
5
 
6
6
  import excel2moodle.core.etHelpers as eth
7
- from excel2moodle.core import stringHelpers
8
7
  from excel2moodle.core.exceptions import QNotParsedException
9
8
  from excel2moodle.core.globals import (
10
- DFIndex,
9
+ Tags,
11
10
  TextElements,
12
11
  XMLTags,
13
12
  feedbackStr,
14
13
  feedBElements,
15
14
  )
16
15
  from excel2moodle.core.question import Picture, Question
17
- from excel2moodle.core.settings import Settings, SettingsKey
16
+ from excel2moodle.core.settings import Settings, Tags
18
17
  from excel2moodle.logger import LogAdapterQuestionID
19
18
 
20
19
  loggerObj = logging.getLogger(__name__)
@@ -47,8 +46,8 @@ class QuestionParser:
47
46
  """Create a ``Picture`` object ``question``if the question needs a pic."""
48
47
  if hasattr(self, "picture") and self.question.picture.ready:
49
48
  return True
50
- picKey = self.rawInput.get(DFIndex.PICTURE, False)
51
- f = self.settings.get(SettingsKey.PICTUREFOLDER)
49
+ picKey = self.rawInput.get(Tags.PICTURE)
50
+ f = self.settings.get(Tags.PICTUREFOLDER)
52
51
  svgFolder = (f / self.question.katName).resolve()
53
52
  if not hasattr(self.question, "picture"):
54
53
  self.question.picture = Picture(picKey, svgFolder, self.question.id)
@@ -57,7 +56,7 @@ class QuestionParser:
57
56
  def setMainText(self) -> None:
58
57
  paragraphs: list[ET._Element] = [TextElements.PLEFT.create()]
59
58
  ET.SubElement(paragraphs[0], "b").text = f"ID {self.question.id}"
60
- text = self.rawInput[DFIndex.TEXT]
59
+ text = self.rawInput[Tags.TEXT]
61
60
  for t in text:
62
61
  paragraphs.append(TextElements.PLEFT.create())
63
62
  paragraphs[-1].text = t
@@ -66,8 +65,8 @@ class QuestionParser:
66
65
 
67
66
  def setBPoints(self) -> None:
68
67
  """If there bulletPoints are set in the Spreadsheet it creates an unordered List-Element in ``Question.bulletList``."""
69
- if DFIndex.BPOINTS in self.rawInput:
70
- bps: str = self.rawInput[DFIndex.BPOINTS]
68
+ if Tags.BPOINTS in self.rawInput:
69
+ bps: list[str] = self.rawInput[Tags.BPOINTS]
71
70
  try:
72
71
  bulletList = self.formatBulletList(bps)
73
72
  except IndexError:
@@ -83,15 +82,14 @@ class QuestionParser:
83
82
  )
84
83
  self.question.bulletList = bulletList
85
84
 
86
- def formatBulletList(self, bps: str) -> ET.Element:
85
+ def formatBulletList(self, bps: list[str]) -> ET.Element:
87
86
  self.logger.debug("Formatting the bulletpoint list")
88
- li: list[str] = stringHelpers.getListFromStr(bps)
89
87
  name = []
90
88
  var = []
91
89
  quant = []
92
90
  unit = []
93
91
  unorderedList = TextElements.ULIST.create()
94
- for item in li:
92
+ for item in bps:
95
93
  sc_split = item.split()
96
94
  name.append(sc_split[0])
97
95
  var.append(sc_split[1])
@@ -118,7 +116,7 @@ class QuestionParser:
118
116
  def appendToTmpEle(
119
117
  self,
120
118
  eleName: str,
121
- text: str | DFIndex,
119
+ text: str | Tags,
122
120
  txtEle=False,
123
121
  **attribs,
124
122
  ) -> None:
@@ -127,7 +125,7 @@ class QuestionParser:
127
125
  It uses the data from ``self.rawInput`` if ``text`` is type``DFIndex``
128
126
  Otherwise the value of ``text`` will be inserted.
129
127
  """
130
- t = self.rawInput[text] if isinstance(text, DFIndex) else text
128
+ t = self.rawInput[text] if isinstance(text, Tags) else text
131
129
  if txtEle is False:
132
130
  self.tmpEle.append(eth.getElement(eleName, t, **attribs))
133
131
  elif txtEle is True:
@@ -135,6 +133,9 @@ class QuestionParser:
135
133
 
136
134
  def _appendStandardTags(self) -> None:
137
135
  """Append the elements defined in the ``cls.standardTags``."""
136
+ self.logger.debug(
137
+ "Appending the Standard Tags %s", type(self.question).standardTags.items()
138
+ )
138
139
  for k, v in type(self.question).standardTags.items():
139
140
  self.appendToTmpEle(k, text=v)
140
141
 
@@ -145,25 +146,27 @@ class QuestionParser:
145
146
  if no Exceptions are raised, ``self.tmpEle`` is passed to ``self.question.element``
146
147
  """
147
148
  self.logger.info("Starting to parse")
148
- self.tmpEle = ET.Element(XMLTags.QUESTION, type=self.question.moodleType)
149
- self.appendToTmpEle(XMLTags.NAME, text=DFIndex.NAME, txtEle=True)
149
+ self.tmpEle: ET.Elemnt = ET.Element(
150
+ XMLTags.QUESTION, type=self.question.moodleType
151
+ )
152
+ self.appendToTmpEle(XMLTags.NAME, text=Tags.NAME, txtEle=True)
150
153
  self.appendToTmpEle(XMLTags.ID, text=self.question.id)
151
154
  if self.hasPicture():
152
155
  self.tmpEle.append(self.question.picture.element)
153
156
  self.tmpEle.append(ET.Element(XMLTags.QTEXT, format="html"))
154
157
  self.appendToTmpEle(XMLTags.POINTS, text=str(self.question.points))
155
- self.appendToTmpEle(XMLTags.PENALTY, text="0.3333")
156
158
  self._appendStandardTags()
157
159
  for feedb in self.genFeedbacks:
158
160
  self.tmpEle.append(eth.getFeedBEle(feedb))
159
- ansList = self.setAnswers()
161
+ ansList = self._parseAnswers()
160
162
  self.setMainText()
161
163
  self.setBPoints()
162
164
  if ansList is not None:
163
165
  for ele in ansList:
164
166
  self.tmpEle.append(ele)
165
- self.logger.info("Sucessfully parsed")
166
167
  self.question.element = self.tmpEle
168
+ self.question.isParsed = True
169
+ self.logger.info("Sucessfully parsed")
167
170
 
168
171
  def getFeedBEle(
169
172
  self,
@@ -181,7 +184,7 @@ class QuestionParser:
181
184
  ele.append(eth.getCdatTxtElement(par))
182
185
  return ele
183
186
 
184
- def setAnswers(self) -> list[ET.Element] | None:
187
+ def _parseAnswers(self) -> list[ET.Element] | None:
185
188
  """Needs to be implemented in the type-specific subclasses."""
186
189
  return None
187
190
 
@@ -211,15 +214,6 @@ class QuestionParser:
211
214
  TextElements.SPANGREEN,
212
215
  ),
213
216
  )
214
- tolerance = float(self.rawInput.get(DFIndex.TOLERANCE, 0))
215
- if tolerance == 0 or tolerance >= 100:
216
- tolerance = self.settings.get(SettingsKey.TOLERANCE)
217
- self.logger.info(
218
- "Using default tolerance %s percent from settings",
219
- tolerance,
220
- )
221
- tolPercent = 100 * tolerance if tolerance < 1 else tolerance
222
- self.logger.debug("Using tolerance %s percent", tolPercent)
223
- relTolerance = abs(round(result * (tolerance / 100), 3))
224
- ansEle.append(eth.getElement(XMLTags.TOLERANCE, text=str(relTolerance)))
217
+ absTolerance = round(result * self.rawInput.get(Tags.TOLERANCE), 4)
218
+ ansEle.append(eth.getElement(XMLTags.TOLERANCE, text=str(absTolerance)))
225
219
  return ansEle