excel2moodle 0.3.3__py3-none-any.whl → 0.3.4__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 +64 -55
- excel2moodle/__main__.py +5 -5
- excel2moodle/core/category.py +53 -33
- excel2moodle/core/dataStructure.py +82 -50
- excel2moodle/core/etHelpers.py +21 -16
- excel2moodle/core/exceptions.py +11 -4
- excel2moodle/core/globals.py +41 -28
- excel2moodle/core/numericMultiQ.py +18 -13
- excel2moodle/core/parser.py +171 -109
- excel2moodle/core/question.py +86 -57
- excel2moodle/core/questionValidator.py +34 -41
- excel2moodle/core/stringHelpers.py +35 -24
- excel2moodle/extra/__init__.py +1 -3
- excel2moodle/extra/equationVerification.py +40 -22
- excel2moodle/ui/appUi.py +91 -68
- excel2moodle/ui/dialogs.py +35 -18
- excel2moodle/ui/settings.py +44 -7
- {excel2moodle-0.3.3.dist-info → excel2moodle-0.3.4.dist-info}/METADATA +4 -3
- excel2moodle-0.3.4.dist-info/RECORD +32 -0
- {excel2moodle-0.3.3.dist-info → excel2moodle-0.3.4.dist-info}/WHEEL +1 -1
- excel2moodle-0.3.4.dist-info/entry_points.txt +2 -0
- excel2moodle-0.3.3.dist-info/RECORD +0 -31
- {excel2moodle-0.3.3.dist-info → excel2moodle-0.3.4.dist-info}/licenses/LICENSE +0 -0
- {excel2moodle-0.3.3.dist-info → excel2moodle-0.3.4.dist-info}/top_level.txt +0 -0
excel2moodle/__init__.py
CHANGED
@@ -1,14 +1,20 @@
|
|
1
1
|
"""This Python program helps to create Moodle questions in less time.
|
2
2
|
|
3
|
-
The aim is to put alle the information for the questions into a spreadsheet
|
4
|
-
|
3
|
+
The aim is to put alle the information for the questions into a spreadsheet
|
4
|
+
file, and then parse it, to generate Moodle compliant XML-Files.
|
5
|
+
Furthermore this program lets you create a single ``.xml``-File with a selection
|
6
|
+
of questions, that then can be imported to a Moodle-Test.
|
5
7
|
|
6
8
|
Concept
|
7
9
|
=========
|
8
|
-
The concept is, to store the different questions into categories of similar
|
9
|
-
|
10
|
-
|
11
|
-
|
10
|
+
The concept is, to store the different questions into categories of similar
|
11
|
+
types and difficulties of questions, for each of which, a separated sheet in
|
12
|
+
the Spreadsheet document should be created.
|
13
|
+
|
14
|
+
There Should be a sheet called *"Kategorien"*, where an overview over the
|
15
|
+
different categories is stored.
|
16
|
+
This sheet stores The names and descriptions, for all categories.
|
17
|
+
The name have to be the same as the actual sheet names with the questions.
|
12
18
|
Furthermore the points used for grading, are set in the "Kategorien" sheet
|
13
19
|
|
14
20
|
Functionality
|
@@ -17,26 +23,33 @@ Functionality
|
|
17
23
|
* Parse Numeric Questions, each into one XML file
|
18
24
|
* create single XML File from a selection of questions
|
19
25
|
"""
|
26
|
+
|
20
27
|
from importlib import metadata
|
21
28
|
from importlib.metadata import version
|
29
|
+
|
22
30
|
try:
|
23
31
|
__version__ = version("excel2moodle")
|
24
32
|
except Exception:
|
25
33
|
__version__ = "unknown"
|
26
34
|
|
27
35
|
|
28
|
-
if
|
36
|
+
if __package__ is not None:
|
29
37
|
meta = metadata.metadata(__package__)
|
30
|
-
e2mMetadata:dict = {
|
38
|
+
e2mMetadata: dict = {
|
31
39
|
"version": __version__,
|
32
|
-
"name": meta[
|
33
|
-
"description": meta[
|
34
|
-
"author": meta[
|
35
|
-
"license": meta[
|
40
|
+
"name": meta["name"],
|
41
|
+
"description": meta["summary"],
|
42
|
+
"author": meta["author"],
|
43
|
+
"license": meta["license-expression"],
|
36
44
|
"documentation": "https://jbosse3.gitlab.io/excel2moodle",
|
37
|
-
"homepage": meta[
|
45
|
+
"homepage": meta["project-url"].split()[1],
|
38
46
|
"issues": "https://gitlab.com/jbosse3/excel2moodle/issues",
|
39
|
-
}
|
47
|
+
}
|
48
|
+
|
49
|
+
import logging as logging
|
50
|
+
from logging import config as logConfig
|
51
|
+
|
52
|
+
from PySide6.QtCore import QObject, Signal
|
40
53
|
|
41
54
|
# from excel2moodle.core import klausurGenerator
|
42
55
|
# from excel2moodle.core import numericMultiQ
|
@@ -48,67 +61,63 @@ if not __package__ == None:
|
|
48
61
|
# from excel2moodle.ui import kGeneratorQt
|
49
62
|
from excel2moodle.ui import settings
|
50
63
|
|
51
|
-
from PySide6.QtCore import QObject, Signal
|
52
|
-
import logging as logging
|
53
|
-
from logging import config as logConfig
|
54
|
-
|
55
64
|
loggerConfig = {
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
'format': '%(asctime)s [%(levelname)s] %(name)s: %(message)s'
|
61
|
-
},
|
65
|
+
"version": 1,
|
66
|
+
"disable_existing_loggers": False,
|
67
|
+
"formatters": {
|
68
|
+
"standard": {"format": "%(asctime)s [%(levelname)s] %(name)s: %(message)s"},
|
62
69
|
},
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
70
|
+
"handlers": {
|
71
|
+
"default": {
|
72
|
+
"level": "DEBUG",
|
73
|
+
"formatter": "standard",
|
74
|
+
"class": "logging.StreamHandler",
|
75
|
+
"stream": "ext://sys.stdout", # Default is stderr
|
69
76
|
},
|
70
77
|
},
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
78
|
+
"loggers": {
|
79
|
+
"": { # root logger
|
80
|
+
"handlers": ["default"],
|
81
|
+
"level": "DEBUG",
|
82
|
+
"propagate": True,
|
76
83
|
},
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
84
|
+
"excel2moodle.questionParser": {
|
85
|
+
"handlers": ["default"],
|
86
|
+
"level": "DEBUG",
|
87
|
+
"propagate": True,
|
81
88
|
},
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
89
|
+
"__main__": { # if __name__ == '__main__'
|
90
|
+
"handlers": ["default"],
|
91
|
+
"level": "DEBUG",
|
92
|
+
"propagate": True,
|
86
93
|
},
|
87
|
-
}
|
94
|
+
},
|
88
95
|
}
|
89
96
|
|
97
|
+
|
90
98
|
class QSignaler(QObject):
|
91
99
|
signal = Signal(str)
|
92
100
|
|
101
|
+
|
93
102
|
class LogHandler(logging.Handler):
|
94
|
-
def __init__(self, *args, **kwargs)->None:
|
95
|
-
super().__init__(*args
|
103
|
+
def __init__(self, *args, **kwargs) -> None:
|
104
|
+
super().__init__(*args, **kwargs)
|
96
105
|
self.emitter = QSignaler()
|
97
|
-
|
106
|
+
# Define a formatter with log level and module
|
98
107
|
log_format = "[%(levelname)s] %(module)s: %(message)s"
|
99
108
|
self.formatter = logging.Formatter(log_format)
|
100
109
|
self.setFormatter(self.formatter)
|
101
110
|
self.logLevelColors = {
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
111
|
+
"DEBUG": "gray",
|
112
|
+
"INFO": "green",
|
113
|
+
"WARNING": "orange",
|
114
|
+
"ERROR": "red",
|
115
|
+
"CRITICAL": "pink",
|
107
116
|
}
|
108
117
|
|
109
|
-
def emit(self, record)->None:
|
118
|
+
def emit(self, record) -> None:
|
110
119
|
log_message = self.format(record)
|
111
|
-
color = self.logLevelColors.get(record.levelname,
|
120
|
+
color = self.logLevelColors.get(record.levelname, "black")
|
112
121
|
prettyMessage = f'<span style="color:{color};">{log_message}</span>'
|
113
122
|
self.emitter.signal.emit(prettyMessage)
|
114
123
|
return None
|
@@ -123,5 +132,5 @@ qSignalLogger = LogHandler()
|
|
123
132
|
logger.addHandler(qSignalLogger)
|
124
133
|
|
125
134
|
|
126
|
-
for k,v in e2mMetadata.items():
|
135
|
+
for k, v in e2mMetadata.items():
|
127
136
|
print(f"{k}: \t {v}\n")
|
excel2moodle/__main__.py
CHANGED
@@ -2,20 +2,20 @@
|
|
2
2
|
|
3
3
|
from PySide6 import QtWidgets, sys
|
4
4
|
|
5
|
-
from
|
5
|
+
from excel2moodle.core import dataStructure
|
6
6
|
from excel2moodle.ui import appUi as ui
|
7
7
|
from excel2moodle.ui.settings import Settings
|
8
8
|
|
9
9
|
|
10
|
-
def main()->None:
|
10
|
+
def main() -> None:
|
11
11
|
app = QtWidgets.QApplication(sys.argv)
|
12
12
|
settings = Settings()
|
13
|
-
database:dataStructure.QuestionDB = dataStructure.QuestionDB(settings)
|
13
|
+
database: dataStructure.QuestionDB = dataStructure.QuestionDB(settings)
|
14
14
|
window = ui.MainWindow(settings, database)
|
15
15
|
database.window = window
|
16
16
|
window.show()
|
17
17
|
sys.exit(app.exec())
|
18
18
|
|
19
|
-
if __name__ =="__main__":
|
20
|
-
main()
|
21
19
|
|
20
|
+
if __name__ == "__main__":
|
21
|
+
main()
|
excel2moodle/core/category.py
CHANGED
@@ -1,27 +1,38 @@
|
|
1
|
-
|
2
1
|
import logging
|
3
|
-
|
2
|
+
|
4
3
|
import lxml.etree as ET
|
4
|
+
import pandas as pd
|
5
5
|
|
6
|
-
from excel2moodle.core.
|
7
|
-
|
8
|
-
|
6
|
+
from excel2moodle.core.parser import (
|
7
|
+
MCQuestionParser,
|
8
|
+
NFMQuestionParser,
|
9
|
+
NFQuestionParser,
|
10
|
+
QNotParsedException,
|
11
|
+
)
|
9
12
|
from excel2moodle.core.question import Question
|
10
13
|
|
11
|
-
|
12
|
-
|
14
|
+
logger = logging.getLogger(__name__)
|
15
|
+
|
13
16
|
|
14
|
-
class Category
|
15
|
-
def __init__(
|
17
|
+
class Category:
|
18
|
+
def __init__(
|
19
|
+
self,
|
20
|
+
n: int,
|
21
|
+
name: str,
|
22
|
+
description: str,
|
23
|
+
dataframe: pd.DataFrame,
|
24
|
+
points: float = 0,
|
25
|
+
version: int = 0,
|
26
|
+
) -> None:
|
16
27
|
self.n = n
|
17
28
|
self.NAME = name
|
18
29
|
self.desc = str(description)
|
19
|
-
self.dataframe:pd.DataFrame = dataframe
|
30
|
+
self.dataframe: pd.DataFrame = dataframe
|
20
31
|
self.points = points
|
21
32
|
self.version = int(version)
|
22
|
-
self.questions:dict[int,Question] = {}
|
23
|
-
self.maxVariants:int|None = None
|
24
|
-
logger.info(f"initializing {self.NAME
|
33
|
+
self.questions: dict[int, Question] = {}
|
34
|
+
self.maxVariants: int | None = None
|
35
|
+
logger.info(f"initializing {self.NAME=}")
|
25
36
|
|
26
37
|
@property
|
27
38
|
def name(self):
|
@@ -39,41 +50,50 @@ class Category():
|
|
39
50
|
return self.NAME == other.NAME
|
40
51
|
return False
|
41
52
|
|
42
|
-
def parseQ(
|
53
|
+
def parseQ(
|
54
|
+
self,
|
55
|
+
q: Question,
|
56
|
+
questionData: dict | None = None,
|
57
|
+
xmlTree: ET._Element | None = None,
|
58
|
+
) -> bool:
|
43
59
|
if q.element is not None:
|
44
60
|
logger.info(f"Question {q.id} is already parsed")
|
45
61
|
return True
|
46
62
|
else:
|
47
63
|
if q.qtype == "NF":
|
48
|
-
parser = NFQuestionParser(
|
49
|
-
logger.debug(
|
64
|
+
parser = NFQuestionParser(q, questionData)
|
65
|
+
logger.debug("setup a new NF parser ")
|
50
66
|
elif q.qtype == "MC":
|
51
|
-
parser = MCQuestionParser(
|
52
|
-
logger.debug(
|
67
|
+
parser = MCQuestionParser(q, questionData)
|
68
|
+
logger.debug("setup a new MC parser ")
|
53
69
|
elif q.qtype == "NFM":
|
54
|
-
parser = NFMQuestionParser(
|
55
|
-
logger.debug(
|
56
|
-
else:
|
57
|
-
logger.error(
|
70
|
+
parser = NFMQuestionParser(q, questionData)
|
71
|
+
logger.debug("setup a new NFM parser ")
|
72
|
+
else:
|
73
|
+
logger.error("ERROR, couldn't setup Parser")
|
58
74
|
return False
|
59
75
|
try:
|
60
76
|
parser.parse(xmlTree=xmlTree)
|
61
77
|
return True
|
62
78
|
except QNotParsedException as e:
|
63
|
-
logger.critical(
|
79
|
+
logger.critical(
|
80
|
+
f"The Question {q.id} couldn't be parsed",
|
81
|
+
exc_info=e,
|
82
|
+
stack_info=True,
|
83
|
+
)
|
64
84
|
return False
|
65
85
|
finally:
|
66
86
|
del parser
|
67
87
|
|
68
|
-
def getCategoryHeader(self)->ET.Element:
|
69
|
-
"""vor den Fragen einer Kategorie wird ein
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
ET.SubElement(
|
74
|
-
ET.SubElement(
|
75
|
-
ET.SubElement(
|
88
|
+
def getCategoryHeader(self) -> ET.Element:
|
89
|
+
"""vor den Fragen einer Kategorie wird ein
|
90
|
+
<question type='category'> eingefügt mit Name und Beschreibung
|
91
|
+
"""
|
92
|
+
header = ET.Element("question", type="category")
|
93
|
+
cat = ET.SubElement(header, "category")
|
94
|
+
info = ET.SubElement(header, "info", format="html")
|
95
|
+
ET.SubElement(cat, "text").text = f"$module$/top/{self.NAME}"
|
96
|
+
ET.SubElement(info, "text").text = str(self.desc)
|
97
|
+
ET.SubElement(header, "idnumber").text = str(self.n)
|
76
98
|
ET.indent(header)
|
77
99
|
return header
|
78
|
-
|
79
|
-
|
@@ -1,87 +1,100 @@
|
|
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
|
+
import logging
|
6
7
|
from pathlib import Path
|
7
|
-
|
8
|
-
|
8
|
+
|
9
|
+
import lxml.etree as ET
|
10
|
+
import pandas as pd
|
11
|
+
from PySide6 import QtCore, QtWidgets
|
9
12
|
from PySide6.QtCore import Signal
|
10
13
|
from PySide6.QtWidgets import QMainWindow, QMessageBox, QTreeWidget
|
11
|
-
import pandas as pd
|
12
|
-
import lxml.etree as ET
|
13
|
-
import logging
|
14
14
|
|
15
15
|
from excel2moodle import QSignaler
|
16
16
|
from excel2moodle.core import stringHelpers
|
17
17
|
from excel2moodle.core.category import Category
|
18
|
-
from excel2moodle.
|
19
|
-
from excel2moodle.
|
18
|
+
from excel2moodle.core.exceptions import InvalidFieldException, QNotParsedException
|
19
|
+
from excel2moodle.core.question import Question
|
20
20
|
from excel2moodle.core.questionValidator import Validator
|
21
|
+
from excel2moodle.ui.dialogs import QuestionVariantDialog
|
21
22
|
from excel2moodle.ui.settings import Settings
|
22
|
-
from excel2moodle.
|
23
|
-
from excel2moodle.core.exceptions import InvalidFieldException, QNotParsedException
|
24
|
-
|
23
|
+
from excel2moodle.ui.treewidget import CategoryItem, QuestionItem
|
25
24
|
|
26
25
|
logger = logging.getLogger(__name__)
|
27
26
|
|
28
27
|
|
29
|
-
class QuestionDB
|
28
|
+
class QuestionDB:
|
30
29
|
"""oberste Klasse für den Test"""
|
30
|
+
|
31
31
|
dataChanged = QSignaler()
|
32
32
|
|
33
|
-
def __init__(self, settings:Settings):
|
33
|
+
def __init__(self, settings: Settings):
|
34
34
|
self.settings = settings
|
35
35
|
self.spreadSheetPath = Path()
|
36
36
|
self.mainPath = Path()
|
37
|
-
self.window:QMainWindow|None = None
|
37
|
+
self.window: QMainWindow | None = None
|
38
38
|
self.version = None
|
39
39
|
self.categoriesMetaData = pd.DataFrame()
|
40
|
-
self.categories:dict[str,Category]={}
|
40
|
+
self.categories: dict[str, Category] = {}
|
41
41
|
self.settings.shPathChanged.connect(self.onSheetPathChanged)
|
42
42
|
|
43
43
|
@QtCore.Slot(Path)
|
44
|
-
def onSheetPathChanged(self, sheet:Path)->None:
|
44
|
+
def onSheetPathChanged(self, sheet: Path) -> None:
|
45
45
|
logger.debug("Slot, new Spreadsheet triggered")
|
46
46
|
self.spreadSheetPath = sheet
|
47
|
-
|
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)
|
48
52
|
self.retrieveCategoriesData()
|
49
53
|
self.parseAll()
|
50
54
|
|
51
|
-
def retrieveCategoriesData(self)->None:
|
52
|
-
"""Scans through the sheet with the metadata for all
|
55
|
+
def retrieveCategoriesData(self) -> None:
|
56
|
+
"""Scans through the sheet with the metadata for all categories
|
53
57
|
|
54
|
-
The information that will be shown in the UI like description
|
55
|
-
|
58
|
+
The information that will be shown in the UI like description
|
59
|
+
and points is retrieved from one spreadsheet sheet.
|
60
|
+
This method gathers this information and stores it in the
|
61
|
+
``categoriesMetaData`` dataframe
|
56
62
|
"""
|
57
63
|
|
58
64
|
logger.info("Start Parsing the Excel Metadata Sheet\n")
|
59
|
-
with open(self.spreadSheetPath,
|
65
|
+
with open(self.spreadSheetPath, "rb") as f:
|
60
66
|
excelFile = pd.ExcelFile(f)
|
61
|
-
self.categoriesMetaData = pd.read_excel(
|
62
|
-
|
63
|
-
|
67
|
+
self.categoriesMetaData = pd.read_excel(
|
68
|
+
f,
|
69
|
+
sheet_name="Kategorien",
|
70
|
+
usecols=["Kategorie", "Beschreibung", "Punkte", "Version"],
|
71
|
+
index_col=0,
|
72
|
+
)
|
64
73
|
logger.info("Sucessfully read categoriesMetaData")
|
65
74
|
print(self.categoriesMetaData)
|
66
75
|
self.categories = {}
|
67
76
|
for sh in excelFile.sheet_names:
|
68
77
|
if sh.startswith("KAT"):
|
69
78
|
n = int(sh[4:])
|
70
|
-
katDf = pd.read_excel(
|
79
|
+
katDf = pd.read_excel(
|
80
|
+
f, sheet_name=str(sh), index_col=0, header=None
|
81
|
+
)
|
71
82
|
if not katDf.empty:
|
72
|
-
p = self.categoriesMetaData["Punkte"].iloc[n-1]
|
73
|
-
points =
|
74
|
-
v = self.categoriesMetaData["Version"].iloc[n-1]
|
75
|
-
version =
|
76
|
-
self.categories[sh] = Category(
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
83
|
+
p = self.categoriesMetaData["Punkte"].iloc[n - 1]
|
84
|
+
points = p if not pd.isna(p) else 1
|
85
|
+
v = self.categoriesMetaData["Version"].iloc[n - 1]
|
86
|
+
version = v if not pd.isna(v) else 0
|
87
|
+
self.categories[sh] = Category(
|
88
|
+
n,
|
89
|
+
sh,
|
90
|
+
self.categoriesMetaData["Beschreibung"].iloc[n - 1],
|
91
|
+
dataframe=katDf,
|
92
|
+
points=points,
|
93
|
+
version=version,
|
94
|
+
)
|
81
95
|
self.dataChanged.signal.emit("whoo")
|
82
96
|
return None
|
83
97
|
|
84
|
-
|
85
98
|
def parseAll(self):
|
86
99
|
self.mainTree = ET.Element("quiz")
|
87
100
|
for c in self.categories.values():
|
@@ -95,17 +108,25 @@ class QuestionDB():
|
|
95
108
|
try:
|
96
109
|
check = validator.validate()
|
97
110
|
except InvalidFieldException as e:
|
98
|
-
logger.error(
|
99
|
-
|
100
|
-
|
111
|
+
logger.error(
|
112
|
+
f"Question {c.id}{
|
113
|
+
q:02d} is invalid.",
|
114
|
+
exc_info=e,
|
115
|
+
)
|
116
|
+
if check:
|
117
|
+
c.questions[q] = validator.question
|
101
118
|
try:
|
102
119
|
c.parseQ(c.questions[q], validator.qdata)
|
103
120
|
except QNotParsedException as e:
|
104
|
-
logger.error(
|
121
|
+
logger.error(
|
122
|
+
f"Frage {
|
123
|
+
c.questions[q].id} konnte nicht erstellt werden",
|
124
|
+
exc_info=e,
|
125
|
+
)
|
105
126
|
|
106
|
-
def appendQuestions(self, questions:list[QuestionItem], file:Path|None=None):
|
127
|
+
def appendQuestions(self, questions: list[QuestionItem], file: Path | None = None):
|
107
128
|
tree = ET.Element("quiz")
|
108
|
-
catdict:dict[Category,list[Question]]={}
|
129
|
+
catdict: dict[Category, list[Question]] = {}
|
109
130
|
for q in questions:
|
110
131
|
logger.debug(f"got a question to append {q=}")
|
111
132
|
cat = q.parent().getCategory()
|
@@ -114,13 +135,24 @@ class QuestionDB():
|
|
114
135
|
print(f"Category is parent of Q {cat=}")
|
115
136
|
catdict[cat].append(q.getQuestion())
|
116
137
|
for cat, qlist in catdict.items():
|
117
|
-
print(f"{cat
|
118
|
-
self.appendQElements(
|
138
|
+
print(f"{cat=}, mit fragen {qlist=}")
|
139
|
+
self.appendQElements(
|
140
|
+
cat,
|
141
|
+
qlist,
|
142
|
+
tree=tree,
|
143
|
+
includeHeader=self.settings.value("testGen/includeCats"),
|
144
|
+
)
|
119
145
|
stringHelpers.printDom(tree, file=file)
|
120
146
|
|
121
|
-
def appendQElements(
|
147
|
+
def appendQElements(
|
148
|
+
self,
|
149
|
+
cat: Category,
|
150
|
+
qList: list[Question],
|
151
|
+
tree: ET.Element,
|
152
|
+
includeHeader: bool = True,
|
153
|
+
) -> None:
|
122
154
|
if includeHeader:
|
123
|
-
tree.append(
|
155
|
+
tree.append(cat.getCategoryHeader())
|
124
156
|
logger.debug(f"Appended a new category item {cat=}")
|
125
157
|
sameVariant = False
|
126
158
|
variant = 1
|
@@ -134,11 +166,11 @@ class QuestionDB():
|
|
134
166
|
sameVariant = dialog.categoryWide
|
135
167
|
logger.debug(f"Die Fragen-Variante {variant} wurde gewählt")
|
136
168
|
q.assemble(variant)
|
137
|
-
else:
|
169
|
+
else:
|
170
|
+
print("skipping this question")
|
138
171
|
else:
|
139
172
|
q.assemble()
|
140
173
|
tree.append(q.element)
|
141
|
-
else:
|
174
|
+
else:
|
175
|
+
logger.warning(f"Frage {q} wurde nicht erstellt")
|
142
176
|
return None
|
143
|
-
|
144
|
-
|
excel2moodle/core/etHelpers.py
CHANGED
@@ -4,29 +4,31 @@ This module host different functions. All of them will return an ``lxml.etree.El
|
|
4
4
|
"""
|
5
5
|
|
6
6
|
import lxml.etree as ET
|
7
|
-
|
8
|
-
import pandas as pd
|
9
|
-
from excel2moodle.core.globals import feedbackStr, TextElements, feedBElements
|
7
|
+
|
10
8
|
import excel2moodle.core.etHelpers as eth
|
9
|
+
from excel2moodle.core.globals import TextElements, feedbackStr, feedBElements
|
10
|
+
|
11
|
+
from .globals import DFIndex, XMLTags
|
12
|
+
|
11
13
|
|
12
|
-
def getElement(
|
14
|
+
def getElement(eleName: str, text: str, **attribs) -> ET.Element:
|
13
15
|
"""Creates an XML-Element with text
|
14
|
-
|
16
|
+
|
15
17
|
If ``type(text)``is a ``QuestionFields``, the specific field is directly read.
|
16
18
|
Otherwise it will include whatever is ``text`` as a string
|
17
19
|
:param **kwargs: are treated as attributes for the Element
|
18
|
-
raises:
|
20
|
+
raises:
|
19
21
|
NanException if the spreadsheet cell of text:QuestionFields is ``nan``
|
20
22
|
"""
|
21
23
|
|
22
24
|
toEle = ET.Element(eleName)
|
23
25
|
toEle.text = str(text)
|
24
26
|
for k, v in attribs.items():
|
25
|
-
toEle.set(k,v)
|
27
|
+
toEle.set(k, v)
|
26
28
|
return toEle
|
27
29
|
|
28
30
|
|
29
|
-
def getTextElement(
|
31
|
+
def getTextElement(eleName: str, text: str | DFIndex, **attribs) -> ET.Element:
|
30
32
|
"""Creates two nested elements: ``eleName`` with child ``text`` which holds the text"""
|
31
33
|
|
32
34
|
toEle = ET.Element(eleName, **attribs)
|
@@ -34,28 +36,31 @@ def getTextElement( eleName:str, text:str|DFIndex, **attribs)->ET.Element:
|
|
34
36
|
toEle.append(child)
|
35
37
|
return toEle
|
36
38
|
|
37
|
-
|
39
|
+
|
40
|
+
def getCdatTxtElement(subEle: ET._Element | list[ET._Element]) -> ET.Element:
|
38
41
|
"""Puts all ``subEle`` as ``str`` into a ``<text><![CDATA[...subEle...]]</text>`` element"""
|
39
42
|
|
40
43
|
textEle = ET.Element(XMLTags.TEXT)
|
41
44
|
if isinstance(subEle, list):
|
42
|
-
elementString
|
45
|
+
elementString: list = []
|
43
46
|
for i in subEle:
|
44
47
|
elementString.append(ET.tostring(i, encoding="unicode", pretty_print=True))
|
45
48
|
textEle.text = ET.CDATA("".join(elementString))
|
46
49
|
return textEle
|
47
50
|
else:
|
48
|
-
textEle.text = ET.CDATA(
|
49
|
-
|
51
|
+
textEle.text = ET.CDATA(
|
52
|
+
ET.tostring(subEle, encoding="unicode", pretty_print=True)
|
53
|
+
)
|
54
|
+
return textEle
|
50
55
|
|
51
56
|
|
52
|
-
def getFeedBEle(
|
53
|
-
|
54
|
-
|
57
|
+
def getFeedBEle(
|
58
|
+
feedback: XMLTags, text: str | None = None, style: TextElements | None = None
|
59
|
+
) -> ET.Element:
|
55
60
|
"""Gets ET Elements with the feedback for the question."""
|
56
61
|
if style is None:
|
57
62
|
span = feedBElements[feedback]
|
58
|
-
else:
|
63
|
+
else:
|
59
64
|
span = style.create()
|
60
65
|
if text is None:
|
61
66
|
text = feedbackStr[feedback]
|
excel2moodle/core/exceptions.py
CHANGED
@@ -1,20 +1,27 @@
|
|
1
|
-
|
2
1
|
from excel2moodle.core.globals import DFIndex
|
3
2
|
|
4
3
|
|
5
4
|
class QNotParsedException(Exception):
|
6
|
-
def __init__(self, message:str, qID:str|int, *args, **kwargs)->None:
|
5
|
+
def __init__(self, message: str, qID: str | int, *args, **kwargs) -> None:
|
7
6
|
super().__init__(message, *args, **kwargs)
|
8
7
|
self.qID = qID
|
9
8
|
|
9
|
+
|
10
10
|
class NanException(QNotParsedException):
|
11
11
|
def __init__(self, message, qID, field, *args, **kwargs):
|
12
12
|
super().__init__(message, qID, *args, **kwargs)
|
13
13
|
self.field = field
|
14
14
|
|
15
|
+
|
15
16
|
class InvalidFieldException(Exception):
|
16
|
-
def __init__(
|
17
|
+
def __init__(
|
18
|
+
self,
|
19
|
+
message: str,
|
20
|
+
qID: str,
|
21
|
+
field: DFIndex | list[DFIndex],
|
22
|
+
*args: object,
|
23
|
+
**kwargs,
|
24
|
+
) -> None:
|
17
25
|
super().__init__(message, *args, **kwargs)
|
18
26
|
self.field = field
|
19
27
|
self.index = qID
|
20
|
-
|