excel2moodle 0.3.3__py3-none-any.whl → 0.3.5__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 +38 -96
- excel2moodle/__main__.py +5 -5
- excel2moodle/core/__init__.py +1 -3
- excel2moodle/core/category.py +67 -47
- excel2moodle/core/dataStructure.py +89 -73
- excel2moodle/core/etHelpers.py +26 -26
- excel2moodle/core/exceptions.py +12 -5
- excel2moodle/core/globals.py +43 -29
- excel2moodle/core/numericMultiQ.py +28 -24
- excel2moodle/core/parser.py +228 -147
- excel2moodle/core/question.py +100 -69
- excel2moodle/core/questionValidator.py +56 -54
- excel2moodle/core/questionWriter.py +232 -139
- excel2moodle/core/stringHelpers.py +38 -34
- excel2moodle/extra/__init__.py +1 -3
- excel2moodle/extra/equationVerification.py +37 -33
- excel2moodle/logger.py +102 -0
- excel2moodle/ui/appUi.py +133 -125
- excel2moodle/ui/dialogs.py +71 -18
- excel2moodle/ui/settings.py +108 -21
- excel2moodle/ui/treewidget.py +13 -10
- excel2moodle/ui/windowMain.py +18 -57
- {excel2moodle-0.3.3.dist-info → excel2moodle-0.3.5.dist-info}/METADATA +4 -3
- excel2moodle-0.3.5.dist-info/RECORD +33 -0
- {excel2moodle-0.3.3.dist-info → excel2moodle-0.3.5.dist-info}/WHEEL +1 -1
- excel2moodle-0.3.5.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.5.dist-info}/licenses/LICENSE +0 -0
- {excel2moodle-0.3.3.dist-info → excel2moodle-0.3.5.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,111 +23,47 @@ 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
|
+
|
27
|
+
import logging
|
28
|
+
import logging.config as logConfig
|
20
29
|
from importlib import metadata
|
21
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
|
35
|
+
|
22
36
|
try:
|
23
37
|
__version__ = version("excel2moodle")
|
24
38
|
except Exception:
|
25
39
|
__version__ = "unknown"
|
26
40
|
|
27
|
-
|
28
|
-
if
|
41
|
+
e2mMetadata: dict = {}
|
42
|
+
if __package__ is not None:
|
29
43
|
meta = metadata.metadata(__package__)
|
30
|
-
e2mMetadata
|
44
|
+
e2mMetadata = {
|
31
45
|
"version": __version__,
|
32
|
-
"name": meta[
|
33
|
-
"description": meta[
|
34
|
-
"author": meta[
|
35
|
-
"license": meta[
|
46
|
+
"name": meta["name"],
|
47
|
+
"description": meta["summary"],
|
48
|
+
"author": meta["author"],
|
49
|
+
"license": meta["license-expression"],
|
36
50
|
"documentation": "https://jbosse3.gitlab.io/excel2moodle",
|
37
|
-
"homepage": meta[
|
51
|
+
"homepage": meta["project-url"].split()[1],
|
38
52
|
"issues": "https://gitlab.com/jbosse3/excel2moodle/issues",
|
39
|
-
}
|
40
|
-
|
41
|
-
# from excel2moodle.core import klausurGenerator
|
42
|
-
# from excel2moodle.core import numericMultiQ
|
43
|
-
# from excel2moodle.core import questionWriter
|
44
|
-
# from excel2moodle.core import questionParser
|
45
|
-
# from excel2moodle.core import stringHelpers
|
46
|
-
# from excel2moodle.core import globals
|
47
|
-
#
|
48
|
-
# from excel2moodle.ui import kGeneratorQt
|
49
|
-
from excel2moodle.ui import settings
|
53
|
+
}
|
50
54
|
|
51
|
-
from PySide6.QtCore import QObject, Signal
|
52
|
-
import logging as logging
|
53
|
-
from logging import config as logConfig
|
54
55
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
'standard': {
|
60
|
-
'format': '%(asctime)s [%(levelname)s] %(name)s: %(message)s'
|
61
|
-
},
|
62
|
-
},
|
63
|
-
'handlers': {
|
64
|
-
'default': {
|
65
|
-
'level': 'DEBUG',
|
66
|
-
'formatter': 'standard',
|
67
|
-
'class': 'logging.StreamHandler',
|
68
|
-
'stream': 'ext://sys.stdout', # Default is stderr
|
69
|
-
},
|
70
|
-
},
|
71
|
-
'loggers': {
|
72
|
-
'': { # root logger
|
73
|
-
'handlers': ['default'],
|
74
|
-
'level': 'DEBUG',
|
75
|
-
'propagate': True
|
76
|
-
},
|
77
|
-
'excel2moodle.questionParser': {
|
78
|
-
'handlers': ['default'],
|
79
|
-
'level': 'DEBUG',
|
80
|
-
'propagate': True
|
81
|
-
},
|
82
|
-
'__main__': { # if __name__ == '__main__'
|
83
|
-
'handlers': ['default'],
|
84
|
-
'level': 'DEBUG',
|
85
|
-
'propagate': True
|
86
|
-
},
|
87
|
-
}
|
88
|
-
}
|
89
|
-
|
90
|
-
class QSignaler(QObject):
|
91
|
-
signal = Signal(str)
|
92
|
-
|
93
|
-
class LogHandler(logging.Handler):
|
94
|
-
def __init__(self, *args, **kwargs)->None:
|
95
|
-
super().__init__(*args,**kwargs)
|
96
|
-
self.emitter = QSignaler()
|
97
|
-
# Define a formatter with log level and module
|
98
|
-
log_format = "[%(levelname)s] %(module)s: %(message)s"
|
99
|
-
self.formatter = logging.Formatter(log_format)
|
100
|
-
self.setFormatter(self.formatter)
|
101
|
-
self.logLevelColors = {
|
102
|
-
'DEBUG': 'gray',
|
103
|
-
'INFO': 'green',
|
104
|
-
'WARNING': 'orange',
|
105
|
-
'ERROR': 'red',
|
106
|
-
'CRITICAL': 'pink'
|
107
|
-
}
|
108
|
-
|
109
|
-
def emit(self, record)->None:
|
110
|
-
log_message = self.format(record)
|
111
|
-
color = self.logLevelColors.get(record.levelname, 'black')
|
112
|
-
prettyMessage = f'<span style="color:{color};">{log_message}</span>'
|
113
|
-
self.emitter.signal.emit(prettyMessage)
|
114
|
-
return None
|
115
|
-
|
116
|
-
|
117
|
-
settings = settings.Settings()
|
56
|
+
settings = Settings()
|
57
|
+
logfile = Path(settings.get(SettingsKey.LOGFILE)).resolve()
|
58
|
+
e2mMetadata["logfile"] = logfile
|
59
|
+
logfile.replace(f"{logfile}.old")
|
118
60
|
|
119
61
|
logger = logging.getLogger(__name__)
|
120
|
-
|
121
|
-
|
122
|
-
qSignalLogger = LogHandler()
|
62
|
+
logConfig.dictConfig(config=loggerConfig)
|
63
|
+
qSignalLogger = LogWindowHandler()
|
123
64
|
logger.addHandler(qSignalLogger)
|
124
65
|
|
125
66
|
|
126
|
-
for k,v in e2mMetadata.items():
|
127
|
-
|
67
|
+
for k, v in e2mMetadata.items():
|
68
|
+
msg = f"{k:^14s}: {v}"
|
69
|
+
logger.info(msg)
|
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/__init__.py
CHANGED
excel2moodle/core/category.py
CHANGED
@@ -1,34 +1,46 @@
|
|
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
|
13
|
+
from excel2moodle.logger import LogAdapterQuestionID
|
10
14
|
|
11
|
-
|
12
|
-
from excel2moodle.core.parser import NFMQuestionParser, NFQuestionParser, MCQuestionParser, QNotParsedException
|
15
|
+
loggerObj = logging.getLogger(__name__)
|
13
16
|
|
14
|
-
|
15
|
-
|
17
|
+
|
18
|
+
class Category:
|
19
|
+
def __init__(
|
20
|
+
self,
|
21
|
+
n: int,
|
22
|
+
name: str,
|
23
|
+
description: str,
|
24
|
+
dataframe: pd.DataFrame,
|
25
|
+
points: float = 0,
|
26
|
+
version: int = 0,
|
27
|
+
) -> None:
|
16
28
|
self.n = n
|
17
29
|
self.NAME = name
|
18
30
|
self.desc = str(description)
|
19
|
-
self.dataframe:pd.DataFrame = dataframe
|
31
|
+
self.dataframe: pd.DataFrame = dataframe
|
20
32
|
self.points = points
|
21
33
|
self.version = int(version)
|
22
|
-
self.questions:dict[int,Question] = {}
|
23
|
-
self.maxVariants:int|None = None
|
24
|
-
|
34
|
+
self.questions: dict[int, Question] = {}
|
35
|
+
self.maxVariants: int | None = None
|
36
|
+
loggerObj.info("initializing Category %s", self.NAME)
|
25
37
|
|
26
38
|
@property
|
27
|
-
def name(self):
|
39
|
+
def name(self) -> str:
|
28
40
|
return self.NAME
|
29
41
|
|
30
42
|
@property
|
31
|
-
def id(self):
|
43
|
+
def id(self) -> str:
|
32
44
|
return f"{self.version}{self.n:02d}"
|
33
45
|
|
34
46
|
def __hash__(self) -> int:
|
@@ -39,41 +51,49 @@ class Category():
|
|
39
51
|
return self.NAME == other.NAME
|
40
52
|
return False
|
41
53
|
|
42
|
-
def parseQ(
|
54
|
+
def parseQ(
|
55
|
+
self,
|
56
|
+
q: Question,
|
57
|
+
questionData: dict | None = None,
|
58
|
+
xmlTree: ET._Element | None = None,
|
59
|
+
) -> bool:
|
60
|
+
"""Parse the given question."""
|
61
|
+
logger = LogAdapterQuestionID(loggerObj, {"qID": q.id})
|
43
62
|
if q.element is not None:
|
44
|
-
logger.info(
|
63
|
+
logger.info("Question already parsed")
|
45
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 ")
|
46
74
|
else:
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
return True
|
62
|
-
except QNotParsedException as e:
|
63
|
-
logger.critical(f"The Question {q.id} couldn't be parsed", exc_info=e, stack_info=True)
|
64
|
-
return False
|
65
|
-
finally:
|
66
|
-
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
|
67
89
|
|
68
|
-
def getCategoryHeader(self)->ET.Element:
|
69
|
-
"""
|
70
|
-
header = ET.Element(
|
71
|
-
cat = ET.SubElement(header,"category")
|
72
|
-
info = ET.SubElement(header,"info", format=
|
73
|
-
ET.SubElement(cat,"text").text = f"$module$/top/{self.NAME}"
|
74
|
-
ET.SubElement(info,"text").text = str(self.desc)
|
75
|
-
ET.SubElement(header,"idnumber").text = str(self.n)
|
90
|
+
def getCategoryHeader(self) -> ET.Element:
|
91
|
+
"""Insert an <question type='category'> before all Questions of this Category."""
|
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,88 +1,87 @@
|
|
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
|
-
from
|
8
|
-
|
9
|
-
|
10
|
-
from PySide6.QtWidgets import QMainWindow, QMessageBox, QTreeWidget
|
8
|
+
from typing import TYPE_CHECKING
|
9
|
+
|
10
|
+
import lxml.etree as ET # noqa: N812
|
11
11
|
import pandas as pd
|
12
|
-
|
13
|
-
import logging
|
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
|
-
from excel2moodle.ui.dialogs import QuestionVariantDialog
|
19
|
-
from excel2moodle.ui.treewidget import QuestionItem, CategoryItem
|
20
|
-
from excel2moodle.core.questionValidator import Validator
|
21
|
-
from excel2moodle.ui.settings import Settings
|
22
|
-
from excel2moodle.core.question import Question
|
23
16
|
from excel2moodle.core.exceptions import InvalidFieldException, QNotParsedException
|
17
|
+
from excel2moodle.core.question import Question
|
18
|
+
from excel2moodle.core.questionValidator import Validator
|
19
|
+
from excel2moodle.logger import QSignaler
|
20
|
+
from excel2moodle.ui.dialogs import QuestionVariantDialog
|
21
|
+
from excel2moodle.ui.settings import Settings, SettingsKey
|
22
|
+
from excel2moodle.ui.treewidget import QuestionItem
|
24
23
|
|
24
|
+
if TYPE_CHECKING:
|
25
|
+
from PySide6.QtWidgets import QMainWindow
|
25
26
|
|
26
27
|
logger = logging.getLogger(__name__)
|
27
28
|
|
28
29
|
|
29
|
-
class QuestionDB
|
30
|
-
"""oberste Klasse für den Test"""
|
30
|
+
class QuestionDB:
|
31
|
+
"""oberste Klasse für den Test."""
|
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.
|
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]={}
|
41
|
-
self.settings.shPathChanged.connect(self.onSheetPathChanged)
|
40
|
+
self.categories: dict[str, Category] = {}
|
42
41
|
|
43
|
-
|
44
|
-
|
45
|
-
logger.debug("Slot, new Spreadsheet triggered")
|
46
|
-
self.spreadSheetPath = sheet
|
47
|
-
self.svgFolder = (self.spreadSheetPath.parent / 'Abbildungen_SVG')
|
48
|
-
self.retrieveCategoriesData()
|
49
|
-
self.parseAll()
|
42
|
+
def readSpreadsheetData(self, sheet: Path) -> None:
|
43
|
+
"""Read the metadata and questions from the spreadsheet.
|
50
44
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
The information that will be shown in the UI like description and points is retrieved from one spreadsheet sheet.
|
55
|
-
This method gathers this information and stores it in the ``categoriesMetaData`` dataframe
|
45
|
+
This method gathers this information and stores it in the
|
46
|
+
``categoriesMetaData`` dataframe
|
47
|
+
It also reads the question data and stores it in ``self.categories = {}``
|
56
48
|
"""
|
57
|
-
|
58
49
|
logger.info("Start Parsing the Excel Metadata Sheet\n")
|
59
|
-
with open(
|
50
|
+
with Path(sheet).open("rb") as f:
|
60
51
|
excelFile = pd.ExcelFile(f)
|
61
|
-
self.categoriesMetaData = pd.read_excel(
|
62
|
-
|
63
|
-
|
52
|
+
self.categoriesMetaData = pd.read_excel(
|
53
|
+
f,
|
54
|
+
sheet_name="Kategorien",
|
55
|
+
usecols=["Kategorie", "Beschreibung", "Punkte", "Version"],
|
56
|
+
index_col=0,
|
57
|
+
)
|
64
58
|
logger.info("Sucessfully read categoriesMetaData")
|
65
|
-
print(self.categoriesMetaData)
|
66
59
|
self.categories = {}
|
67
60
|
for sh in excelFile.sheet_names:
|
68
61
|
if sh.startswith("KAT"):
|
69
62
|
n = int(sh[4:])
|
70
|
-
katDf = pd.read_excel(
|
63
|
+
katDf = pd.read_excel(
|
64
|
+
f,
|
65
|
+
sheet_name=str(sh),
|
66
|
+
index_col=0,
|
67
|
+
header=None,
|
68
|
+
)
|
71
69
|
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
|
-
|
70
|
+
p = self.categoriesMetaData["Punkte"].iloc[n - 1]
|
71
|
+
points = p if not pd.isna(p) else 1
|
72
|
+
v = self.categoriesMetaData["Version"].iloc[n - 1]
|
73
|
+
version = v if not pd.isna(v) else 0
|
74
|
+
self.categories[sh] = Category(
|
75
|
+
n,
|
76
|
+
sh,
|
77
|
+
self.categoriesMetaData["Beschreibung"].iloc[n - 1],
|
78
|
+
dataframe=katDf,
|
79
|
+
points=points,
|
80
|
+
version=version,
|
81
|
+
)
|
81
82
|
self.dataChanged.signal.emit("whoo")
|
82
|
-
return None
|
83
83
|
|
84
|
-
|
85
|
-
def parseAll(self):
|
84
|
+
def parseAll(self) -> None:
|
86
85
|
self.mainTree = ET.Element("quiz")
|
87
86
|
for c in self.categories.values():
|
88
87
|
validator = Validator(c)
|
@@ -95,50 +94,67 @@ class QuestionDB():
|
|
95
94
|
try:
|
96
95
|
check = validator.validate()
|
97
96
|
except InvalidFieldException as e:
|
98
|
-
logger.
|
99
|
-
|
100
|
-
|
97
|
+
logger.exception(
|
98
|
+
f"Question {c.id}{q:02d} is invalid.",
|
99
|
+
exc_info=e,
|
100
|
+
)
|
101
|
+
if check:
|
102
|
+
c.questions[q] = validator.question
|
101
103
|
try:
|
102
104
|
c.parseQ(c.questions[q], validator.qdata)
|
103
105
|
except QNotParsedException as e:
|
104
|
-
logger.
|
105
|
-
|
106
|
-
|
106
|
+
logger.exception(
|
107
|
+
f"Frage {
|
108
|
+
c.questions[q].id
|
109
|
+
} konnte nicht erstellt werden",
|
110
|
+
exc_info=e,
|
111
|
+
)
|
112
|
+
|
113
|
+
def appendQuestions(
|
114
|
+
self, questions: list[QuestionItem], file: Path | None = None
|
115
|
+
) -> None:
|
116
|
+
"""Append selected question Elements to the tree."""
|
107
117
|
tree = ET.Element("quiz")
|
108
|
-
catdict:dict[Category,list[Question]]={}
|
118
|
+
catdict: dict[Category, list[Question]] = {}
|
109
119
|
for q in questions:
|
110
120
|
logger.debug(f"got a question to append {q=}")
|
111
121
|
cat = q.parent().getCategory()
|
112
122
|
if cat not in catdict:
|
113
123
|
catdict[cat] = []
|
114
|
-
print(f"Category is parent of Q {cat=}")
|
115
124
|
catdict[cat].append(q.getQuestion())
|
116
125
|
for cat, qlist in catdict.items():
|
117
|
-
|
118
|
-
|
126
|
+
self.appendQElements(
|
127
|
+
cat,
|
128
|
+
qlist,
|
129
|
+
tree=tree,
|
130
|
+
includeHeader=self.settings.get(SettingsKey.INCLUDEINCATS),
|
131
|
+
)
|
119
132
|
stringHelpers.printDom(tree, file=file)
|
120
133
|
|
121
|
-
def appendQElements(
|
134
|
+
def appendQElements(
|
135
|
+
self,
|
136
|
+
cat: Category,
|
137
|
+
qList: list[Question],
|
138
|
+
tree: ET.Element,
|
139
|
+
includeHeader: bool = True,
|
140
|
+
) -> None:
|
122
141
|
if includeHeader:
|
123
|
-
tree.append(
|
142
|
+
tree.append(cat.getCategoryHeader())
|
124
143
|
logger.debug(f"Appended a new category item {cat=}")
|
125
|
-
|
126
|
-
variant = 1
|
144
|
+
variant: int = self.settings.get(SettingsKey.QUESTIONVARIANT)
|
127
145
|
for q in qList:
|
128
146
|
if cat.parseQ(q):
|
129
147
|
if q.variants is not None:
|
130
|
-
if
|
148
|
+
if variant == 0 or variant > q.variants:
|
131
149
|
dialog = QuestionVariantDialog(self.window, q)
|
132
150
|
if dialog.exec() == QtWidgets.QDialog.Accepted:
|
133
151
|
variant = dialog.variant
|
134
|
-
sameVariant = dialog.categoryWide
|
135
152
|
logger.debug(f"Die Fragen-Variante {variant} wurde gewählt")
|
136
153
|
q.assemble(variant)
|
137
|
-
else:
|
154
|
+
else:
|
155
|
+
pass
|
138
156
|
else:
|
139
157
|
q.assemble()
|
140
158
|
tree.append(q.element)
|
141
|
-
else:
|
142
|
-
|
143
|
-
|
144
|
-
|
159
|
+
else:
|
160
|
+
logger.warning(f"Frage {q} wurde nicht erstellt")
|
excel2moodle/core/etHelpers.py
CHANGED
@@ -1,62 +1,62 @@
|
|
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
|
"""
|
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
|
+
|
13
|
+
|
14
|
+
def getElement(eleName: str, text: str, **attribs) -> ET.Element:
|
15
|
+
"""Creates an XML-Element with text.
|
11
16
|
|
12
|
-
def getElement( eleName : str, text: str, **attribs)->ET.Element:
|
13
|
-
"""Creates an XML-Element with text
|
14
|
-
|
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
|
-
|
22
23
|
toEle = ET.Element(eleName)
|
23
24
|
toEle.text = str(text)
|
24
25
|
for k, v in attribs.items():
|
25
|
-
toEle.set(k,v)
|
26
|
+
toEle.set(k, v)
|
26
27
|
return toEle
|
27
28
|
|
28
29
|
|
29
|
-
def getTextElement(
|
30
|
-
"""Creates two nested elements: ``eleName`` with child ``text`` which holds the text"""
|
31
|
-
|
30
|
+
def getTextElement(eleName: str, text: str | DFIndex, **attribs) -> ET.Element:
|
31
|
+
"""Creates two nested elements: ``eleName`` with child ``text`` which holds the text."""
|
32
32
|
toEle = ET.Element(eleName, **attribs)
|
33
33
|
child = getElement("text", text)
|
34
34
|
toEle.append(child)
|
35
35
|
return toEle
|
36
36
|
|
37
|
-
def getCdatTxtElement(subEle: ET._Element|list[ET._Element])->ET.Element:
|
38
|
-
"""Puts all ``subEle`` as ``str`` into a ``<text><![CDATA[...subEle...]]</text>`` element"""
|
39
37
|
|
38
|
+
def getCdatTxtElement(subEle: ET._Element | list[ET._Element]) -> ET.Element:
|
39
|
+
"""Puts all ``subEle`` as ``str`` into a ``<text><![CDATA[...subEle...]]</text>`` element."""
|
40
40
|
textEle = ET.Element(XMLTags.TEXT)
|
41
41
|
if isinstance(subEle, list):
|
42
|
-
elementString
|
42
|
+
elementString: list = []
|
43
43
|
for i in subEle:
|
44
44
|
elementString.append(ET.tostring(i, encoding="unicode", pretty_print=True))
|
45
45
|
textEle.text = ET.CDATA("".join(elementString))
|
46
46
|
return textEle
|
47
|
-
|
48
|
-
|
49
|
-
|
47
|
+
textEle.text = ET.CDATA(
|
48
|
+
ET.tostring(subEle, encoding="unicode", pretty_print=True),
|
49
|
+
)
|
50
|
+
return textEle
|
50
51
|
|
51
52
|
|
52
|
-
def getFeedBEle(
|
53
|
-
|
54
|
-
|
53
|
+
def getFeedBEle(
|
54
|
+
feedback: XMLTags,
|
55
|
+
text: str | None = None,
|
56
|
+
style: TextElements | None = None,
|
57
|
+
) -> ET.Element:
|
55
58
|
"""Gets ET Elements with the feedback for the question."""
|
56
|
-
if style is None
|
57
|
-
span = feedBElements[feedback]
|
58
|
-
else:
|
59
|
-
span = style.create()
|
59
|
+
span = feedBElements[feedback] if style is None else style.create()
|
60
60
|
if text is None:
|
61
61
|
text = feedbackStr[feedback]
|
62
62
|
ele = ET.Element(feedback, format="html")
|