excel2moodle 0.3.4__py3-none-any.whl → 0.3.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- excel2moodle/__init__.py +16 -88
- excel2moodle/__main__.py +9 -1
- excel2moodle/core/__init__.py +1 -3
- excel2moodle/core/category.py +32 -32
- excel2moodle/core/dataStructure.py +35 -51
- excel2moodle/core/etHelpers.py +12 -17
- excel2moodle/core/exceptions.py +1 -1
- excel2moodle/core/globals.py +2 -1
- excel2moodle/core/numericMultiQ.py +11 -12
- excel2moodle/core/parser.py +121 -102
- excel2moodle/core/question.py +32 -30
- excel2moodle/core/questionValidator.py +28 -19
- excel2moodle/core/questionWriter.py +232 -139
- excel2moodle/core/stringHelpers.py +16 -23
- excel2moodle/extra/equationVerification.py +13 -27
- excel2moodle/logger.py +101 -0
- excel2moodle/ui/appUi.py +97 -102
- excel2moodle/ui/dialogs.py +40 -4
- excel2moodle/ui/settings.py +105 -54
- excel2moodle/ui/treewidget.py +13 -10
- excel2moodle/ui/windowMain.py +18 -57
- {excel2moodle-0.3.4.dist-info → excel2moodle-0.3.6.dist-info}/METADATA +1 -1
- excel2moodle-0.3.6.dist-info/RECORD +33 -0
- {excel2moodle-0.3.4.dist-info → excel2moodle-0.3.6.dist-info}/WHEEL +1 -1
- excel2moodle-0.3.4.dist-info/RECORD +0 -32
- {excel2moodle-0.3.4.dist-info → excel2moodle-0.3.6.dist-info}/entry_points.txt +0 -0
- {excel2moodle-0.3.4.dist-info → excel2moodle-0.3.6.dist-info}/licenses/LICENSE +0 -0
- {excel2moodle-0.3.4.dist-info → excel2moodle-0.3.6.dist-info}/top_level.txt +0 -0
excel2moodle/__init__.py
CHANGED
@@ -24,18 +24,24 @@ Functionality
|
|
24
24
|
* create single XML File from a selection of questions
|
25
25
|
"""
|
26
26
|
|
27
|
+
import logging
|
28
|
+
import logging.config as logConfig
|
27
29
|
from importlib import metadata
|
28
30
|
from importlib.metadata import version
|
31
|
+
from pathlib import Path
|
32
|
+
|
33
|
+
from excel2moodle.logger import LogWindowHandler, loggerConfig
|
34
|
+
from excel2moodle.ui.settings import Settings, SettingsKey
|
29
35
|
|
30
36
|
try:
|
31
37
|
__version__ = version("excel2moodle")
|
32
38
|
except Exception:
|
33
39
|
__version__ = "unknown"
|
34
40
|
|
35
|
-
|
41
|
+
e2mMetadata: dict = {}
|
36
42
|
if __package__ is not None:
|
37
43
|
meta = metadata.metadata(__package__)
|
38
|
-
e2mMetadata
|
44
|
+
e2mMetadata = {
|
39
45
|
"version": __version__,
|
40
46
|
"name": meta["name"],
|
41
47
|
"description": meta["summary"],
|
@@ -46,91 +52,13 @@ if __package__ is not None:
|
|
46
52
|
"issues": "https://gitlab.com/jbosse3/excel2moodle/issues",
|
47
53
|
}
|
48
54
|
|
49
|
-
import logging as logging
|
50
|
-
from logging import config as logConfig
|
51
|
-
|
52
|
-
from PySide6.QtCore import QObject, Signal
|
53
|
-
|
54
|
-
# from excel2moodle.core import klausurGenerator
|
55
|
-
# from excel2moodle.core import numericMultiQ
|
56
|
-
# from excel2moodle.core import questionWriter
|
57
|
-
# from excel2moodle.core import questionParser
|
58
|
-
# from excel2moodle.core import stringHelpers
|
59
|
-
# from excel2moodle.core import globals
|
60
|
-
#
|
61
|
-
# from excel2moodle.ui import kGeneratorQt
|
62
|
-
from excel2moodle.ui import settings
|
63
|
-
|
64
|
-
loggerConfig = {
|
65
|
-
"version": 1,
|
66
|
-
"disable_existing_loggers": False,
|
67
|
-
"formatters": {
|
68
|
-
"standard": {"format": "%(asctime)s [%(levelname)s] %(name)s: %(message)s"},
|
69
|
-
},
|
70
|
-
"handlers": {
|
71
|
-
"default": {
|
72
|
-
"level": "DEBUG",
|
73
|
-
"formatter": "standard",
|
74
|
-
"class": "logging.StreamHandler",
|
75
|
-
"stream": "ext://sys.stdout", # Default is stderr
|
76
|
-
},
|
77
|
-
},
|
78
|
-
"loggers": {
|
79
|
-
"": { # root logger
|
80
|
-
"handlers": ["default"],
|
81
|
-
"level": "DEBUG",
|
82
|
-
"propagate": True,
|
83
|
-
},
|
84
|
-
"excel2moodle.questionParser": {
|
85
|
-
"handlers": ["default"],
|
86
|
-
"level": "DEBUG",
|
87
|
-
"propagate": True,
|
88
|
-
},
|
89
|
-
"__main__": { # if __name__ == '__main__'
|
90
|
-
"handlers": ["default"],
|
91
|
-
"level": "DEBUG",
|
92
|
-
"propagate": True,
|
93
|
-
},
|
94
|
-
},
|
95
|
-
}
|
96
|
-
|
97
|
-
|
98
|
-
class QSignaler(QObject):
|
99
|
-
signal = Signal(str)
|
100
|
-
|
101
|
-
|
102
|
-
class LogHandler(logging.Handler):
|
103
|
-
def __init__(self, *args, **kwargs) -> None:
|
104
|
-
super().__init__(*args, **kwargs)
|
105
|
-
self.emitter = QSignaler()
|
106
|
-
# Define a formatter with log level and module
|
107
|
-
log_format = "[%(levelname)s] %(module)s: %(message)s"
|
108
|
-
self.formatter = logging.Formatter(log_format)
|
109
|
-
self.setFormatter(self.formatter)
|
110
|
-
self.logLevelColors = {
|
111
|
-
"DEBUG": "gray",
|
112
|
-
"INFO": "green",
|
113
|
-
"WARNING": "orange",
|
114
|
-
"ERROR": "red",
|
115
|
-
"CRITICAL": "pink",
|
116
|
-
}
|
117
|
-
|
118
|
-
def emit(self, record) -> None:
|
119
|
-
log_message = self.format(record)
|
120
|
-
color = self.logLevelColors.get(record.levelname, "black")
|
121
|
-
prettyMessage = f'<span style="color:{color};">{log_message}</span>'
|
122
|
-
self.emitter.signal.emit(prettyMessage)
|
123
|
-
return None
|
124
|
-
|
125
|
-
|
126
|
-
settings = settings.Settings()
|
127
|
-
|
128
|
-
logger = logging.getLogger(__name__)
|
129
|
-
logging.config.dictConfig(config=loggerConfig)
|
130
|
-
|
131
|
-
qSignalLogger = LogHandler()
|
132
|
-
logger.addHandler(qSignalLogger)
|
133
55
|
|
56
|
+
settings = Settings()
|
57
|
+
logfile = Path(settings.get(SettingsKey.LOGFILE)).resolve()
|
58
|
+
e2mMetadata["logfile"] = logfile
|
59
|
+
if logfile.exists():
|
60
|
+
logfile.replace(f"{logfile}.old")
|
134
61
|
|
135
|
-
|
136
|
-
|
62
|
+
mainLogger = logging.getLogger(__name__)
|
63
|
+
logConfig.dictConfig(config=loggerConfig)
|
64
|
+
qSignalLogger = LogWindowHandler()
|
excel2moodle/__main__.py
CHANGED
@@ -1,19 +1,27 @@
|
|
1
|
-
"""Main Function to make the Package executable"""
|
1
|
+
"""Main Function to make the Package executable."""
|
2
|
+
|
3
|
+
import signal
|
2
4
|
|
3
5
|
from PySide6 import QtWidgets, sys
|
4
6
|
|
7
|
+
from excel2moodle import e2mMetadata, mainLogger, qSignalLogger
|
5
8
|
from excel2moodle.core import dataStructure
|
6
9
|
from excel2moodle.ui import appUi as ui
|
7
10
|
from excel2moodle.ui.settings import Settings
|
8
11
|
|
9
12
|
|
10
13
|
def main() -> None:
|
14
|
+
mainLogger.addHandler(qSignalLogger)
|
15
|
+
signal.signal(signal.SIGINT, signal.SIG_DFL)
|
11
16
|
app = QtWidgets.QApplication(sys.argv)
|
12
17
|
settings = Settings()
|
13
18
|
database: dataStructure.QuestionDB = dataStructure.QuestionDB(settings)
|
14
19
|
window = ui.MainWindow(settings, database)
|
15
20
|
database.window = window
|
16
21
|
window.show()
|
22
|
+
for k, v in e2mMetadata.items():
|
23
|
+
msg = f"{k:^14s}: {v}"
|
24
|
+
mainLogger.info(msg)
|
17
25
|
sys.exit(app.exec())
|
18
26
|
|
19
27
|
|
excel2moodle/core/__init__.py
CHANGED
excel2moodle/core/category.py
CHANGED
@@ -10,8 +10,9 @@ from excel2moodle.core.parser import (
|
|
10
10
|
QNotParsedException,
|
11
11
|
)
|
12
12
|
from excel2moodle.core.question import Question
|
13
|
+
from excel2moodle.logger import LogAdapterQuestionID
|
13
14
|
|
14
|
-
|
15
|
+
loggerObj = logging.getLogger(__name__)
|
15
16
|
|
16
17
|
|
17
18
|
class Category:
|
@@ -32,14 +33,14 @@ class Category:
|
|
32
33
|
self.version = int(version)
|
33
34
|
self.questions: dict[int, Question] = {}
|
34
35
|
self.maxVariants: int | None = None
|
35
|
-
|
36
|
+
loggerObj.info("initializing Category %s", self.NAME)
|
36
37
|
|
37
38
|
@property
|
38
|
-
def name(self):
|
39
|
+
def name(self) -> str:
|
39
40
|
return self.NAME
|
40
41
|
|
41
42
|
@property
|
42
|
-
def id(self):
|
43
|
+
def id(self) -> str:
|
43
44
|
return f"{self.version}{self.n:02d}"
|
44
45
|
|
45
46
|
def __hash__(self) -> int:
|
@@ -56,39 +57,38 @@ class Category:
|
|
56
57
|
questionData: dict | None = None,
|
57
58
|
xmlTree: ET._Element | None = None,
|
58
59
|
) -> bool:
|
60
|
+
"""Parse the given question."""
|
61
|
+
logger = LogAdapterQuestionID(loggerObj, {"qID": q.id})
|
59
62
|
if q.element is not None:
|
60
|
-
logger.info(
|
63
|
+
logger.info("Question already parsed")
|
61
64
|
return True
|
65
|
+
if q.qtype == "NF":
|
66
|
+
parser = NFQuestionParser(q, questionData)
|
67
|
+
logger.debug("setup a new NF parser ")
|
68
|
+
elif q.qtype == "MC":
|
69
|
+
parser = MCQuestionParser(q, questionData)
|
70
|
+
logger.debug("setup a new MC parser ")
|
71
|
+
elif q.qtype == "NFM":
|
72
|
+
parser = NFMQuestionParser(q, questionData)
|
73
|
+
logger.debug("setup a new NFM parser ")
|
62
74
|
else:
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
return True
|
78
|
-
except QNotParsedException as e:
|
79
|
-
logger.critical(
|
80
|
-
f"The Question {q.id} couldn't be parsed",
|
81
|
-
exc_info=e,
|
82
|
-
stack_info=True,
|
83
|
-
)
|
84
|
-
return False
|
85
|
-
finally:
|
86
|
-
del parser
|
75
|
+
logger.error("couldn't setup Parser")
|
76
|
+
return False
|
77
|
+
try:
|
78
|
+
parser.parse(xmlTree=xmlTree)
|
79
|
+
return True
|
80
|
+
except QNotParsedException as e:
|
81
|
+
logger.critical(
|
82
|
+
"Question couldn't be parsed",
|
83
|
+
exc_info=e,
|
84
|
+
stack_info=True,
|
85
|
+
)
|
86
|
+
return False
|
87
|
+
finally:
|
88
|
+
del parser
|
87
89
|
|
88
90
|
def getCategoryHeader(self) -> ET.Element:
|
89
|
-
"""
|
90
|
-
<question type='category'> eingefügt mit Name und Beschreibung
|
91
|
-
"""
|
91
|
+
"""Insert an <question type='category'> before all Questions of this Category."""
|
92
92
|
header = ET.Element("question", type="category")
|
93
93
|
cat = ET.SubElement(header, "category")
|
94
94
|
info = ET.SubElement(header, "info", format="html")
|
@@ -1,68 +1,53 @@
|
|
1
|
-
"""Main Module which does the heavy lifting
|
1
|
+
"""Main Module which does the heavy lifting.
|
2
2
|
|
3
3
|
At the heart is the class ``xmlTest``
|
4
4
|
"""
|
5
5
|
|
6
6
|
import logging
|
7
7
|
from pathlib import Path
|
8
|
+
from typing import TYPE_CHECKING
|
8
9
|
|
9
|
-
import lxml.etree as ET
|
10
|
+
import lxml.etree as ET # noqa: N812
|
10
11
|
import pandas as pd
|
11
|
-
from PySide6 import
|
12
|
-
from PySide6.QtCore import Signal
|
13
|
-
from PySide6.QtWidgets import QMainWindow, QMessageBox, QTreeWidget
|
12
|
+
from PySide6 import QtWidgets
|
14
13
|
|
15
|
-
from excel2moodle import QSignaler
|
16
14
|
from excel2moodle.core import stringHelpers
|
17
15
|
from excel2moodle.core.category import Category
|
18
16
|
from excel2moodle.core.exceptions import InvalidFieldException, QNotParsedException
|
19
17
|
from excel2moodle.core.question import Question
|
20
18
|
from excel2moodle.core.questionValidator import Validator
|
19
|
+
from excel2moodle.logger import QSignaler
|
21
20
|
from excel2moodle.ui.dialogs import QuestionVariantDialog
|
22
|
-
from excel2moodle.ui.settings import Settings
|
23
|
-
from excel2moodle.ui.treewidget import
|
21
|
+
from excel2moodle.ui.settings import Settings, SettingsKey
|
22
|
+
from excel2moodle.ui.treewidget import QuestionItem
|
23
|
+
|
24
|
+
if TYPE_CHECKING:
|
25
|
+
from PySide6.QtWidgets import QMainWindow
|
24
26
|
|
25
27
|
logger = logging.getLogger(__name__)
|
26
28
|
|
27
29
|
|
28
30
|
class QuestionDB:
|
29
|
-
"""oberste Klasse für den Test"""
|
31
|
+
"""oberste Klasse für den Test."""
|
30
32
|
|
31
33
|
dataChanged = QSignaler()
|
32
34
|
|
33
|
-
def __init__(self, settings: Settings):
|
35
|
+
def __init__(self, settings: Settings) -> None:
|
34
36
|
self.settings = settings
|
35
|
-
self.spreadSheetPath = Path()
|
36
|
-
self.mainPath = Path()
|
37
37
|
self.window: QMainWindow | None = None
|
38
38
|
self.version = None
|
39
39
|
self.categoriesMetaData = pd.DataFrame()
|
40
40
|
self.categories: dict[str, Category] = {}
|
41
|
-
self.settings.shPathChanged.connect(self.onSheetPathChanged)
|
42
|
-
|
43
|
-
@QtCore.Slot(Path)
|
44
|
-
def onSheetPathChanged(self, sheet: Path) -> None:
|
45
|
-
logger.debug("Slot, new Spreadsheet triggered")
|
46
|
-
self.spreadSheetPath = sheet
|
47
|
-
svgFolder = self.spreadSheetPath.parent / str(
|
48
|
-
self.settings.get("core/pictureSubFolder", default="Abbildungen_SVG")
|
49
|
-
)
|
50
|
-
svgFolder.resolve()
|
51
|
-
self.settings.set("core/pictureFolder", svgFolder)
|
52
|
-
self.retrieveCategoriesData()
|
53
|
-
self.parseAll()
|
54
41
|
|
55
|
-
def
|
56
|
-
"""
|
42
|
+
def readSpreadsheetData(self, sheet: Path) -> None:
|
43
|
+
"""Read the metadata and questions from the spreadsheet.
|
57
44
|
|
58
|
-
The information that will be shown in the UI like description
|
59
|
-
and points is retrieved from one spreadsheet sheet.
|
60
45
|
This method gathers this information and stores it in the
|
61
46
|
``categoriesMetaData`` dataframe
|
47
|
+
It also reads the question data and stores it in ``self.categories = {}``
|
62
48
|
"""
|
63
|
-
|
64
49
|
logger.info("Start Parsing the Excel Metadata Sheet\n")
|
65
|
-
with open(
|
50
|
+
with Path(sheet).open("rb") as f:
|
66
51
|
excelFile = pd.ExcelFile(f)
|
67
52
|
self.categoriesMetaData = pd.read_excel(
|
68
53
|
f,
|
@@ -71,13 +56,15 @@ class QuestionDB:
|
|
71
56
|
index_col=0,
|
72
57
|
)
|
73
58
|
logger.info("Sucessfully read categoriesMetaData")
|
74
|
-
print(self.categoriesMetaData)
|
75
59
|
self.categories = {}
|
76
60
|
for sh in excelFile.sheet_names:
|
77
61
|
if sh.startswith("KAT"):
|
78
62
|
n = int(sh[4:])
|
79
63
|
katDf = pd.read_excel(
|
80
|
-
f,
|
64
|
+
f,
|
65
|
+
sheet_name=str(sh),
|
66
|
+
index_col=0,
|
67
|
+
header=None,
|
81
68
|
)
|
82
69
|
if not katDf.empty:
|
83
70
|
p = self.categoriesMetaData["Punkte"].iloc[n - 1]
|
@@ -92,10 +79,9 @@ class QuestionDB:
|
|
92
79
|
points=points,
|
93
80
|
version=version,
|
94
81
|
)
|
95
|
-
self.dataChanged.signal.emit("whoo")
|
96
|
-
return None
|
82
|
+
# self.dataChanged.signal.emit("whoo")
|
97
83
|
|
98
|
-
def parseAll(self):
|
84
|
+
def parseAll(self) -> None:
|
99
85
|
self.mainTree = ET.Element("quiz")
|
100
86
|
for c in self.categories.values():
|
101
87
|
validator = Validator(c)
|
@@ -108,9 +94,8 @@ class QuestionDB:
|
|
108
94
|
try:
|
109
95
|
check = validator.validate()
|
110
96
|
except InvalidFieldException as e:
|
111
|
-
logger.
|
112
|
-
f"Question {c.id}{
|
113
|
-
q:02d} is invalid.",
|
97
|
+
logger.exception(
|
98
|
+
f"Question {c.id}{q:02d} is invalid.",
|
114
99
|
exc_info=e,
|
115
100
|
)
|
116
101
|
if check:
|
@@ -118,13 +103,17 @@ class QuestionDB:
|
|
118
103
|
try:
|
119
104
|
c.parseQ(c.questions[q], validator.qdata)
|
120
105
|
except QNotParsedException as e:
|
121
|
-
logger.
|
106
|
+
logger.exception(
|
122
107
|
f"Frage {
|
123
|
-
c.questions[q].id
|
108
|
+
c.questions[q].id
|
109
|
+
} konnte nicht erstellt werden",
|
124
110
|
exc_info=e,
|
125
111
|
)
|
126
112
|
|
127
|
-
def appendQuestions(
|
113
|
+
def appendQuestions(
|
114
|
+
self, questions: list[QuestionItem], file: Path | None = None
|
115
|
+
) -> None:
|
116
|
+
"""Append selected question Elements to the tree."""
|
128
117
|
tree = ET.Element("quiz")
|
129
118
|
catdict: dict[Category, list[Question]] = {}
|
130
119
|
for q in questions:
|
@@ -132,15 +121,13 @@ class QuestionDB:
|
|
132
121
|
cat = q.parent().getCategory()
|
133
122
|
if cat not in catdict:
|
134
123
|
catdict[cat] = []
|
135
|
-
print(f"Category is parent of Q {cat=}")
|
136
124
|
catdict[cat].append(q.getQuestion())
|
137
125
|
for cat, qlist in catdict.items():
|
138
|
-
print(f"{cat=}, mit fragen {qlist=}")
|
139
126
|
self.appendQElements(
|
140
127
|
cat,
|
141
128
|
qlist,
|
142
129
|
tree=tree,
|
143
|
-
includeHeader=self.settings.
|
130
|
+
includeHeader=self.settings.get(SettingsKey.INCLUDEINCATS),
|
144
131
|
)
|
145
132
|
stringHelpers.printDom(tree, file=file)
|
146
133
|
|
@@ -154,23 +141,20 @@ class QuestionDB:
|
|
154
141
|
if includeHeader:
|
155
142
|
tree.append(cat.getCategoryHeader())
|
156
143
|
logger.debug(f"Appended a new category item {cat=}")
|
157
|
-
|
158
|
-
variant = 1
|
144
|
+
variant: int = self.settings.get(SettingsKey.QUESTIONVARIANT)
|
159
145
|
for q in qList:
|
160
146
|
if cat.parseQ(q):
|
161
147
|
if q.variants is not None:
|
162
|
-
if
|
148
|
+
if variant == 0 or variant > q.variants:
|
163
149
|
dialog = QuestionVariantDialog(self.window, q)
|
164
150
|
if dialog.exec() == QtWidgets.QDialog.Accepted:
|
165
151
|
variant = dialog.variant
|
166
|
-
sameVariant = dialog.categoryWide
|
167
152
|
logger.debug(f"Die Fragen-Variante {variant} wurde gewählt")
|
168
153
|
q.assemble(variant)
|
169
154
|
else:
|
170
|
-
|
155
|
+
pass
|
171
156
|
else:
|
172
157
|
q.assemble()
|
173
158
|
tree.append(q.element)
|
174
159
|
else:
|
175
160
|
logger.warning(f"Frage {q} wurde nicht erstellt")
|
176
|
-
return None
|
excel2moodle/core/etHelpers.py
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
"""Helper Module which aids in creating XML-Elements for the Questions
|
1
|
+
"""Helper Module which aids in creating XML-Elements for the Questions.
|
2
2
|
|
3
3
|
This module host different functions. All of them will return an ``lxml.etree.Element``
|
4
4
|
"""
|
@@ -12,7 +12,7 @@ from .globals import DFIndex, XMLTags
|
|
12
12
|
|
13
13
|
|
14
14
|
def getElement(eleName: str, text: str, **attribs) -> ET.Element:
|
15
|
-
"""Creates an XML-Element with text
|
15
|
+
"""Creates an XML-Element with text.
|
16
16
|
|
17
17
|
If ``type(text)``is a ``QuestionFields``, the specific field is directly read.
|
18
18
|
Otherwise it will include whatever is ``text`` as a string
|
@@ -20,7 +20,6 @@ def getElement(eleName: str, text: str, **attribs) -> ET.Element:
|
|
20
20
|
raises:
|
21
21
|
NanException if the spreadsheet cell of text:QuestionFields is ``nan``
|
22
22
|
"""
|
23
|
-
|
24
23
|
toEle = ET.Element(eleName)
|
25
24
|
toEle.text = str(text)
|
26
25
|
for k, v in attribs.items():
|
@@ -29,8 +28,7 @@ def getElement(eleName: str, text: str, **attribs) -> ET.Element:
|
|
29
28
|
|
30
29
|
|
31
30
|
def getTextElement(eleName: str, text: str | DFIndex, **attribs) -> ET.Element:
|
32
|
-
"""Creates two nested elements: ``eleName`` with child ``text`` which holds the text"""
|
33
|
-
|
31
|
+
"""Creates two nested elements: ``eleName`` with child ``text`` which holds the text."""
|
34
32
|
toEle = ET.Element(eleName, **attribs)
|
35
33
|
child = getElement("text", text)
|
36
34
|
toEle.append(child)
|
@@ -38,8 +36,7 @@ def getTextElement(eleName: str, text: str | DFIndex, **attribs) -> ET.Element:
|
|
38
36
|
|
39
37
|
|
40
38
|
def getCdatTxtElement(subEle: ET._Element | list[ET._Element]) -> ET.Element:
|
41
|
-
"""Puts all ``subEle`` as ``str`` into a ``<text><![CDATA[...subEle...]]</text>`` element"""
|
42
|
-
|
39
|
+
"""Puts all ``subEle`` as ``str`` into a ``<text><![CDATA[...subEle...]]</text>`` element."""
|
43
40
|
textEle = ET.Element(XMLTags.TEXT)
|
44
41
|
if isinstance(subEle, list):
|
45
42
|
elementString: list = []
|
@@ -47,21 +44,19 @@ def getCdatTxtElement(subEle: ET._Element | list[ET._Element]) -> ET.Element:
|
|
47
44
|
elementString.append(ET.tostring(i, encoding="unicode", pretty_print=True))
|
48
45
|
textEle.text = ET.CDATA("".join(elementString))
|
49
46
|
return textEle
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
return textEle
|
47
|
+
textEle.text = ET.CDATA(
|
48
|
+
ET.tostring(subEle, encoding="unicode", pretty_print=True),
|
49
|
+
)
|
50
|
+
return textEle
|
55
51
|
|
56
52
|
|
57
53
|
def getFeedBEle(
|
58
|
-
feedback: XMLTags,
|
54
|
+
feedback: XMLTags,
|
55
|
+
text: str | None = None,
|
56
|
+
style: TextElements | None = None,
|
59
57
|
) -> ET.Element:
|
60
58
|
"""Gets ET Elements with the feedback for the question."""
|
61
|
-
if style is None
|
62
|
-
span = feedBElements[feedback]
|
63
|
-
else:
|
64
|
-
span = style.create()
|
59
|
+
span = feedBElements[feedback] if style is None else style.create()
|
65
60
|
if text is None:
|
66
61
|
text = feedbackStr[feedback]
|
67
62
|
ele = ET.Element(feedback, format="html")
|
excel2moodle/core/exceptions.py
CHANGED
@@ -8,7 +8,7 @@ class QNotParsedException(Exception):
|
|
8
8
|
|
9
9
|
|
10
10
|
class NanException(QNotParsedException):
|
11
|
-
def __init__(self, message, qID, field, *args, **kwargs):
|
11
|
+
def __init__(self, message, qID, field, *args, **kwargs) -> None:
|
12
12
|
super().__init__(message, qID, *args, **kwargs)
|
13
13
|
self.field = field
|
14
14
|
|
excel2moodle/core/globals.py
CHANGED
@@ -10,7 +10,7 @@ questionTypes = {
|
|
10
10
|
|
11
11
|
|
12
12
|
class DFIndex(StrEnum):
|
13
|
-
"""
|
13
|
+
"""The identifier string for for the spreadsheet and the string for the xml-tag.
|
14
14
|
|
15
15
|
Each enum corresponds to a list of two values.
|
16
16
|
The first Value is the index in the spreadsheet, the second is the name of the xml-tag
|
@@ -26,6 +26,7 @@ class DFIndex(StrEnum):
|
|
26
26
|
PICTURE = "picture"
|
27
27
|
NUMBER = "number"
|
28
28
|
ANSTYPE = "answerType"
|
29
|
+
TOLERANCE = "tolerance"
|
29
30
|
|
30
31
|
|
31
32
|
class TextElements(Enum):
|
@@ -1,11 +1,11 @@
|
|
1
|
-
"""Numeric Multi Questions Module to calculate results from a formula
|
1
|
+
"""Numeric Multi Questions Module to calculate results from a formula.
|
2
2
|
|
3
3
|
This module calculates a series of results from al matrix of variables.
|
4
4
|
For each column in the matrix there will be one result.
|
5
5
|
As well it returns a bullet points string that shows the numerical values corresponding to the set of variables
|
6
6
|
"""
|
7
7
|
|
8
|
-
import re
|
8
|
+
import re
|
9
9
|
|
10
10
|
import pandas as pd
|
11
11
|
from asteval import Interpreter
|
@@ -14,7 +14,7 @@ astEval = Interpreter()
|
|
14
14
|
|
15
15
|
|
16
16
|
def getVariablesDict(df: pd.DataFrame, keyList: list, index: int) -> dict:
|
17
|
-
"""Liest alle Variablen-Listen deren Name in ``keyList`` ist aus dem DataFrame im Column[index]"""
|
17
|
+
"""Liest alle Variablen-Listen deren Name in ``keyList`` ist aus dem DataFrame im Column[index]."""
|
18
18
|
dic = {}
|
19
19
|
for k in keyList:
|
20
20
|
val = df.loc[str(k)][index]
|
@@ -23,7 +23,6 @@ def getVariablesDict(df: pd.DataFrame, keyList: list, index: int) -> dict:
|
|
23
23
|
dic[str(k)] = li
|
24
24
|
else:
|
25
25
|
dic[str(k)] = [str(val)]
|
26
|
-
print(f"Folgende Variablen wurden gefunden:\n{dic}\n")
|
27
26
|
return dic
|
28
27
|
|
29
28
|
|
@@ -36,12 +35,11 @@ def setParameters(parameters: dict, index: int) -> None:
|
|
36
35
|
comma = re.compile(r",")
|
37
36
|
value = comma.sub(".", v[index])
|
38
37
|
astEval.symtable[k] = float(value)
|
39
|
-
return None
|
40
38
|
|
41
39
|
|
42
40
|
def insertVariablesToBPoints(varDict: dict, bulletPoints: str, index: int) -> str:
|
43
|
-
"""
|
44
|
-
|
41
|
+
"""Für jeden Eintrag im varDict, wird im bulletPoints String der
|
42
|
+
Substring "{key}" durch value[index] ersetzt.
|
45
43
|
"""
|
46
44
|
for k, v in varDict.items():
|
47
45
|
s = r"{" + str(k) + r"}"
|
@@ -51,9 +49,7 @@ def insertVariablesToBPoints(varDict: dict, bulletPoints: str, index: int) -> st
|
|
51
49
|
|
52
50
|
|
53
51
|
def getVarsList(bps: str) -> list:
|
54
|
-
"""
|
55
|
-
Durchsucht den bulletPoints String nach den Variablen, die als "{var}" gekennzeichnet sind
|
56
|
-
"""
|
52
|
+
"""Durchsucht den bulletPoints String nach den Variablen `{var}`."""
|
57
53
|
vars = re.findall(r"\{\w\}", str(bps))
|
58
54
|
variablen = []
|
59
55
|
for v in vars:
|
@@ -62,9 +58,12 @@ def getVarsList(bps: str) -> list:
|
|
62
58
|
|
63
59
|
|
64
60
|
def parseNumericMultiQuestion(
|
65
|
-
datFrame: pd.DataFrame,
|
61
|
+
datFrame: pd.DataFrame,
|
62
|
+
bulletPoints: str,
|
63
|
+
equation: str,
|
64
|
+
questionIndex: int,
|
66
65
|
) -> tuple[list[str], list[float]]:
|
67
|
-
"""Berechnet die Ergebnisse anhand der Variablen in *bulletPoints
|
66
|
+
"""Berechnet die Ergebnisse anhand der Variablen in *bulletPoints*.
|
68
67
|
|
69
68
|
Gibt eine Liste mit allen Ergebnissen zurück
|
70
69
|
und eine Liste mit den bulletPoints-Strings, die die Numerischen Variablen enthalten
|