excel2moodle 0.7.1__tar.gz → 0.7.3__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.
- {excel2moodle-0.7.1 → excel2moodle-0.7.3}/PKG-INFO +19 -1
- {excel2moodle-0.7.1 → excel2moodle-0.7.3}/README.md +18 -0
- {excel2moodle-0.7.1 → excel2moodle-0.7.3}/excel2moodle/core/bullets.py +28 -10
- excel2moodle-0.7.1/excel2moodle/core/settings.py → excel2moodle-0.7.3/excel2moodle/core/globals.py +100 -153
- {excel2moodle-0.7.1 → excel2moodle-0.7.3}/excel2moodle/core/parser.py +14 -14
- {excel2moodle-0.7.1 → excel2moodle-0.7.3}/excel2moodle/core/question.py +1 -0
- excel2moodle-0.7.3/excel2moodle/core/settings.py +121 -0
- {excel2moodle-0.7.1 → excel2moodle-0.7.3}/excel2moodle/question_types/cloze.py +9 -9
- {excel2moodle-0.7.1 → excel2moodle-0.7.3}/excel2moodle/question_types/mc.py +17 -17
- {excel2moodle-0.7.1 → excel2moodle-0.7.3}/excel2moodle/question_types/nf.py +4 -4
- {excel2moodle-0.7.1 → excel2moodle-0.7.3}/excel2moodle/question_types/nfm.py +6 -6
- {excel2moodle-0.7.1 → excel2moodle-0.7.3}/excel2moodle.egg-info/PKG-INFO +19 -1
- {excel2moodle-0.7.1 → excel2moodle-0.7.3}/pyproject.toml +1 -1
- {excel2moodle-0.7.1 → excel2moodle-0.7.3}/test/test_bullets.py +22 -0
- excel2moodle-0.7.1/excel2moodle/core/globals.py +0 -91
- {excel2moodle-0.7.1 → excel2moodle-0.7.3}/LICENSE +0 -0
- {excel2moodle-0.7.1 → excel2moodle-0.7.3}/excel2moodle/__init__.py +0 -0
- {excel2moodle-0.7.1 → excel2moodle-0.7.3}/excel2moodle/__main__.py +0 -0
- {excel2moodle-0.7.1 → excel2moodle-0.7.3}/excel2moodle/core/__init__.py +0 -0
- {excel2moodle-0.7.1 → excel2moodle-0.7.3}/excel2moodle/core/category.py +0 -0
- {excel2moodle-0.7.1 → excel2moodle-0.7.3}/excel2moodle/core/dataStructure.py +0 -0
- {excel2moodle-0.7.1 → excel2moodle-0.7.3}/excel2moodle/core/etHelpers.py +0 -0
- {excel2moodle-0.7.1 → excel2moodle-0.7.3}/excel2moodle/core/exceptions.py +0 -0
- {excel2moodle-0.7.1 → excel2moodle-0.7.3}/excel2moodle/core/stringHelpers.py +0 -0
- {excel2moodle-0.7.1 → excel2moodle-0.7.3}/excel2moodle/core/validator.py +0 -0
- {excel2moodle-0.7.1 → excel2moodle-0.7.3}/excel2moodle/extra/__init__.py +0 -0
- {excel2moodle-0.7.1 → excel2moodle-0.7.3}/excel2moodle/extra/equationChecker.py +0 -0
- {excel2moodle-0.7.1 → excel2moodle-0.7.3}/excel2moodle/extra/scriptCaller.py +0 -0
- {excel2moodle-0.7.1 → excel2moodle-0.7.3}/excel2moodle/extra/updateQuery.py +0 -0
- {excel2moodle-0.7.1 → excel2moodle-0.7.3}/excel2moodle/extra/variableGenerator.py +0 -0
- {excel2moodle-0.7.1 → excel2moodle-0.7.3}/excel2moodle/logger.py +0 -0
- {excel2moodle-0.7.1 → excel2moodle-0.7.3}/excel2moodle/question_types/__init__.py +0 -0
- {excel2moodle-0.7.1 → excel2moodle-0.7.3}/excel2moodle/ui/UI_equationChecker.py +0 -0
- {excel2moodle-0.7.1 → excel2moodle-0.7.3}/excel2moodle/ui/UI_exportSettingsDialog.py +0 -0
- {excel2moodle-0.7.1 → excel2moodle-0.7.3}/excel2moodle/ui/UI_mainWindow.py +0 -0
- {excel2moodle-0.7.1 → excel2moodle-0.7.3}/excel2moodle/ui/UI_updateDlg.py +0 -0
- {excel2moodle-0.7.1 → excel2moodle-0.7.3}/excel2moodle/ui/UI_variableGenerator.py +0 -0
- {excel2moodle-0.7.1 → excel2moodle-0.7.3}/excel2moodle/ui/UI_variantDialog.py +0 -0
- {excel2moodle-0.7.1 → excel2moodle-0.7.3}/excel2moodle/ui/__init__.py +0 -0
- {excel2moodle-0.7.1 → excel2moodle-0.7.3}/excel2moodle/ui/appUi.py +0 -0
- {excel2moodle-0.7.1 → excel2moodle-0.7.3}/excel2moodle/ui/dialogs.py +0 -0
- {excel2moodle-0.7.1 → excel2moodle-0.7.3}/excel2moodle/ui/treewidget.py +0 -0
- {excel2moodle-0.7.1 → excel2moodle-0.7.3}/excel2moodle.egg-info/SOURCES.txt +0 -0
- {excel2moodle-0.7.1 → excel2moodle-0.7.3}/excel2moodle.egg-info/dependency_links.txt +0 -0
- {excel2moodle-0.7.1 → excel2moodle-0.7.3}/excel2moodle.egg-info/entry_points.txt +0 -0
- {excel2moodle-0.7.1 → excel2moodle-0.7.3}/excel2moodle.egg-info/requires.txt +0 -0
- {excel2moodle-0.7.1 → excel2moodle-0.7.3}/excel2moodle.egg-info/top_level.txt +0 -0
- {excel2moodle-0.7.1 → excel2moodle-0.7.3}/setup.cfg +0 -0
- {excel2moodle-0.7.1 → excel2moodle-0.7.3}/test/test_feedbacking.py +0 -0
- {excel2moodle-0.7.1 → excel2moodle-0.7.3}/test/test_nfmParsing.py +0 -0
- {excel2moodle-0.7.1 → excel2moodle-0.7.3}/test/test_parseQuestion.py +0 -0
- {excel2moodle-0.7.1 → excel2moodle-0.7.3}/test/test_picture.py +0 -0
- {excel2moodle-0.7.1 → excel2moodle-0.7.3}/test/test_questionDataGet.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: excel2moodle
|
3
|
-
Version: 0.7.
|
3
|
+
Version: 0.7.3
|
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,24 @@ 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.3 (2025-10-12)
|
94
|
+
further bulletPoint improvements
|
95
|
+
|
96
|
+
No changes.
|
97
|
+
|
98
|
+
## 0.7.2 (2025-10-11)
|
99
|
+
small important bugfixes
|
100
|
+
|
101
|
+
### improvement (2 changes)
|
102
|
+
|
103
|
+
- [BulletPoints are decoded using regex to allow multi word names](https://gitlab.com/jbosse3/excel2moodle/-/commit/7e32e9817323054d84a0dfb9a0a241c702fd096d)
|
104
|
+
- [Restructured globals, renamed rawInput to rawData](https://gitlab.com/jbosse3/excel2moodle/-/commit/effd9c3cd196b36d49204fe715acc1ffb124549c)
|
105
|
+
|
106
|
+
### bugfix (2 changes)
|
107
|
+
|
108
|
+
- [Added the number do the mandatory tags because it is](https://gitlab.com/jbosse3/excel2moodle/-/commit/56e9b69d71504dffe7c235d69ff44dec6931db28)
|
109
|
+
- [fixed assigning the first result to all clozes](https://gitlab.com/jbosse3/excel2moodle/-/commit/09a281c253502adc23442892be03aac36e6ea720)
|
110
|
+
|
93
111
|
## 0.7.1 (2025-10-04)
|
94
112
|
feedbacking improved
|
95
113
|
|
@@ -68,6 +68,24 @@ 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.3 (2025-10-12)
|
72
|
+
further bulletPoint improvements
|
73
|
+
|
74
|
+
No changes.
|
75
|
+
|
76
|
+
## 0.7.2 (2025-10-11)
|
77
|
+
small important bugfixes
|
78
|
+
|
79
|
+
### improvement (2 changes)
|
80
|
+
|
81
|
+
- [BulletPoints are decoded using regex to allow multi word names](https://gitlab.com/jbosse3/excel2moodle/-/commit/7e32e9817323054d84a0dfb9a0a241c702fd096d)
|
82
|
+
- [Restructured globals, renamed rawInput to rawData](https://gitlab.com/jbosse3/excel2moodle/-/commit/effd9c3cd196b36d49204fe715acc1ffb124549c)
|
83
|
+
|
84
|
+
### bugfix (2 changes)
|
85
|
+
|
86
|
+
- [Added the number do the mandatory tags because it is](https://gitlab.com/jbosse3/excel2moodle/-/commit/56e9b69d71504dffe7c235d69ff44dec6931db28)
|
87
|
+
- [fixed assigning the first result to all clozes](https://gitlab.com/jbosse3/excel2moodle/-/commit/09a281c253502adc23442892be03aac36e6ea720)
|
88
|
+
|
71
89
|
## 0.7.1 (2025-10-04)
|
72
90
|
feedbacking improved
|
73
91
|
|
@@ -57,24 +57,41 @@ 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"
|
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
|
-
|
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(
|
86
|
+
num: float = float(value.replace(",", "."))
|
72
87
|
bulletName = i + 1
|
73
88
|
else:
|
74
89
|
bulletName = match.group(1)
|
75
90
|
num: float = 0.0
|
76
91
|
self.logger.debug("Got an variable bulletItem, match: %s", match)
|
77
|
-
|
92
|
+
# for userfriendliness because % would be the comment in latex
|
93
|
+
if unit == "%":
|
94
|
+
unit = r"\%"
|
78
95
|
self.bullets[bulletName] = BulletP(name=name, var=var, unit=unit, value=num)
|
79
96
|
self.element.append(self.bullets[bulletName].element)
|
80
97
|
return self.element
|
@@ -86,6 +103,7 @@ class BulletP:
|
|
86
103
|
self.var: str = var
|
87
104
|
self.unit: str = unit
|
88
105
|
self.element: ET.Element
|
106
|
+
self.value: float = value
|
89
107
|
self.update(value=value)
|
90
108
|
|
91
109
|
def update(self, value: float = 1) -> None:
|
excel2moodle-0.7.1/excel2moodle/core/settings.py → excel2moodle-0.7.3/excel2moodle/core/globals.py
RENAMED
@@ -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
|
-
|
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
|
-
|
106
|
-
|
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
|
109
|
-
|
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
|
-
|
116
|
-
|
117
|
-
|
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
|
-
|
120
|
-
|
121
|
-
|
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
|
-
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
122
|
-
bps: list[str] = self.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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
|
@@ -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
|
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.
|
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.
|
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.
|
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.
|
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.
|
351
|
-
for key in self.
|
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.
|
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
|
-
|
32
|
-
|
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.
|
67
|
+
feedbackList = [self.rawData.get(Tags.FALSEFB) for _ in answerList]
|
68
68
|
else:
|
69
|
-
feedbackList = [self.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
135
|
-
falseFbs = self.
|
134
|
+
trueFbs = self.rawData.get(Tags.TRUEANSFB)
|
135
|
+
falseFbs = self.rawData.get(Tags.FALSEANSFB)
|
136
136
|
if trueFbs is None:
|
137
|
-
trueFbs = [self.
|
137
|
+
trueFbs = [self.rawData.get(Tags.FALSEFB) for _ in trueAnsList]
|
138
138
|
if falseFbs is None:
|
139
|
-
falseFbs = [self.
|
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.
|
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.
|
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.
|
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
|
-
|
20
|
+
optionalTags: ClassVar[dict[Tags, type | UnionType]] = {
|
21
21
|
Tags.BPOINTS: str,
|
22
22
|
}
|
23
|
-
|
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.
|
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.
|
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
|
-
|
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.
|
62
|
-
self.
|
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.
|
69
|
-
feedback=self.
|
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.
|
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.
|
3
|
+
Version: 0.7.3
|
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,24 @@ 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.3 (2025-10-12)
|
94
|
+
further bulletPoint improvements
|
95
|
+
|
96
|
+
No changes.
|
97
|
+
|
98
|
+
## 0.7.2 (2025-10-11)
|
99
|
+
small important bugfixes
|
100
|
+
|
101
|
+
### improvement (2 changes)
|
102
|
+
|
103
|
+
- [BulletPoints are decoded using regex to allow multi word names](https://gitlab.com/jbosse3/excel2moodle/-/commit/7e32e9817323054d84a0dfb9a0a241c702fd096d)
|
104
|
+
- [Restructured globals, renamed rawInput to rawData](https://gitlab.com/jbosse3/excel2moodle/-/commit/effd9c3cd196b36d49204fe715acc1ffb124549c)
|
105
|
+
|
106
|
+
### bugfix (2 changes)
|
107
|
+
|
108
|
+
- [Added the number do the mandatory tags because it is](https://gitlab.com/jbosse3/excel2moodle/-/commit/56e9b69d71504dffe7c235d69ff44dec6931db28)
|
109
|
+
- [fixed assigning the first result to all clozes](https://gitlab.com/jbosse3/excel2moodle/-/commit/09a281c253502adc23442892be03aac36e6ea720)
|
110
|
+
|
93
111
|
## 0.7.1 (2025-10-04)
|
94
112
|
feedbacking improved
|
95
113
|
|
@@ -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,24 @@ 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
|
+
(r"Nutzungsgrad \eta = 33 \%", "Nutzungsgrad", r"\eta", 33, r"\%"),
|
59
|
+
(r"Nutzungsgrad \eta = 33 %", "Nutzungsgrad", r"\eta", 33, r"\%"),
|
60
|
+
],
|
61
|
+
)
|
62
|
+
def test_paresBPointNames(bulletString, name, var, value, unit) -> None:
|
63
|
+
bList: BulletList = BulletList([bulletString], "0000")
|
64
|
+
assert bList.bullets.get(1).name == name
|
65
|
+
assert bList.bullets.get(1).value == value
|
66
|
+
assert bList.bullets.get(1).var == var
|
67
|
+
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
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|