excel2moodle 0.7.1__tar.gz → 0.7.2__tar.gz

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 (53) hide show
  1. {excel2moodle-0.7.1 → excel2moodle-0.7.2}/PKG-INFO +14 -1
  2. {excel2moodle-0.7.1 → excel2moodle-0.7.2}/README.md +13 -0
  3. {excel2moodle-0.7.1 → excel2moodle-0.7.2}/excel2moodle/core/bullets.py +25 -9
  4. excel2moodle-0.7.1/excel2moodle/core/settings.py → excel2moodle-0.7.2/excel2moodle/core/globals.py +100 -153
  5. {excel2moodle-0.7.1 → excel2moodle-0.7.2}/excel2moodle/core/parser.py +14 -14
  6. {excel2moodle-0.7.1 → excel2moodle-0.7.2}/excel2moodle/core/question.py +1 -0
  7. excel2moodle-0.7.2/excel2moodle/core/settings.py +121 -0
  8. {excel2moodle-0.7.1 → excel2moodle-0.7.2}/excel2moodle/question_types/cloze.py +9 -9
  9. {excel2moodle-0.7.1 → excel2moodle-0.7.2}/excel2moodle/question_types/mc.py +17 -17
  10. {excel2moodle-0.7.1 → excel2moodle-0.7.2}/excel2moodle/question_types/nf.py +4 -4
  11. {excel2moodle-0.7.1 → excel2moodle-0.7.2}/excel2moodle/question_types/nfm.py +6 -6
  12. {excel2moodle-0.7.1 → excel2moodle-0.7.2}/excel2moodle.egg-info/PKG-INFO +14 -1
  13. {excel2moodle-0.7.1 → excel2moodle-0.7.2}/pyproject.toml +1 -1
  14. {excel2moodle-0.7.1 → excel2moodle-0.7.2}/test/test_bullets.py +20 -0
  15. excel2moodle-0.7.1/excel2moodle/core/globals.py +0 -91
  16. {excel2moodle-0.7.1 → excel2moodle-0.7.2}/LICENSE +0 -0
  17. {excel2moodle-0.7.1 → excel2moodle-0.7.2}/excel2moodle/__init__.py +0 -0
  18. {excel2moodle-0.7.1 → excel2moodle-0.7.2}/excel2moodle/__main__.py +0 -0
  19. {excel2moodle-0.7.1 → excel2moodle-0.7.2}/excel2moodle/core/__init__.py +0 -0
  20. {excel2moodle-0.7.1 → excel2moodle-0.7.2}/excel2moodle/core/category.py +0 -0
  21. {excel2moodle-0.7.1 → excel2moodle-0.7.2}/excel2moodle/core/dataStructure.py +0 -0
  22. {excel2moodle-0.7.1 → excel2moodle-0.7.2}/excel2moodle/core/etHelpers.py +0 -0
  23. {excel2moodle-0.7.1 → excel2moodle-0.7.2}/excel2moodle/core/exceptions.py +0 -0
  24. {excel2moodle-0.7.1 → excel2moodle-0.7.2}/excel2moodle/core/stringHelpers.py +0 -0
  25. {excel2moodle-0.7.1 → excel2moodle-0.7.2}/excel2moodle/core/validator.py +0 -0
  26. {excel2moodle-0.7.1 → excel2moodle-0.7.2}/excel2moodle/extra/__init__.py +0 -0
  27. {excel2moodle-0.7.1 → excel2moodle-0.7.2}/excel2moodle/extra/equationChecker.py +0 -0
  28. {excel2moodle-0.7.1 → excel2moodle-0.7.2}/excel2moodle/extra/scriptCaller.py +0 -0
  29. {excel2moodle-0.7.1 → excel2moodle-0.7.2}/excel2moodle/extra/updateQuery.py +0 -0
  30. {excel2moodle-0.7.1 → excel2moodle-0.7.2}/excel2moodle/extra/variableGenerator.py +0 -0
  31. {excel2moodle-0.7.1 → excel2moodle-0.7.2}/excel2moodle/logger.py +0 -0
  32. {excel2moodle-0.7.1 → excel2moodle-0.7.2}/excel2moodle/question_types/__init__.py +0 -0
  33. {excel2moodle-0.7.1 → excel2moodle-0.7.2}/excel2moodle/ui/UI_equationChecker.py +0 -0
  34. {excel2moodle-0.7.1 → excel2moodle-0.7.2}/excel2moodle/ui/UI_exportSettingsDialog.py +0 -0
  35. {excel2moodle-0.7.1 → excel2moodle-0.7.2}/excel2moodle/ui/UI_mainWindow.py +0 -0
  36. {excel2moodle-0.7.1 → excel2moodle-0.7.2}/excel2moodle/ui/UI_updateDlg.py +0 -0
  37. {excel2moodle-0.7.1 → excel2moodle-0.7.2}/excel2moodle/ui/UI_variableGenerator.py +0 -0
  38. {excel2moodle-0.7.1 → excel2moodle-0.7.2}/excel2moodle/ui/UI_variantDialog.py +0 -0
  39. {excel2moodle-0.7.1 → excel2moodle-0.7.2}/excel2moodle/ui/__init__.py +0 -0
  40. {excel2moodle-0.7.1 → excel2moodle-0.7.2}/excel2moodle/ui/appUi.py +0 -0
  41. {excel2moodle-0.7.1 → excel2moodle-0.7.2}/excel2moodle/ui/dialogs.py +0 -0
  42. {excel2moodle-0.7.1 → excel2moodle-0.7.2}/excel2moodle/ui/treewidget.py +0 -0
  43. {excel2moodle-0.7.1 → excel2moodle-0.7.2}/excel2moodle.egg-info/SOURCES.txt +0 -0
  44. {excel2moodle-0.7.1 → excel2moodle-0.7.2}/excel2moodle.egg-info/dependency_links.txt +0 -0
  45. {excel2moodle-0.7.1 → excel2moodle-0.7.2}/excel2moodle.egg-info/entry_points.txt +0 -0
  46. {excel2moodle-0.7.1 → excel2moodle-0.7.2}/excel2moodle.egg-info/requires.txt +0 -0
  47. {excel2moodle-0.7.1 → excel2moodle-0.7.2}/excel2moodle.egg-info/top_level.txt +0 -0
  48. {excel2moodle-0.7.1 → excel2moodle-0.7.2}/setup.cfg +0 -0
  49. {excel2moodle-0.7.1 → excel2moodle-0.7.2}/test/test_feedbacking.py +0 -0
  50. {excel2moodle-0.7.1 → excel2moodle-0.7.2}/test/test_nfmParsing.py +0 -0
  51. {excel2moodle-0.7.1 → excel2moodle-0.7.2}/test/test_parseQuestion.py +0 -0
  52. {excel2moodle-0.7.1 → excel2moodle-0.7.2}/test/test_picture.py +0 -0
  53. {excel2moodle-0.7.1 → excel2moodle-0.7.2}/test/test_questionDataGet.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: excel2moodle
3
- Version: 0.7.1
3
+ Version: 0.7.2
4
4
  Summary: A package for converting questions from a spreadsheet, to valid moodle-xml
5
5
  Author: Jakob Bosse
6
6
  License-Expression: GPL-3.0-or-later
@@ -90,6 +90,19 @@ If You want to support my work as well, you can by me a [coffee](https://ko-fi.c
90
90
 
91
91
  # Changelogs
92
92
 
93
+ ## 0.7.2 (2025-10-11)
94
+ small important bugfixes
95
+
96
+ ### improvement (2 changes)
97
+
98
+ - [BulletPoints are decoded using regex to allow multi word names](https://gitlab.com/jbosse3/excel2moodle/-/commit/7e32e9817323054d84a0dfb9a0a241c702fd096d)
99
+ - [Restructured globals, renamed rawInput to rawData](https://gitlab.com/jbosse3/excel2moodle/-/commit/effd9c3cd196b36d49204fe715acc1ffb124549c)
100
+
101
+ ### bugfix (2 changes)
102
+
103
+ - [Added the number do the mandatory tags because it is](https://gitlab.com/jbosse3/excel2moodle/-/commit/56e9b69d71504dffe7c235d69ff44dec6931db28)
104
+ - [fixed assigning the first result to all clozes](https://gitlab.com/jbosse3/excel2moodle/-/commit/09a281c253502adc23442892be03aac36e6ea720)
105
+
93
106
  ## 0.7.1 (2025-10-04)
94
107
  feedbacking improved
95
108
 
@@ -68,6 +68,19 @@ If You want to support my work as well, you can by me a [coffee](https://ko-fi.c
68
68
 
69
69
  # Changelogs
70
70
 
71
+ ## 0.7.2 (2025-10-11)
72
+ small important bugfixes
73
+
74
+ ### improvement (2 changes)
75
+
76
+ - [BulletPoints are decoded using regex to allow multi word names](https://gitlab.com/jbosse3/excel2moodle/-/commit/7e32e9817323054d84a0dfb9a0a241c702fd096d)
77
+ - [Restructured globals, renamed rawInput to rawData](https://gitlab.com/jbosse3/excel2moodle/-/commit/effd9c3cd196b36d49204fe715acc1ffb124549c)
78
+
79
+ ### bugfix (2 changes)
80
+
81
+ - [Added the number do the mandatory tags because it is](https://gitlab.com/jbosse3/excel2moodle/-/commit/56e9b69d71504dffe7c235d69ff44dec6931db28)
82
+ - [fixed assigning the first result to all clozes](https://gitlab.com/jbosse3/excel2moodle/-/commit/09a281c253502adc23442892be03aac36e6ea720)
83
+
71
84
  ## 0.7.1 (2025-10-04)
72
85
  feedbacking improved
73
86
 
@@ -57,18 +57,33 @@ class BulletList:
57
57
 
58
58
  def _setupBullets(self, bps: list[str]) -> ET.Element:
59
59
  self.logger.debug("Formatting the bulletpoint list")
60
- varFinder = re.compile(r"=\s*\{(\w+)\}")
60
+ varFinder = re.compile(r"\{(\w+)\}")
61
+ bulletFinder = re.compile(
62
+ r"^\s?(?P<desc>.*?)"
63
+ r"(?:\s+(?P<var>[\w+\{\\/\}^_-]+)\s*=\s*)"
64
+ r"(?P<val>[.,\{\w+\}]+)"
65
+ r"(?:\s+(?P<unit>[\w/\\^²³⁴⁵⁶]+)\s*$)"
66
+ )
61
67
  for i, item in enumerate(bps):
62
- sc_split = item.split()
63
- name = sc_split[0]
64
- var = sc_split[1]
65
- quant = sc_split[3]
66
- unit = sc_split[4]
67
-
68
- match = re.search(varFinder, item)
68
+ match = re.search(bulletFinder, item)
69
69
  if match is None:
70
+ self.logger.error("Couldn't find any bullets")
71
+ msg = f"Couldn't decode the bullet point: {item}"
72
+ raise ValueError(msg)
73
+ name = match.group("desc")
74
+ var = match.group("var")
75
+ unit = match.group("unit")
76
+ value = match.group("val")
77
+ self.logger.info(
78
+ "Decoded bulletPoint: name: %s, var: %s, - value: %s, - unit: %s.",
79
+ name,
80
+ var,
81
+ value,
82
+ unit,
83
+ )
84
+ if (match := re.search(varFinder, value)) is None:
70
85
  self.logger.debug("Got a normal bulletItem")
71
- num: float = float(quant.replace(",", "."))
86
+ num: float = float(value.replace(",", "."))
72
87
  bulletName = i + 1
73
88
  else:
74
89
  bulletName = match.group(1)
@@ -86,6 +101,7 @@ class BulletP:
86
101
  self.var: str = var
87
102
  self.unit: str = unit
88
103
  self.element: ET.Element
104
+ self.value: float = value
89
105
  self.update(value=value)
90
106
 
91
107
  def update(self, value: float = 1) -> None:
@@ -1,13 +1,20 @@
1
- """Settings module provides the adjusted subclass of ``PySide6.QtCore.QSettings``."""
2
-
3
1
  import logging
4
- from enum import StrEnum
2
+ from enum import Enum, StrEnum
5
3
  from pathlib import Path
6
- from typing import ClassVar, Literal, overload
4
+
5
+ import lxml.etree as ET
7
6
 
8
7
  logger = logging.getLogger(__name__)
9
8
 
10
9
 
10
+ QUESTION_TYPES = {
11
+ "NF": "numerical",
12
+ "NFM": "numerical",
13
+ "MC": "multichoice",
14
+ "CLOZE": "cloze",
15
+ }
16
+
17
+
11
18
  class Tags(StrEnum):
12
19
  """Tags and Settings Keys are needed to always acess the correct Value.
13
20
 
@@ -16,50 +23,6 @@ class Tags(StrEnum):
16
23
  Further, this Enum defines, which type a setting has to be.
17
24
  """
18
25
 
19
- def __new__(
20
- cls,
21
- key: str,
22
- typ: type,
23
- default: str | float | Path | bool | None,
24
- place: str = "project",
25
- ) -> object:
26
- """Define new settings class."""
27
- obj = str.__new__(cls, key)
28
- obj._value_ = key
29
- obj._typ_ = typ
30
- obj._default_ = default
31
- obj._place_ = place
32
- return obj
33
-
34
- def __init__(
35
- self,
36
- _,
37
- typ: type,
38
- default: str | float | Path | None,
39
- place: str = "project",
40
- ) -> None:
41
- self._typ_: type = typ
42
- self._place_: str = place
43
- self._default_ = default
44
- self._full_ = f"{self._place_}/{self._value_}"
45
-
46
- @property
47
- def default(self) -> str | int | float | Path | bool | None:
48
- """Get default value for the key."""
49
- return self._default_
50
-
51
- @property
52
- def place(self) -> str:
53
- return self._place_
54
-
55
- @property
56
- def full(self) -> str:
57
- return self._full_
58
-
59
- def typ(self) -> type:
60
- """Get type of the keys data."""
61
- return self._typ_
62
-
63
26
  QUESTIONVARIANT = "defaultquestionvariant", int, 1, "testgen"
64
27
  INCLUDEINCATS = "includecats", bool, False, "testgen"
65
28
  GENEXPORTREPORT = "exportreport", bool, False, "testgen"
@@ -101,114 +64,98 @@ class Tags(StrEnum):
101
64
  MEDIASCRIPTS = "mediascripts", list, None
102
65
  MEDIACALL = "parametricmedia", str, None
103
66
 
67
+ def __new__(
68
+ cls,
69
+ key: str,
70
+ typ: type,
71
+ default: str | float | Path | bool | None,
72
+ place: str = "project",
73
+ ) -> object:
74
+ """Define new settings class."""
75
+ obj = str.__new__(cls, key)
76
+ obj._value_ = key
77
+ obj._typ_ = typ
78
+ obj._default_ = default
79
+ obj._place_ = place
80
+ return obj
81
+
82
+ def __init__(
83
+ self,
84
+ _,
85
+ typ: type,
86
+ default: str | float | Path | None,
87
+ place: str = "project",
88
+ ) -> None:
89
+ self._typ_: type = typ
90
+ self._place_: str = place
91
+ self._default_ = default
92
+ self._full_ = f"{self._place_}/{self._value_}"
104
93
 
105
- class Settings:
106
- values: ClassVar[dict[str, str | float | Path | list]] = {}
94
+ @property
95
+ def default(self) -> str | int | float | Path | bool | None:
96
+ """Get default value for the key."""
97
+ return self._default_
98
+
99
+ @property
100
+ def place(self) -> str:
101
+ return self._place_
102
+
103
+ @property
104
+ def full(self) -> str:
105
+ return self._full_
107
106
 
108
- def __contains__(self, tag: Tags) -> bool:
109
- return bool(tag in type(self).values)
107
+ def typ(self) -> type:
108
+ """Get type of the keys data."""
109
+ return self._typ_
110
110
 
111
- @classmethod
112
- def clear(cls) -> None:
113
- cls.values.clear()
114
111
 
115
- @classmethod
116
- def pop(cls, key: str):
117
- return cls.values.pop(key)
112
+ class TextElements(Enum):
113
+ PLEFT = "p", "text-align: left;"
114
+ SPANRED = "span", "color: rgb(239, 69, 64)"
115
+ SPANGREEN = "span", "color: rgb(152, 202, 62)"
116
+ SPANORANGE = "span", "color: rgb(152, 100, 100)"
117
+ ULIST = "ul", ""
118
+ LISTITEM = "li", "text-align: left;"
119
+ DIV = "div", ""
118
120
 
119
- @overload
120
- @classmethod
121
- def get(
122
- cls,
123
- key: Literal[Tags.POINTS],
124
- ) -> float: ...
125
- @overload
126
- @classmethod
127
- def get(
128
- cls,
129
- key: Literal[
130
- Tags.QUESTIONVARIANT,
131
- Tags.TOLERANCE,
132
- Tags.PICTUREWIDTH,
133
- Tags.ANSPICWIDTH,
134
- Tags.WRONGSIGNPERCENT,
135
- ],
136
- ) -> int: ...
137
- @overload
138
- @classmethod
139
- def get(cls, key: Literal[Tags.INCLUDEINCATS, Tags.GENEXPORTREPORT]) -> bool: ...
140
- @overload
141
- @classmethod
142
- def get(
143
- cls,
144
- key: Literal[
145
- Tags.PICTURESUBFOLDER,
146
- Tags.LOGLEVEL,
147
- Tags.LOGFILE,
148
- Tags.CATEGORIESSHEET,
149
- Tags.IMPORTMODULE,
150
- Tags.WRONGSIGNFB,
151
- ],
152
- ) -> str: ...
153
- @overload
154
- @classmethod
155
- def get(
156
- cls,
157
- key: Literal[Tags.PICTUREFOLDER, Tags.SPREADSHEETPATH],
158
- ) -> Path: ...
159
-
160
- @classmethod
161
- def get(cls, key: Tags):
162
- """Get the typesafe settings value.
163
-
164
- If no setting is made, the default value is returned.
165
- """
166
- try:
167
- raw = cls.values[key]
168
- except KeyError:
169
- default = key.default
170
- if default is None:
171
- return None
172
- logger.debug("Returning the default value for %s", key)
173
- return default
174
- if key.typ() is Path:
175
- path: Path = Path(raw)
176
- try:
177
- path.resolve(strict=True)
178
- except ValueError:
179
- logger.warning(
180
- f"The settingsvalue {key} couldn't be fetched with correct typ",
181
- )
182
- return key.default
183
- logger.debug("Returning path setting: %s = %s", key, path)
184
- return path
185
- try:
186
- return key.typ()(raw)
187
- except (ValueError, TypeError):
188
- logger.warning(
189
- f"The settingsvalue {key} couldn't be fetched with correct typ",
190
- )
191
- return key.default
192
-
193
- @classmethod
194
- def set(
195
- cls,
196
- key: Tags | str,
197
- value: float | bool | Path | str,
198
- ) -> None:
199
- """Set the setting to value."""
200
- if key in Tags:
201
- tag = Tags(key) if not isinstance(key, Tags) else key
202
- try:
203
- cls.values[tag] = tag.typ()(value)
204
- except TypeError:
205
- logger.exception(
206
- "trying to save %s = %s %s with wrong type not possible.",
207
- tag,
208
- value,
209
- type(value),
210
- )
211
- return
212
- logger.info("Saved %s = %s: %s", key, value, tag.typ().__name__)
121
+ def create(self, tag: str | None = None):
122
+ if tag is None:
123
+ tag, style = self.value
213
124
  else:
214
- logger.warning("got invalid local Setting %s = %s", key, value)
125
+ style = self.value[1]
126
+ return ET.Element(tag, dir="ltr", style=style)
127
+
128
+ @property
129
+ def style(
130
+ self,
131
+ ) -> str:
132
+ return self.value[1]
133
+
134
+
135
+ class XMLTags(StrEnum):
136
+ NAME = "name"
137
+ QTEXT = "questiontext"
138
+ QUESTION = "question"
139
+ TEXT = "text"
140
+ PICTURE = "file"
141
+ GENFEEDB = "generalfeedback"
142
+ CORFEEDB = "correctfeedback"
143
+ PCORFEEDB = "partialcorrectfeedback"
144
+ INCORFEEDB = "incorrectfeedback"
145
+ ANSFEEDBACK = "feedback"
146
+ POINTS = "defaultgrade"
147
+ PENALTY = "penalty"
148
+ HIDE = "hidden"
149
+ ID = "idnumber"
150
+ TYPE = "type"
151
+ ANSWER = "answer"
152
+ TOLERANCE = "tolerance"
153
+
154
+
155
+ feedBElements = {
156
+ XMLTags.CORFEEDB: TextElements.SPANGREEN.create(),
157
+ XMLTags.PCORFEEDB: TextElements.SPANORANGE.create(),
158
+ XMLTags.INCORFEEDB: TextElements.SPANRED.create(),
159
+ XMLTags.ANSFEEDBACK: TextElements.SPANGREEN.create(),
160
+ XMLTags.GENFEEDB: TextElements.SPANGREEN.create(),
161
+ }
@@ -34,18 +34,18 @@ class QuestionParser:
34
34
 
35
35
  def setup(self, question: Question) -> None:
36
36
  self.question: Question = question
37
- self.rawInput = question.rawData
37
+ self.rawData = question.rawData
38
38
  self.logger = LogAdapterQuestionID(loggerObj, {"qID": self.question.id})
39
39
  self.logger.debug(
40
40
  "The following Data was provided: %s",
41
- self.rawInput,
41
+ self.rawData,
42
42
  )
43
43
 
44
44
  def hasPicture(self) -> bool:
45
45
  """Create a ``Picture`` object ``question``if the question needs a pic."""
46
46
  if hasattr(self, "picture") and self.question.picture.ready:
47
47
  return True
48
- picKey = self.rawInput.get(Tags.PICTURE)
48
+ picKey = self.rawData.get(Tags.PICTURE)
49
49
  f = self.settings.get(Tags.PICTUREFOLDER)
50
50
  svgFolder = (f / self.question.katName).resolve()
51
51
  if not hasattr(self.question, "picture"):
@@ -53,7 +53,7 @@ class QuestionParser:
53
53
  picKey,
54
54
  svgFolder,
55
55
  self.question.id,
56
- width=self.rawInput.get(Tags.PICTUREWIDTH),
56
+ width=self.rawData.get(Tags.PICTUREWIDTH),
57
57
  )
58
58
  return bool(self.question.picture.ready)
59
59
 
@@ -63,7 +63,7 @@ class QuestionParser:
63
63
  ET.SubElement(
64
64
  ET.SubElement(textHTMLroot, "p"), "b"
65
65
  ).text = f"ID {self.question.id}"
66
- text = self.rawInput[Tags.TEXT]
66
+ text = self.rawData[Tags.TEXT]
67
67
  for t in text:
68
68
  par = TextElements.PLEFT.create()
69
69
  par.text = t
@@ -83,7 +83,7 @@ class QuestionParser:
83
83
  It uses the data from ``self.rawInput`` if ``text`` is type``DFIndex``
84
84
  Otherwise the value of ``text`` will be inserted.
85
85
  """
86
- t = self.rawInput.get(text) if isinstance(text, Tags) else text
86
+ t = self.rawData.get(text) if isinstance(text, Tags) else text
87
87
  if txtEle is False:
88
88
  self.tmpEle.append(eth.getElement(eleName, t, **attribs))
89
89
  elif txtEle is True:
@@ -118,8 +118,8 @@ class QuestionParser:
118
118
 
119
119
  self.htmlRoot = ET.Element("div")
120
120
  self.htmlRoot.append(self.getMainTextElement())
121
- if Tags.BPOINTS in self.rawInput:
122
- bps: list[str] = self.rawInput[Tags.BPOINTS]
121
+ if Tags.BPOINTS in self.rawData:
122
+ bps: list[str] = self.rawData[Tags.BPOINTS]
123
123
  try:
124
124
  bullets: BulletList = BulletList(bps, self.question.id)
125
125
  except IndexError:
@@ -135,7 +135,7 @@ class QuestionParser:
135
135
  if self.hasPicture():
136
136
  self.htmlRoot.append(self.question.picture.htmlTag)
137
137
  textRootElem.append(self.question.picture.element)
138
- if Tags.MEDIACALL in self.rawInput:
138
+ if Tags.MEDIACALL in self.rawData:
139
139
  self.insertScriptedMedia()
140
140
  ansList = self._parseAnswers()
141
141
  if ansList is not None:
@@ -163,7 +163,7 @@ class QuestionParser:
163
163
  span = feedBElements[feedback] if style is None else style.create()
164
164
  if text is None:
165
165
  self.logger.error("Giving a feedback without providing text is nonsens")
166
- text = self.rawInput.get(Tags.GENERALFB)
166
+ text = self.rawData.get(Tags.GENERALFB)
167
167
  ele = ET.Element(feedback, format="html")
168
168
  par = TextElements.PLEFT.create()
169
169
  span.text = text
@@ -173,13 +173,13 @@ class QuestionParser:
173
173
 
174
174
  def insertScriptedMedia(self) -> None:
175
175
  """Load the scripts, insert the div and call a Function."""
176
- for script in self.rawInput.get(Tags.MEDIASCRIPTS):
176
+ for script in self.rawData.get(Tags.MEDIASCRIPTS):
177
177
  ET.SubElement(
178
178
  self.htmlRoot, "script", type="text/javascript", src=script
179
179
  ).text = ""
180
180
  divId = f"scriptedMedia-{self.question.id}"
181
181
  ET.SubElement(self.htmlRoot, "div", id=divId).text = ""
182
- scriptCall = MediaCall(self.rawInput.get(Tags.MEDIACALL), divId=divId)
182
+ scriptCall = MediaCall(self.rawData.get(Tags.MEDIACALL), divId=divId)
183
183
  self.htmlRoot.append(scriptCall.element)
184
184
  if isinstance(self.question, ParametricQuestion):
185
185
  self.question.updateQue.append(scriptCall)
@@ -209,7 +209,7 @@ class QuestionParser:
209
209
  format=format,
210
210
  )
211
211
  if feedback is None:
212
- feedback = self.rawInput.get(Tags.TRUEFB)
212
+ feedback = self.rawData.get(Tags.TRUEFB)
213
213
  ansEle.append(
214
214
  self.getFeedBEle(
215
215
  feedback=XMLTags.ANSFEEDBACK,
@@ -217,6 +217,6 @@ class QuestionParser:
217
217
  style=TextElements.SPANGREEN,
218
218
  ),
219
219
  )
220
- absTolerance = round(result * self.rawInput.get(Tags.TOLERANCE), 4)
220
+ absTolerance = round(result * self.rawData.get(Tags.TOLERANCE), 4)
221
221
  ansEle.append(eth.getElement(XMLTags.TOLERANCE, text=str(absTolerance)))
222
222
  return ansEle
@@ -120,6 +120,7 @@ class Question:
120
120
  mandatoryTags: ClassVar[dict[Tags, type | UnionType]] = {
121
121
  Tags.TEXT: str,
122
122
  Tags.NAME: str,
123
+ Tags.NUMBER: int,
123
124
  Tags.TYPE: str,
124
125
  }
125
126
  optionalTags: ClassVar[dict[Tags, type | UnionType]] = {
@@ -0,0 +1,121 @@
1
+ """Settings module provides the adjusted subclass of ``PySide6.QtCore.QSettings``."""
2
+
3
+ import logging
4
+ from pathlib import Path
5
+ from typing import ClassVar, Literal, overload
6
+
7
+ from excel2moodle.core.globals import Tags
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+
12
+ class Settings:
13
+ values: ClassVar[dict[str, str | float | Path | list]] = {}
14
+
15
+ def __contains__(self, tag: Tags) -> bool:
16
+ return bool(tag in type(self).values)
17
+
18
+ @classmethod
19
+ def clear(cls) -> None:
20
+ cls.values.clear()
21
+
22
+ @classmethod
23
+ def pop(cls, key: str):
24
+ return cls.values.pop(key)
25
+
26
+ @overload
27
+ @classmethod
28
+ def get(
29
+ cls,
30
+ key: Literal[Tags.POINTS],
31
+ ) -> float: ...
32
+ @overload
33
+ @classmethod
34
+ def get(
35
+ cls,
36
+ key: Literal[
37
+ Tags.QUESTIONVARIANT,
38
+ Tags.TOLERANCE,
39
+ Tags.PICTUREWIDTH,
40
+ Tags.ANSPICWIDTH,
41
+ Tags.WRONGSIGNPERCENT,
42
+ ],
43
+ ) -> int: ...
44
+ @overload
45
+ @classmethod
46
+ def get(cls, key: Literal[Tags.INCLUDEINCATS, Tags.GENEXPORTREPORT]) -> bool: ...
47
+ @overload
48
+ @classmethod
49
+ def get(
50
+ cls,
51
+ key: Literal[
52
+ Tags.PICTURESUBFOLDER,
53
+ Tags.LOGLEVEL,
54
+ Tags.LOGFILE,
55
+ Tags.CATEGORIESSHEET,
56
+ Tags.IMPORTMODULE,
57
+ Tags.WRONGSIGNFB,
58
+ ],
59
+ ) -> str: ...
60
+ @overload
61
+ @classmethod
62
+ def get(
63
+ cls,
64
+ key: Literal[Tags.PICTUREFOLDER, Tags.SPREADSHEETPATH],
65
+ ) -> Path: ...
66
+
67
+ @classmethod
68
+ def get(cls, key: Tags):
69
+ """Get the typesafe settings value.
70
+
71
+ If no setting is made, the default value is returned.
72
+ """
73
+ try:
74
+ raw = cls.values[key]
75
+ except KeyError:
76
+ default = key.default
77
+ if default is None:
78
+ return None
79
+ logger.debug("Returning the default value for %s", key)
80
+ return default
81
+ if key.typ() is Path:
82
+ path: Path = Path(raw)
83
+ try:
84
+ path.resolve(strict=True)
85
+ except ValueError:
86
+ logger.warning(
87
+ f"The settingsvalue {key} couldn't be fetched with correct typ",
88
+ )
89
+ return key.default
90
+ logger.debug("Returning path setting: %s = %s", key, path)
91
+ return path
92
+ try:
93
+ return key.typ()(raw)
94
+ except (ValueError, TypeError):
95
+ logger.warning(
96
+ f"The settingsvalue {key} couldn't be fetched with correct typ",
97
+ )
98
+ return key.default
99
+
100
+ @classmethod
101
+ def set(
102
+ cls,
103
+ key: Tags | str,
104
+ value: float | bool | Path | str,
105
+ ) -> None:
106
+ """Set the setting to value."""
107
+ if key in Tags:
108
+ tag = Tags(key) if not isinstance(key, Tags) else key
109
+ try:
110
+ cls.values[tag] = tag.typ()(value)
111
+ except TypeError:
112
+ logger.exception(
113
+ "trying to save %s = %s %s with wrong type not possible.",
114
+ tag,
115
+ value,
116
+ type(value),
117
+ )
118
+ return
119
+ logger.info("Saved %s = %s: %s", key, value, tag.typ().__name__)
120
+ else:
121
+ logger.warning("got invalid local Setting %s = %s", key, value)
@@ -1,4 +1,4 @@
1
- """Implementation of tde cloze question type.
1
+ """Implementation of the cloze question type.
2
2
 
3
3
  This question type is like the NFM but supports multiple fields of answers.
4
4
  All Answers are calculated off an equation using the same variables.
@@ -68,7 +68,7 @@ class ClozePart:
68
68
  self.logger.debug("MC Answer Part already up to date.")
69
69
  return
70
70
  if self.typ == "NFM":
71
- result = self.result.getResult(variant)
71
+ result = self.result.getResult(number=variant, equation=self.num)
72
72
  self._element.text = self.getNumericAnsStr(
73
73
  self.question.rawData,
74
74
  result,
@@ -263,11 +263,11 @@ class ClozeQuestionParser(NFMQuestionParser):
263
263
 
264
264
  def _setupParts(self) -> None:
265
265
  parts: dict[int, ClozePart] = {}
266
- for key in self.rawInput:
266
+ for key in self.rawData:
267
267
  if key.startswith(Tags.QUESTIONPART):
268
268
  partNumber = self.getPartNumber(key)
269
269
  parts[partNumber] = ClozePart(
270
- self.question, self.rawInput[key], partNumber
270
+ self.question, self.rawData[key], partNumber
271
271
  )
272
272
  partsNum = len(parts)
273
273
  equations: dict[int, str] = self._getPartValues(Tags.RESULT)
@@ -310,7 +310,7 @@ class ClozeQuestionParser(NFMQuestionParser):
310
310
  msg = f"Unclear Parts are defined. Either define `true:{num}` and `false:{num}` or `result:{num}` "
311
311
  raise QNotParsedException(msg, self.question.id)
312
312
  if len(points) == 0:
313
- pts = round(self.rawInput.get(Tags.POINTS) / partsNum, 3)
313
+ pts = round(self.rawData.get(Tags.POINTS) / partsNum, 3)
314
314
  point = self._roundClozePartPoints(pts)
315
315
  for part in parts.values():
316
316
  part.points = point
@@ -325,7 +325,7 @@ class ClozeQuestionParser(NFMQuestionParser):
325
325
  def _roundClozePartPoints(self, points: float | None = None) -> int:
326
326
  """Get the integer points for the cloze part."""
327
327
  if points is None:
328
- points = self.rawInput.get(Tags.POINTS)
328
+ points = self.rawData.get(Tags.POINTS)
329
329
  corrPoints: int = round(points)
330
330
  if not math.isclose(corrPoints, points):
331
331
  self.logger.warning(
@@ -347,8 +347,8 @@ class ClozeQuestionParser(NFMQuestionParser):
347
347
  ) -> dict[int, list[str]]: ...
348
348
  def _getPartValues(self, Tag):
349
349
  tagValues: dict = {
350
- self.getPartNumber(key): self.rawInput[key]
351
- for key in self.rawInput
350
+ self.getPartNumber(key): self.rawData[key]
351
+ for key in self.rawData
352
352
  if key.startswith(Tag)
353
353
  }
354
354
  return tagValues
@@ -363,7 +363,7 @@ class ClozeQuestionParser(NFMQuestionParser):
363
363
  if part.typ == "NFM":
364
364
  result = self.question.parametrics.getResult(1, partNum)
365
365
  ansStr = ClozePart.getNumericAnsStr(
366
- self.rawInput,
366
+ self.rawData,
367
367
  result=result,
368
368
  points=part.points,
369
369
  )
@@ -28,8 +28,8 @@ class MCQuestion(Question):
28
28
  "showstandardinstruction": "0",
29
29
  "shownumcorrect": "",
30
30
  }
31
- mcOpt: ClassVar[dict[Tags, type | UnionType]] = {}
32
- mcMand: ClassVar[dict[Tags, type | UnionType]] = {
31
+ optionalTags: ClassVar[dict[Tags, type | UnionType]] = {}
32
+ mandatoryTags: ClassVar[dict[Tags, type | UnionType]] = {
33
33
  Tags.TRUE: str,
34
34
  Tags.FALSE: str,
35
35
  Tags.ANSTYPE: str,
@@ -64,9 +64,9 @@ class MCQuestionParser(QuestionParser):
64
64
  elementList: list[ET.Element] = []
65
65
  if feedbackList is None:
66
66
  if fraction > 0:
67
- feedbackList = [self.rawInput.get(Tags.FALSEFB) for _ in answerList]
67
+ feedbackList = [self.rawData.get(Tags.FALSEFB) for _ in answerList]
68
68
  else:
69
- feedbackList = [self.rawInput.get(Tags.TRUEFB) for _ in answerList]
69
+ feedbackList = [self.rawData.get(Tags.TRUEFB) for _ in answerList]
70
70
  for i, ans in enumerate(answerList):
71
71
  p = TextElements.PLEFT.create()
72
72
  if self.answerType == "picture":
@@ -101,21 +101,21 @@ class MCQuestionParser(QuestionParser):
101
101
  return elementList
102
102
 
103
103
  def _parseAnswers(self) -> list[ET.Element]:
104
- self.answerType = self.rawInput.get(Tags.ANSTYPE)
104
+ self.answerType = self.rawData.get(Tags.ANSTYPE)
105
105
  if self.answerType not in self.question.AnsStyles:
106
106
  msg = f"The Answer style: {self.answerType} is not supported"
107
107
  raise InvalidFieldException(msg, self.question.id, Tags.ANSTYPE)
108
108
  if self.answerType == "picture":
109
109
  f = self.settings.get(Tags.PICTUREFOLDER)
110
110
  imgFolder = (f / self.question.katName).resolve()
111
- width = self.rawInput.get(Tags.ANSPICWIDTH)
111
+ width = self.rawData.get(Tags.ANSPICWIDTH)
112
112
  self.trueImgs: list[Picture] = [
113
113
  Picture(t, imgFolder, self.question.id, width=width)
114
- for t in self.rawInput.get(Tags.TRUE)
114
+ for t in self.rawData.get(Tags.TRUE)
115
115
  ]
116
116
  self.falseImgs: list[Picture] = [
117
117
  Picture(t, imgFolder, self.question.id, width=width)
118
- for t in self.rawInput.get(Tags.FALSE)
118
+ for t in self.rawData.get(Tags.FALSE)
119
119
  ]
120
120
  trueAnsList: list[str] = [pic.htmlTag for pic in self.trueImgs if pic.ready]
121
121
  falseAList: list[str] = [pic.htmlTag for pic in self.falseImgs if pic.ready]
@@ -124,26 +124,26 @@ class MCQuestionParser(QuestionParser):
124
124
  raise QNotParsedException(msg, self.question.id)
125
125
  else:
126
126
  trueAnsList: list[str] = stringHelpers.texWrapper(
127
- self.rawInput.get(Tags.TRUE), style=self.answerType
127
+ self.rawData.get(Tags.TRUE), style=self.answerType
128
128
  )
129
129
  self.logger.debug(f"got the following true answers \n {trueAnsList=}")
130
130
  falseAList: list[str] = stringHelpers.texWrapper(
131
- self.rawInput.get(Tags.FALSE), style=self.answerType
131
+ self.rawData.get(Tags.FALSE), style=self.answerType
132
132
  )
133
133
  self.logger.debug(f"got the following false answers \n {falseAList=}")
134
- trueFbs = self.rawInput.get(Tags.TRUEANSFB)
135
- falseFbs = self.rawInput.get(Tags.FALSEANSFB)
134
+ trueFbs = self.rawData.get(Tags.TRUEANSFB)
135
+ falseFbs = self.rawData.get(Tags.FALSEANSFB)
136
136
  if trueFbs is None:
137
- trueFbs = [self.rawInput.get(Tags.FALSEFB) for _ in trueAnsList]
137
+ trueFbs = [self.rawData.get(Tags.FALSEFB) for _ in trueAnsList]
138
138
  if falseFbs is None:
139
- falseFbs = [self.rawInput.get(Tags.TRUEFB) for _ in falseAList]
139
+ falseFbs = [self.rawData.get(Tags.TRUEFB) for _ in falseAList]
140
140
  if len(trueFbs) < len(trueAnsList):
141
141
  self.logger.warning(
142
142
  "There are less true-feedbacks than true-answers given. Using fallback feedback"
143
143
  )
144
144
  delta = len(trueAnsList) - len(trueFbs)
145
145
  while delta > 0:
146
- trueFbs.append(self.rawInput.get(Tags.TRUEFB))
146
+ trueFbs.append(self.rawData.get(Tags.TRUEFB))
147
147
  delta -= 1
148
148
  if len(falseFbs) < len(falseAList):
149
149
  self.logger.warning(
@@ -151,7 +151,7 @@ class MCQuestionParser(QuestionParser):
151
151
  )
152
152
  delta = len(falseAList) - len(falseFbs)
153
153
  while delta > 0:
154
- falseFbs.append(self.rawInput.get(Tags.TRUEFB))
154
+ falseFbs.append(self.rawData.get(Tags.TRUEFB))
155
155
  delta -= 1
156
156
  truefrac = 1 / len(trueAnsList) * 100
157
157
  falsefrac = 1 / len(falseAList) * (-100)
@@ -176,5 +176,5 @@ class MCQuestionParser(QuestionParser):
176
176
  XMLTags.INCORFEEDB: Tags.FALSEFB,
177
177
  }
178
178
  for feedb, tag in feedBacks.items():
179
- self.tmpEle.append(self.getFeedBEle(feedb, text=self.rawInput.get(tag)))
179
+ self.tmpEle.append(self.getFeedBEle(feedb, text=self.rawData.get(tag)))
180
180
  self._finalizeParsing()
@@ -17,10 +17,10 @@ class NFQuestion(Question):
17
17
  def __init__(self, *args, **kwargs) -> None:
18
18
  super().__init__(*args, **kwargs)
19
19
 
20
- nfOpt: ClassVar[dict[Tags, type | UnionType]] = {
20
+ optionalTags: ClassVar[dict[Tags, type | UnionType]] = {
21
21
  Tags.BPOINTS: str,
22
22
  }
23
- nfMand: ClassVar[dict[Tags, type | UnionType]] = {
23
+ mandatoryTags: ClassVar[dict[Tags, type | UnionType]] = {
24
24
  Tags.RESULT: float | int,
25
25
  }
26
26
 
@@ -37,13 +37,13 @@ class NFQuestionParser(QuestionParser):
37
37
  super().setup(question)
38
38
 
39
39
  def _parseAnswers(self) -> list[ET.Element]:
40
- result: float = self.rawInput.get(Tags.RESULT)
40
+ result: float = self.rawData.get(Tags.RESULT)
41
41
  ansEle: list[ET.Element] = []
42
42
  ansEle.append(self.getNumericAnsElement(result=result))
43
43
  return ansEle
44
44
 
45
45
  def _finalizeParsing(self) -> None:
46
46
  self.tmpEle.append(
47
- self.getFeedBEle(XMLTags.GENFEEDB, text=self.rawInput.get(Tags.GENERALFB))
47
+ self.getFeedBEle(XMLTags.GENFEEDB, text=self.rawData.get(Tags.GENERALFB))
48
48
  )
49
49
  return super()._finalizeParsing()
@@ -14,7 +14,7 @@ from excel2moodle.core.question import ParametricQuestion, Parametrics
14
14
 
15
15
 
16
16
  class NFMQuestion(ParametricQuestion):
17
- nfmMand: ClassVar[dict[Tags, type | UnionType]] = {
17
+ mandatoryTags: ClassVar[dict[Tags, type | UnionType]] = {
18
18
  Tags.RESULT: str,
19
19
  Tags.BPOINTS: str,
20
20
  }
@@ -58,20 +58,20 @@ class NFMQuestionParser(QuestionParser):
58
58
  def _parseAnswers(self) -> list[ET.Element]:
59
59
  variables = self.question.bulletList.getVariablesDict(self.question)
60
60
  self.question.parametrics = Parametrics(
61
- self.rawInput.get(Tags.EQUATION),
62
- self.rawInput.get(Tags.FIRSTRESULT),
61
+ self.rawData.get(Tags.EQUATION),
62
+ self.rawData.get(Tags.FIRSTRESULT),
63
63
  self.question.id,
64
64
  )
65
65
  self.question.parametrics.variables = variables
66
66
  self.question.answerElement = self.getNumericAnsElement()
67
67
  self.question.answerElementWrongSign = self.getNumericAnsElement(
68
- fraction=self.rawInput.get(Tags.WRONGSIGNPERCENT),
69
- feedback=self.rawInput.get(Tags.WRONGSIGNFB),
68
+ fraction=self.rawData.get(Tags.WRONGSIGNPERCENT),
69
+ feedback=self.rawData.get(Tags.WRONGSIGNFB),
70
70
  )
71
71
  return [self.question.answerElement, self.question.answerElementWrongSign]
72
72
 
73
73
  def _finalizeParsing(self) -> None:
74
74
  self.tmpEle.append(
75
- self.getFeedBEle(XMLTags.GENFEEDB, text=self.rawInput.get(Tags.GENERALFB))
75
+ self.getFeedBEle(XMLTags.GENFEEDB, text=self.rawData.get(Tags.GENERALFB))
76
76
  )
77
77
  return super()._finalizeParsing()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: excel2moodle
3
- Version: 0.7.1
3
+ Version: 0.7.2
4
4
  Summary: A package for converting questions from a spreadsheet, to valid moodle-xml
5
5
  Author: Jakob Bosse
6
6
  License-Expression: GPL-3.0-or-later
@@ -90,6 +90,19 @@ If You want to support my work as well, you can by me a [coffee](https://ko-fi.c
90
90
 
91
91
  # Changelogs
92
92
 
93
+ ## 0.7.2 (2025-10-11)
94
+ small important bugfixes
95
+
96
+ ### improvement (2 changes)
97
+
98
+ - [BulletPoints are decoded using regex to allow multi word names](https://gitlab.com/jbosse3/excel2moodle/-/commit/7e32e9817323054d84a0dfb9a0a241c702fd096d)
99
+ - [Restructured globals, renamed rawInput to rawData](https://gitlab.com/jbosse3/excel2moodle/-/commit/effd9c3cd196b36d49204fe715acc1ffb124549c)
100
+
101
+ ### bugfix (2 changes)
102
+
103
+ - [Added the number do the mandatory tags because it is](https://gitlab.com/jbosse3/excel2moodle/-/commit/56e9b69d71504dffe7c235d69ff44dec6931db28)
104
+ - [fixed assigning the first result to all clozes](https://gitlab.com/jbosse3/excel2moodle/-/commit/09a281c253502adc23442892be03aac36e6ea720)
105
+
93
106
  ## 0.7.1 (2025-10-04)
94
107
  feedbacking improved
95
108
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "excel2moodle"
7
- version = "0.7.1"
7
+ version = "0.7.2"
8
8
  authors = [
9
9
  { name="Jakob Bosse" },
10
10
  ]
@@ -2,6 +2,7 @@ from pathlib import Path
2
2
 
3
3
  import pytest
4
4
 
5
+ from excel2moodle.core.bullets import BulletList
5
6
  from excel2moodle.core.dataStructure import QuestionDB
6
7
  from excel2moodle.core.settings import Settings, Tags
7
8
 
@@ -43,3 +44,22 @@ def test_bulletVariants(variant, bulletName, bulletStr) -> None:
43
44
  question.getUpdatedElement(variant=variant)
44
45
  for bullet in question.bulletList.bullets[bulletName].element:
45
46
  assert bullet == bulletStr
47
+
48
+
49
+ @pytest.mark.parametrize(
50
+ ("bulletString", "name", "var", "value", "unit"),
51
+ [
52
+ (r"Große Kraft F = 25,0 kN", "Große Kraft", "F", 25.0, "kN"),
53
+ (r"Lange Kraft F_l = 22,0 kN", "Lange Kraft", "F_l", 22.0, "kN"),
54
+ (r"Streckenlast p = 15,0 kN/m", "Streckenlast", "p", 15, "kN/m"),
55
+ (r"Längste Strecke l_{max} = 15.0 km", "Längste Strecke", "l_{max}", 15, "km"),
56
+ (r"Strng Var q_{st} = 33,339 kN/m²", "Strng Var", "q_{st}", 33.339, "kN/m²"),
57
+ (r"Max Sp \sigma_{max} = 33 kN/m^2", "Max Sp", r"\sigma_{max}", 33, "kN/m^2"),
58
+ ],
59
+ )
60
+ def test_paresBPointNames(bulletString, name, var, value, unit) -> None:
61
+ bList: BulletList = BulletList([bulletString], "0000")
62
+ assert bList.bullets.get(1).name == name
63
+ assert bList.bullets.get(1).value == value
64
+ assert bList.bullets.get(1).var == var
65
+ assert bList.bullets.get(1).unit == unit
@@ -1,91 +0,0 @@
1
- from enum import Enum, StrEnum
2
-
3
- import lxml.etree as ET
4
-
5
- from excel2moodle.core.settings import Tags
6
-
7
- QUESTION_TYPES = {
8
- "NF": "numerical",
9
- "NFM": "numerical",
10
- "MC": "multichoice",
11
- "CLOZE": "cloze",
12
- }
13
-
14
-
15
- class TextElements(Enum):
16
- PLEFT = "p", "text-align: left;"
17
- SPANRED = "span", "color: rgb(239, 69, 64)"
18
- SPANGREEN = "span", "color: rgb(152, 202, 62)"
19
- SPANORANGE = "span", "color: rgb(152, 100, 100)"
20
- ULIST = "ul", ""
21
- LISTITEM = "li", "text-align: left;"
22
- DIV = "div", ""
23
-
24
- def create(self, tag: str | None = None):
25
- if tag is None:
26
- tag, style = self.value
27
- else:
28
- style = self.value[1]
29
- return ET.Element(tag, dir="ltr", style=style)
30
-
31
- @property
32
- def style(
33
- self,
34
- ) -> str:
35
- return self.value[1]
36
-
37
-
38
- class XMLTags(StrEnum):
39
- def __new__(cls, value: str, dfkey: Tags | None = None):
40
- obj = str.__new__(cls, value)
41
- obj._value_ = value
42
- if dfkey is not None:
43
- obj._dfkey_ = dfkey
44
- return obj
45
-
46
- def __init__(self, _: str, dfkey: Tags | None = None, getEle=None) -> None:
47
- if isinstance(dfkey, Tags):
48
- self._dfkey_: str = dfkey
49
- if getEle:
50
- self._getEle_: object = getEle
51
-
52
- @property
53
- def dfkey(self) -> str:
54
- return self._dfkey_
55
-
56
- def set(self, getEle) -> None:
57
- self._getEle_ = getEle
58
-
59
- def __repr__(self) -> str:
60
- msg = []
61
- msg.append(f"XML Tag {self.value=}")
62
- if hasattr(self, "_dfkey_"):
63
- msg.append(f"Df Key {self.dfkey=}")
64
- return "\n".join(msg)
65
-
66
- NAME = "name", Tags.NAME
67
- QTEXT = "questiontext", Tags.TEXT
68
- QUESTION = "question"
69
- TEXT = "text"
70
- PICTURE = "file", Tags.PICTURE
71
- GENFEEDB = "generalfeedback"
72
- CORFEEDB = "correctfeedback"
73
- PCORFEEDB = "partialcorrectfeedback"
74
- INCORFEEDB = "incorrectfeedback"
75
- ANSFEEDBACK = "feedback"
76
- POINTS = "defaultgrade"
77
- PENALTY = "penalty"
78
- HIDE = "hidden"
79
- ID = "idnumber"
80
- TYPE = "type"
81
- ANSWER = "answer"
82
- TOLERANCE = "tolerance"
83
-
84
-
85
- feedBElements = {
86
- XMLTags.CORFEEDB: TextElements.SPANGREEN.create(),
87
- XMLTags.PCORFEEDB: TextElements.SPANORANGE.create(),
88
- XMLTags.INCORFEEDB: TextElements.SPANRED.create(),
89
- XMLTags.ANSFEEDBACK: TextElements.SPANGREEN.create(),
90
- XMLTags.GENFEEDB: TextElements.SPANGREEN.create(),
91
- }
File without changes
File without changes