excel2moodle 0.6.0__tar.gz → 0.6.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {excel2moodle-0.6.0/excel2moodle.egg-info → excel2moodle-0.6.2}/PKG-INFO +53 -19
- {excel2moodle-0.6.0 → excel2moodle-0.6.2}/README.md +51 -18
- {excel2moodle-0.6.0 → excel2moodle-0.6.2}/excel2moodle/__main__.py +3 -2
- {excel2moodle-0.6.0 → excel2moodle-0.6.2}/excel2moodle/core/category.py +24 -6
- {excel2moodle-0.6.0 → excel2moodle-0.6.2}/excel2moodle/core/dataStructure.py +116 -27
- {excel2moodle-0.6.0 → excel2moodle-0.6.2}/excel2moodle/core/etHelpers.py +0 -20
- {excel2moodle-0.6.0 → excel2moodle-0.6.2}/excel2moodle/core/globals.py +0 -9
- {excel2moodle-0.6.0 → excel2moodle-0.6.2}/excel2moodle/core/parser.py +21 -13
- {excel2moodle-0.6.0 → excel2moodle-0.6.2}/excel2moodle/core/question.py +43 -23
- {excel2moodle-0.6.0 → excel2moodle-0.6.2}/excel2moodle/core/settings.py +11 -4
- {excel2moodle-0.6.0 → excel2moodle-0.6.2}/excel2moodle/extra/equationVerification.py +0 -2
- {excel2moodle-0.6.0 → excel2moodle-0.6.2}/excel2moodle/question_types/cloze.py +116 -83
- {excel2moodle-0.6.0 → excel2moodle-0.6.2}/excel2moodle/question_types/mc.py +15 -10
- {excel2moodle-0.6.0 → excel2moodle-0.6.2}/excel2moodle/question_types/nf.py +7 -1
- {excel2moodle-0.6.0 → excel2moodle-0.6.2}/excel2moodle/question_types/nfm.py +14 -10
- {excel2moodle-0.6.0 → excel2moodle-0.6.2}/excel2moodle/ui/UI_exportSettingsDialog.py +60 -14
- {excel2moodle-0.6.0 → excel2moodle-0.6.2}/excel2moodle/ui/appUi.py +21 -7
- {excel2moodle-0.6.0 → excel2moodle-0.6.2}/excel2moodle/ui/dialogs.py +12 -0
- {excel2moodle-0.6.0 → excel2moodle-0.6.2/excel2moodle.egg-info}/PKG-INFO +53 -19
- {excel2moodle-0.6.0 → excel2moodle-0.6.2}/excel2moodle.egg-info/SOURCES.txt +1 -1
- {excel2moodle-0.6.0 → excel2moodle-0.6.2}/excel2moodle.egg-info/requires.txt +1 -0
- {excel2moodle-0.6.0 → excel2moodle-0.6.2}/pyproject.toml +3 -2
- excel2moodle-0.6.2/test/test_feedbacking.py +73 -0
- {excel2moodle-0.6.0 → excel2moodle-0.6.2}/test/test_nfmParsing.py +41 -12
- {excel2moodle-0.6.0 → excel2moodle-0.6.2}/test/test_parseQuestion.py +0 -14
- excel2moodle-0.6.0/MANIFEST.in +0 -1
- {excel2moodle-0.6.0 → excel2moodle-0.6.2}/LICENSE +0 -0
- {excel2moodle-0.6.0 → excel2moodle-0.6.2}/excel2moodle/__init__.py +0 -0
- {excel2moodle-0.6.0 → excel2moodle-0.6.2}/excel2moodle/core/__init__.py +0 -0
- {excel2moodle-0.6.0 → excel2moodle-0.6.2}/excel2moodle/core/bullets.py +0 -0
- {excel2moodle-0.6.0 → excel2moodle-0.6.2}/excel2moodle/core/exceptions.py +0 -0
- {excel2moodle-0.6.0 → excel2moodle-0.6.2}/excel2moodle/core/stringHelpers.py +0 -0
- {excel2moodle-0.6.0 → excel2moodle-0.6.2}/excel2moodle/core/validator.py +0 -0
- {excel2moodle-0.6.0 → excel2moodle-0.6.2}/excel2moodle/extra/__init__.py +0 -0
- {excel2moodle-0.6.0 → excel2moodle-0.6.2}/excel2moodle/extra/variableGenerator.py +0 -0
- {excel2moodle-0.6.0 → excel2moodle-0.6.2}/excel2moodle/logger.py +0 -0
- {excel2moodle-0.6.0 → excel2moodle-0.6.2}/excel2moodle/question_types/__init__.py +0 -0
- {excel2moodle-0.6.0 → excel2moodle-0.6.2}/excel2moodle/ui/UI_equationChecker.py +0 -0
- {excel2moodle-0.6.0 → excel2moodle-0.6.2}/excel2moodle/ui/UI_mainWindow.py +0 -0
- {excel2moodle-0.6.0 → excel2moodle-0.6.2}/excel2moodle/ui/UI_variableGenerator.py +0 -0
- {excel2moodle-0.6.0 → excel2moodle-0.6.2}/excel2moodle/ui/UI_variantDialog.py +0 -0
- {excel2moodle-0.6.0 → excel2moodle-0.6.2}/excel2moodle/ui/__init__.py +0 -0
- {excel2moodle-0.6.0 → excel2moodle-0.6.2}/excel2moodle/ui/equationChecker.py +0 -0
- {excel2moodle-0.6.0 → excel2moodle-0.6.2}/excel2moodle/ui/treewidget.py +0 -0
- {excel2moodle-0.6.0 → excel2moodle-0.6.2}/excel2moodle.egg-info/dependency_links.txt +0 -0
- {excel2moodle-0.6.0 → excel2moodle-0.6.2}/excel2moodle.egg-info/entry_points.txt +0 -0
- {excel2moodle-0.6.0 → excel2moodle-0.6.2}/excel2moodle.egg-info/top_level.txt +0 -0
- {excel2moodle-0.6.0 → excel2moodle-0.6.2}/setup.cfg +0 -0
- {excel2moodle-0.6.0 → excel2moodle-0.6.2}/test/test_bullets.py +0 -0
- {excel2moodle-0.6.0 → excel2moodle-0.6.2}/test/test_picture.py +0 -0
- {excel2moodle-0.6.0 → excel2moodle-0.6.2}/test/test_questionDataGet.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: excel2moodle
|
3
|
-
Version: 0.6.
|
3
|
+
Version: 0.6.2
|
4
4
|
Summary: A package for converting questions from a spreadsheet, to valid moodle-xml
|
5
5
|
Author: Jakob Bosse
|
6
6
|
License-Expression: GPL-3.0-or-later
|
@@ -17,16 +17,20 @@ Requires-Dist: lxml>=5.4.0
|
|
17
17
|
Requires-Dist: asteval>=1.0.6
|
18
18
|
Requires-Dist: python-calamine>=0.3.2
|
19
19
|
Requires-Dist: pyside6-essentials>=6.8.0
|
20
|
+
Requires-Dist: pyyaml>=6.0.2
|
20
21
|
Dynamic: license-file
|
21
22
|
|
22
23
|
# excel 2 Moodle
|
23
|
-
{width=35%}
|
24
|
+
{width=35%}
|
24
25
|
|
25
|
-
|
26
|
-
The idea is to write the questions data into a spreadsheet file, from which the
|
27
|
-
All questions or a selection of questions can
|
26
|
+
*excel2moodle* helps to create Moodle questions in less time.
|
27
|
+
The idea is to write the questions data into a spreadsheet file, from which the *excel2moodle* generates moodle compliant xml Files.
|
28
|
+
All questions or a selection of questions can then be imported into moodle.
|
28
29
|
|
29
30
|
## Concept
|
31
|
+
At the heart the *excel2moodle* is a simple key-value-pair "syntax", where the key is set once in the first spreadsheet column.
|
32
|
+
Each key can be provided with a value for each question in its column
|
33
|
+
To enhance reusability, key values which are correct for more than one question can be set per category or for all questions.
|
30
34
|
The concept is, to store the different questions into categories of similar types and difficulties of questions, for each of which, a separated sheet in the Spreadsheet document should be created.
|
31
35
|
|
32
36
|
A `settings` sheet contains global settings to be used for all questions and categories.
|
@@ -41,33 +45,38 @@ If you already have python and uv installed, it is as easy as running `uv tool i
|
|
41
45
|
|
42
46
|
### [ Documentation ](https://jbosse3.gitlab.io/excel2moodle/index.html)
|
43
47
|
Once excel2moodle is installed you can checkout the [example question sheet](https://gitlab.com/jbosse3/excel2moodle/-/tree/master/example?ref_type=heads)
|
44
|
-
in the repository.
|
48
|
+
in the repository. You need to download all Files in the `example` directory and save them together.
|
45
49
|
|
46
|
-
|
50
|
+
Most steps are already documented as [ tutorials ](https://jbosse3.gitlab.io/excel2moodle/howto.html)
|
47
51
|
you can follow along.
|
48
52
|
|
49
53
|
And please have a look into the [**user Reference**](https://jbosse3.gitlab.io/excel2moodle/userReference.html)
|
50
54
|
of the documentation.
|
51
|
-
That part explains each part of defining a question.
|
55
|
+
That part explains in more detail each part of defining a question.
|
52
56
|
|
53
57
|
|
54
|
-
##
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
+
## Features
|
59
|
+
- Fully parametrized numeric questions:
|
60
|
+
* Formulas for the calculated results can be coded into extensive python modules, which can be loaded.
|
61
|
+
- Question Preview:
|
58
62
|
+ This helps you when selecting the correct questions for the export.
|
59
|
-
|
63
|
+
- Equation Verification:
|
64
|
+
+ this tool helps you to validate the correct equation for the parametrized Questions.
|
65
|
+
- Variable Generation:
|
66
|
+
+ You can generate variables for the parametric Question to easily create hundreds of different variants of the same question.
|
67
|
+
- Export Options:
|
60
68
|
+ you can export the questions preserving the categories in moodle
|
61
69
|
|
62
70
|
### Question Types
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
*
|
71
|
+
- Generate multiple Choice Questions:
|
72
|
+
* The answers can be pictures or normal text
|
73
|
+
- Generate Numeric Questions
|
74
|
+
- Generate parametrized numeric Questions:
|
75
|
+
* With the parametrization *excel2moodle* calculats the numeric answer from a given formula based on a set of variables.
|
76
|
+
- Generate parametrized cloze Questions
|
68
77
|
|
69
78
|
|
70
|
-
{width=80%}
|
79
|
+
{width=80%}
|
71
80
|
|
72
81
|
## Licensing and authorship
|
73
82
|
excel2moodle is lincensed under the latest [GNU GPL license](https://gitlab.com/jbosse3/excel2moodle/-/blob/master/LICENSE)
|
@@ -81,6 +90,31 @@ If You want to support my work as well, you can by me a [coffee](https://ko-fi.c
|
|
81
90
|
|
82
91
|
# Changelogs
|
83
92
|
|
93
|
+
## 0.6.2 (2025-08-02)
|
94
|
+
Adding export options and fixing cloze points bug
|
95
|
+
|
96
|
+
### feature (4 changes)
|
97
|
+
|
98
|
+
- [Added export options to include all Question Variants and generate report](https://gitlab.com/jbosse3/excel2moodle/-/commit/6433615de23174451748b69669a9dce748dd5b4d)
|
99
|
+
- [Implemented export dialog generator method](https://gitlab.com/jbosse3/excel2moodle/-/commit/a8eda982309bf9a6dae7ef2b261a59654f2c8910)
|
100
|
+
- [Answer Feedback strings settable in sheet](https://gitlab.com/jbosse3/excel2moodle/-/commit/ad90da49ac60e429ad3243f54846b08f0caf5bc7)
|
101
|
+
- [Inverted result and feedback for NFM & Cloze questions](https://gitlab.com/jbosse3/excel2moodle/-/commit/57d77c83a661398b0082f84d25e5447000df9096)
|
102
|
+
|
103
|
+
### improvement (1 change)
|
104
|
+
|
105
|
+
- [Missing `settings` sheet raises an error](https://gitlab.com/jbosse3/excel2moodle/-/commit/e1cc42c1d31981bf74582b23c24c6ac378e9256d)
|
106
|
+
|
107
|
+
### bugfix (1 change)
|
108
|
+
|
109
|
+
- [resolve cloze moodle import error due to float points](https://gitlab.com/jbosse3/excel2moodle/-/commit/f13b7b9df39df55d65b6063a9deb1fc1c72f5ebb)
|
110
|
+
|
111
|
+
## 0.6.1 (2025-07-12)
|
112
|
+
Fixing import error caused by dumping pyside meta package
|
113
|
+
|
114
|
+
### bugfix (1 change)
|
115
|
+
|
116
|
+
- [fixing pyside import error](https://gitlab.com/jbosse3/excel2moodle/-/commit/e5e0fc7695caa1a6864785828ff7311fa9624ad4)
|
117
|
+
|
84
118
|
## 0.6.0 (2025-07-12)
|
85
119
|
Added variable generator and other architechtural improvements
|
86
120
|
|
@@ -1,11 +1,14 @@
|
|
1
1
|
# excel 2 Moodle
|
2
|
-
{width=35%}
|
2
|
+
{width=35%}
|
3
3
|
|
4
|
-
|
5
|
-
The idea is to write the questions data into a spreadsheet file, from which the
|
6
|
-
All questions or a selection of questions can
|
4
|
+
*excel2moodle* helps to create Moodle questions in less time.
|
5
|
+
The idea is to write the questions data into a spreadsheet file, from which the *excel2moodle* generates moodle compliant xml Files.
|
6
|
+
All questions or a selection of questions can then be imported into moodle.
|
7
7
|
|
8
8
|
## Concept
|
9
|
+
At the heart the *excel2moodle* is a simple key-value-pair "syntax", where the key is set once in the first spreadsheet column.
|
10
|
+
Each key can be provided with a value for each question in its column
|
11
|
+
To enhance reusability, key values which are correct for more than one question can be set per category or for all questions.
|
9
12
|
The concept is, to store the different questions into categories of similar types and difficulties of questions, for each of which, a separated sheet in the Spreadsheet document should be created.
|
10
13
|
|
11
14
|
A `settings` sheet contains global settings to be used for all questions and categories.
|
@@ -20,33 +23,38 @@ If you already have python and uv installed, it is as easy as running `uv tool i
|
|
20
23
|
|
21
24
|
### [ Documentation ](https://jbosse3.gitlab.io/excel2moodle/index.html)
|
22
25
|
Once excel2moodle is installed you can checkout the [example question sheet](https://gitlab.com/jbosse3/excel2moodle/-/tree/master/example?ref_type=heads)
|
23
|
-
in the repository.
|
26
|
+
in the repository. You need to download all Files in the `example` directory and save them together.
|
24
27
|
|
25
|
-
|
28
|
+
Most steps are already documented as [ tutorials ](https://jbosse3.gitlab.io/excel2moodle/howto.html)
|
26
29
|
you can follow along.
|
27
30
|
|
28
31
|
And please have a look into the [**user Reference**](https://jbosse3.gitlab.io/excel2moodle/userReference.html)
|
29
32
|
of the documentation.
|
30
|
-
That part explains each part of defining a question.
|
33
|
+
That part explains in more detail each part of defining a question.
|
31
34
|
|
32
35
|
|
33
|
-
##
|
34
|
-
|
35
|
-
|
36
|
-
|
36
|
+
## Features
|
37
|
+
- Fully parametrized numeric questions:
|
38
|
+
* Formulas for the calculated results can be coded into extensive python modules, which can be loaded.
|
39
|
+
- Question Preview:
|
37
40
|
+ This helps you when selecting the correct questions for the export.
|
38
|
-
|
41
|
+
- Equation Verification:
|
42
|
+
+ this tool helps you to validate the correct equation for the parametrized Questions.
|
43
|
+
- Variable Generation:
|
44
|
+
+ You can generate variables for the parametric Question to easily create hundreds of different variants of the same question.
|
45
|
+
- Export Options:
|
39
46
|
+ you can export the questions preserving the categories in moodle
|
40
47
|
|
41
48
|
### Question Types
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
*
|
49
|
+
- Generate multiple Choice Questions:
|
50
|
+
* The answers can be pictures or normal text
|
51
|
+
- Generate Numeric Questions
|
52
|
+
- Generate parametrized numeric Questions:
|
53
|
+
* With the parametrization *excel2moodle* calculats the numeric answer from a given formula based on a set of variables.
|
54
|
+
- Generate parametrized cloze Questions
|
47
55
|
|
48
56
|
|
49
|
-
{width=80%}
|
57
|
+
{width=80%}
|
50
58
|
|
51
59
|
## Licensing and authorship
|
52
60
|
excel2moodle is lincensed under the latest [GNU GPL license](https://gitlab.com/jbosse3/excel2moodle/-/blob/master/LICENSE)
|
@@ -60,6 +68,31 @@ If You want to support my work as well, you can by me a [coffee](https://ko-fi.c
|
|
60
68
|
|
61
69
|
# Changelogs
|
62
70
|
|
71
|
+
## 0.6.2 (2025-08-02)
|
72
|
+
Adding export options and fixing cloze points bug
|
73
|
+
|
74
|
+
### feature (4 changes)
|
75
|
+
|
76
|
+
- [Added export options to include all Question Variants and generate report](https://gitlab.com/jbosse3/excel2moodle/-/commit/6433615de23174451748b69669a9dce748dd5b4d)
|
77
|
+
- [Implemented export dialog generator method](https://gitlab.com/jbosse3/excel2moodle/-/commit/a8eda982309bf9a6dae7ef2b261a59654f2c8910)
|
78
|
+
- [Answer Feedback strings settable in sheet](https://gitlab.com/jbosse3/excel2moodle/-/commit/ad90da49ac60e429ad3243f54846b08f0caf5bc7)
|
79
|
+
- [Inverted result and feedback for NFM & Cloze questions](https://gitlab.com/jbosse3/excel2moodle/-/commit/57d77c83a661398b0082f84d25e5447000df9096)
|
80
|
+
|
81
|
+
### improvement (1 change)
|
82
|
+
|
83
|
+
- [Missing `settings` sheet raises an error](https://gitlab.com/jbosse3/excel2moodle/-/commit/e1cc42c1d31981bf74582b23c24c6ac378e9256d)
|
84
|
+
|
85
|
+
### bugfix (1 change)
|
86
|
+
|
87
|
+
- [resolve cloze moodle import error due to float points](https://gitlab.com/jbosse3/excel2moodle/-/commit/f13b7b9df39df55d65b6063a9deb1fc1c72f5ebb)
|
88
|
+
|
89
|
+
## 0.6.1 (2025-07-12)
|
90
|
+
Fixing import error caused by dumping pyside meta package
|
91
|
+
|
92
|
+
### bugfix (1 change)
|
93
|
+
|
94
|
+
- [fixing pyside import error](https://gitlab.com/jbosse3/excel2moodle/-/commit/e5e0fc7695caa1a6864785828ff7311fa9624ad4)
|
95
|
+
|
63
96
|
## 0.6.0 (2025-07-12)
|
64
97
|
Added variable generator and other architechtural improvements
|
65
98
|
|
@@ -2,9 +2,10 @@
|
|
2
2
|
|
3
3
|
import logging.config as logConfig
|
4
4
|
import signal
|
5
|
+
import sys
|
5
6
|
from pathlib import Path
|
6
7
|
|
7
|
-
from PySide6 import
|
8
|
+
from PySide6.QtWidgets import QApplication
|
8
9
|
|
9
10
|
import excel2moodle
|
10
11
|
from excel2moodle import e2mMetadata, mainLogger
|
@@ -23,7 +24,7 @@ def main() -> None:
|
|
23
24
|
logfile.replace(f"{logfile}.old")
|
24
25
|
logConfig.dictConfig(config=loggerConfig)
|
25
26
|
signal.signal(signal.SIGINT, signal.SIG_DFL)
|
26
|
-
app =
|
27
|
+
app = QApplication(sys.argv)
|
27
28
|
settings = Settings()
|
28
29
|
database: dataStructure.QuestionDB = dataStructure.QuestionDB(settings)
|
29
30
|
window = ui.MainWindow(settings, database)
|
@@ -5,10 +5,9 @@ from typing import TYPE_CHECKING
|
|
5
5
|
import lxml.etree as ET
|
6
6
|
import pandas as pd
|
7
7
|
|
8
|
-
from excel2moodle.core.settings import Tags
|
9
|
-
|
10
8
|
if TYPE_CHECKING:
|
11
9
|
from excel2moodle.core.question import Question
|
10
|
+
from excel2moodle.core.settings import Tags
|
12
11
|
|
13
12
|
loggerObj = logging.getLogger(__name__)
|
14
13
|
|
@@ -30,7 +29,7 @@ class Category:
|
|
30
29
|
self.desc = str(description)
|
31
30
|
self.dataframe: pd.DataFrame = dataframe
|
32
31
|
self.settings: dict[str, float | str] = settings if settings else {}
|
33
|
-
self.
|
32
|
+
self._questions: dict[int, Question]
|
34
33
|
self.maxVariants: int | None = None
|
35
34
|
loggerObj.info("initializing Category %s", self.NAME)
|
36
35
|
|
@@ -50,6 +49,18 @@ class Category:
|
|
50
49
|
def id(self) -> str:
|
51
50
|
return f"{self.n:02d}"
|
52
51
|
|
52
|
+
@property
|
53
|
+
def questions(self) -> dict:
|
54
|
+
if not hasattr(self, "_questions"):
|
55
|
+
msg = "Category question are not yet initialized"
|
56
|
+
raise ValueError(msg)
|
57
|
+
return self._questions
|
58
|
+
|
59
|
+
def appendQuestion(self, questionNumber: int, question) -> None:
|
60
|
+
if not hasattr(self, "_questions"):
|
61
|
+
self._questions: dict[int, Question] = {}
|
62
|
+
self._questions[questionNumber] = question
|
63
|
+
|
53
64
|
def __hash__(self) -> int:
|
54
65
|
return hash(self.NAME)
|
55
66
|
|
@@ -58,13 +69,20 @@ class Category:
|
|
58
69
|
return self.NAME == other.NAME
|
59
70
|
return False
|
60
71
|
|
61
|
-
def getCategoryHeader(self) -> ET.Element:
|
72
|
+
def getCategoryHeader(self, subCategory: str | None = None) -> ET.Element:
|
62
73
|
"""Insert an <question type='category'> before all Questions of this Category."""
|
63
74
|
header = ET.Element("question", type="category")
|
64
75
|
cat = ET.SubElement(header, "category")
|
65
76
|
info = ET.SubElement(header, "info", format="html")
|
66
|
-
|
77
|
+
catStr = (
|
78
|
+
f"$module$/top/{self.NAME}"
|
79
|
+
if subCategory is None
|
80
|
+
else f"$module$/top/{self.NAME}/Question-{subCategory[2:]}_Variants"
|
81
|
+
)
|
82
|
+
ET.SubElement(cat, "text").text = catStr
|
67
83
|
ET.SubElement(info, "text").text = str(self.desc)
|
68
|
-
ET.SubElement(header, "idnumber").text =
|
84
|
+
ET.SubElement(header, "idnumber").text = (
|
85
|
+
f"cat-{self.id}" if subCategory is None else f"variants-{subCategory}"
|
86
|
+
)
|
69
87
|
ET.indent(header)
|
70
88
|
return header
|
@@ -3,6 +3,7 @@
|
|
3
3
|
At the heart is the class ``xmlTest``
|
4
4
|
"""
|
5
5
|
|
6
|
+
import datetime as dt
|
6
7
|
import logging
|
7
8
|
import sys
|
8
9
|
from concurrent.futures import ProcessPoolExecutor, as_completed
|
@@ -11,14 +12,16 @@ from typing import TYPE_CHECKING
|
|
11
12
|
|
12
13
|
import lxml.etree as ET # noqa: N812
|
13
14
|
import pandas as pd
|
15
|
+
import yaml
|
14
16
|
from PySide6.QtCore import QObject, Signal
|
15
17
|
from PySide6.QtWidgets import QDialog
|
16
18
|
|
19
|
+
from excel2moodle import __version__
|
17
20
|
from excel2moodle.core import stringHelpers
|
18
21
|
from excel2moodle.core.category import Category
|
19
22
|
from excel2moodle.core.exceptions import InvalidFieldException, QNotParsedException
|
20
23
|
from excel2moodle.core.globals import Tags
|
21
|
-
from excel2moodle.core.question import Question
|
24
|
+
from excel2moodle.core.question import ParametricQuestion, Question
|
22
25
|
from excel2moodle.core.settings import Settings
|
23
26
|
from excel2moodle.core.validator import Validator
|
24
27
|
from excel2moodle.logger import LogAdapterQuestionID
|
@@ -72,9 +75,10 @@ class QuestionDB:
|
|
72
75
|
def __init__(self, settings: Settings) -> None:
|
73
76
|
self.settings = settings
|
74
77
|
self.window: QMainWindow | None = None
|
75
|
-
self.version = None
|
76
78
|
self.categoriesMetaData: pd.DataFrame
|
77
79
|
self.categories: dict[str, Category]
|
80
|
+
self._exportedQuestions: list[Question] = []
|
81
|
+
self._exportedAll: bool = False
|
78
82
|
|
79
83
|
@property
|
80
84
|
def spreadsheet(self) -> Path:
|
@@ -94,22 +98,39 @@ class QuestionDB:
|
|
94
98
|
``categoriesMetaData`` dataframe
|
95
99
|
Setup the categories and store them in ``self.categories = {}``
|
96
100
|
Pass the question data to the categories.
|
101
|
+
|
102
|
+
Raises
|
103
|
+
------
|
104
|
+
ValueError
|
105
|
+
When there is no 'seetings' worksheet in the file.
|
106
|
+
InvalidFieldException
|
107
|
+
When the settings are invalid
|
108
|
+
|
109
|
+
Before raising it logges the exceptions with a meaningful message.
|
110
|
+
|
97
111
|
"""
|
98
112
|
sheetPath = sheetPath if sheetPath else self.spreadsheet
|
99
113
|
logger.info("Start Parsing the Excel Metadata Sheet\n")
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
114
|
+
try:
|
115
|
+
with Path(sheetPath).open("rb") as f:
|
116
|
+
settingDf = pd.read_excel(
|
117
|
+
f,
|
118
|
+
sheet_name="settings",
|
119
|
+
index_col=0,
|
120
|
+
engine="calamine",
|
121
|
+
)
|
122
|
+
except ValueError:
|
123
|
+
logger.exception(
|
124
|
+
"Did you forget to specify a 'settings' sheet in the file?"
|
106
125
|
)
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
126
|
+
raise
|
127
|
+
logger.debug("Found the settings: \n\t%s", settingDf)
|
128
|
+
settingDf = self.harmonizeDFIndex(settingDf)
|
129
|
+
for tag, value in settingDf.iterrows():
|
130
|
+
val = value.iloc[0]
|
131
|
+
if pd.notna(val):
|
132
|
+
self.settings.set(tag, val)
|
133
|
+
|
113
134
|
try:
|
114
135
|
self._validateProjectSettings(sheetPath=sheetPath)
|
115
136
|
except InvalidFieldException:
|
@@ -342,13 +363,18 @@ class QuestionDB:
|
|
342
363
|
else:
|
343
364
|
msg = "couldn't setup Parser"
|
344
365
|
raise QNotParsedException(msg, question.id)
|
345
|
-
category.
|
366
|
+
category.appendQuestion(qNumber, question)
|
346
367
|
return question
|
347
368
|
|
348
369
|
def appendQuestions(
|
349
|
-
self,
|
370
|
+
self,
|
371
|
+
questions: list[QuestionItem],
|
372
|
+
file: Path | None = None,
|
373
|
+
pCount: int = 0,
|
374
|
+
qCount: int = 0,
|
350
375
|
) -> None:
|
351
376
|
"""Append selected question Elements to the tree."""
|
377
|
+
self._exportedQuestions.clear()
|
352
378
|
tree = ET.Element("quiz")
|
353
379
|
catdict: dict[Category, list[Question]] = {}
|
354
380
|
for q in questions:
|
@@ -365,6 +391,8 @@ class QuestionDB:
|
|
365
391
|
includeHeader=self.settings.get(Tags.INCLUDEINCATS),
|
366
392
|
)
|
367
393
|
stringHelpers.printDom(tree, file=file)
|
394
|
+
if self.settings.get(Tags.GENEXPORTREPORT):
|
395
|
+
self.generateExportReport(file, pCount=pCount, qCount=qCount)
|
368
396
|
|
369
397
|
def _appendQElements(
|
370
398
|
self,
|
@@ -373,17 +401,78 @@ class QuestionDB:
|
|
373
401
|
tree: ET.Element,
|
374
402
|
includeHeader: bool = True,
|
375
403
|
) -> None:
|
376
|
-
|
404
|
+
variant: int = self.settings.get(Tags.QUESTIONVARIANT)
|
405
|
+
if includeHeader or variant == -1:
|
377
406
|
tree.append(cat.getCategoryHeader())
|
378
407
|
logger.debug(f"Appended a new category item {cat=}")
|
379
|
-
|
408
|
+
self._exportedAll: bool = True
|
380
409
|
for q in qList:
|
381
|
-
if
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
410
|
+
if not isinstance(q, ParametricQuestion):
|
411
|
+
tree.append(q.getUpdatedElement())
|
412
|
+
self._exportedQuestions.append(q)
|
413
|
+
continue
|
414
|
+
if variant == -1:
|
415
|
+
tree.append(cat.getCategoryHeader(subCategory=q.id))
|
416
|
+
for var in range(q.parametrics.variants):
|
417
|
+
tree.append(q.getUpdatedElement(variant=var))
|
418
|
+
elif variant == 0 or variant > q.parametrics.variants:
|
419
|
+
dialog = QuestionVariantDialog(self.window, q)
|
420
|
+
if dialog.exec() == QDialog.Accepted:
|
421
|
+
variant = dialog.variant
|
422
|
+
logger.debug("Die Fragen-Variante %s wurde gewählt", variant)
|
423
|
+
else:
|
424
|
+
logger.warning("Keine Fragenvariante wurde gewählt.")
|
425
|
+
tree.append(q.getUpdatedElement(variant=variant))
|
426
|
+
self._exportedQuestions.append(q)
|
427
|
+
|
428
|
+
def generateExportReport(
|
429
|
+
self, file: Path | None = None, pCount: int = 0, qCount: int = 0
|
430
|
+
) -> None:
|
431
|
+
"""Generate a YAML report of the exported questions."""
|
432
|
+
if not self._exportedQuestions:
|
433
|
+
return
|
434
|
+
if file:
|
435
|
+
base_path = file.with_name(f"{file.stem}_export_report.yaml")
|
436
|
+
else:
|
437
|
+
base_path = self.spreadsheet.parent / "export_report.yaml"
|
438
|
+
|
439
|
+
for i in range(99):
|
440
|
+
report_path = base_path.with_name(f"{base_path.stem}-{i:02d}.yaml")
|
441
|
+
if not report_path.resolve().exists():
|
442
|
+
break
|
443
|
+
|
444
|
+
report_data = {
|
445
|
+
"export_metadata": {
|
446
|
+
"export_time": dt.datetime.now(tz=None).strftime("%Y-%m-%d %H:%M:%S"),
|
447
|
+
"excel2moodle_version": __version__,
|
448
|
+
},
|
449
|
+
"categories": {},
|
450
|
+
}
|
451
|
+
if qCount != 0:
|
452
|
+
report_data["export_metadata"]["question_count"] = qCount
|
453
|
+
if pCount != 0:
|
454
|
+
report_data["export_metadata"]["total_point_count"] = pCount
|
455
|
+
|
456
|
+
sorted_questions = sorted(
|
457
|
+
self._exportedQuestions, key=lambda q: (q.category.name, q.id)
|
458
|
+
)
|
459
|
+
|
460
|
+
for question in sorted_questions:
|
461
|
+
category_name = question.category.name
|
462
|
+
if category_name not in report_data["categories"]:
|
463
|
+
report_data["categories"][category_name] = {
|
464
|
+
"description": question.category.desc,
|
465
|
+
"questions": [],
|
466
|
+
}
|
467
|
+
|
468
|
+
question_data = {"id": question.id, "name": question.name}
|
469
|
+
if isinstance(question, ParametricQuestion) and question.currentVariant > 0:
|
470
|
+
if self._exportedAll:
|
471
|
+
question_data["exported_variant"] = "all"
|
472
|
+
else:
|
473
|
+
question_data["exported_variant"] = question.currentVariant + 1
|
474
|
+
|
475
|
+
report_data["categories"][category_name]["questions"].append(question_data)
|
476
|
+
|
477
|
+
with report_path.open("w") as f:
|
478
|
+
yaml.dump(report_data, f, sort_keys=False)
|
@@ -5,9 +5,6 @@ This module host different functions. All of them will return an ``lxml.etree.El
|
|
5
5
|
|
6
6
|
import lxml.etree as ET
|
7
7
|
|
8
|
-
import excel2moodle.core.etHelpers as eth
|
9
|
-
from excel2moodle.core.globals import TextElements, feedbackStr, feedBElements
|
10
|
-
|
11
8
|
from .globals import Tags, XMLTags
|
12
9
|
|
13
10
|
|
@@ -48,20 +45,3 @@ def getCdatTxtElement(subEle: ET._Element | list[ET._Element]) -> ET.Element:
|
|
48
45
|
ET.tostring(subEle, encoding="unicode", pretty_print=True),
|
49
46
|
)
|
50
47
|
return textEle
|
51
|
-
|
52
|
-
|
53
|
-
def getFeedBEle(
|
54
|
-
feedback: XMLTags,
|
55
|
-
text: str | None = None,
|
56
|
-
style: TextElements | None = None,
|
57
|
-
) -> ET.Element:
|
58
|
-
"""Gets ET Elements with the feedback for the question."""
|
59
|
-
span = feedBElements[feedback] if style is None else style.create()
|
60
|
-
if text is None:
|
61
|
-
text = feedbackStr[feedback]
|
62
|
-
ele = ET.Element(feedback, format="html")
|
63
|
-
par = TextElements.PLEFT.create()
|
64
|
-
span.text = text
|
65
|
-
par.append(span)
|
66
|
-
ele.append(eth.getCdatTxtElement(par))
|
67
|
-
return ele
|
@@ -89,12 +89,3 @@ feedBElements = {
|
|
89
89
|
XMLTags.ANSFEEDBACK: TextElements.SPANGREEN.create(),
|
90
90
|
XMLTags.GENFEEDB: TextElements.SPANGREEN.create(),
|
91
91
|
}
|
92
|
-
feedbackStr = {
|
93
|
-
XMLTags.CORFEEDB: "Die Frage wurde richtig beantwortet",
|
94
|
-
XMLTags.PCORFEEDB: "Die Frage wurde teilweise richtig beantwortet",
|
95
|
-
XMLTags.INCORFEEDB: "Die Frage wurde Falsch beantwortet",
|
96
|
-
XMLTags.GENFEEDB: "Sie haben eine Antwort abgegeben",
|
97
|
-
"right": "richtig",
|
98
|
-
"wrong": "falsch",
|
99
|
-
"right1Percent": "Gratultaion, die Frage wurde im Rahmen der Toleranz richtig beantwortet",
|
100
|
-
}
|
@@ -9,7 +9,6 @@ from excel2moodle.core.globals import (
|
|
9
9
|
Tags,
|
10
10
|
TextElements,
|
11
11
|
XMLTags,
|
12
|
-
feedbackStr,
|
13
12
|
feedBElements,
|
14
13
|
)
|
15
14
|
from excel2moodle.core.question import Picture, Question
|
@@ -30,7 +29,6 @@ class QuestionParser:
|
|
30
29
|
|
31
30
|
def __init__(self) -> None:
|
32
31
|
"""Initialize the general Question parser."""
|
33
|
-
self.genFeedbacks: list[XMLTags] = []
|
34
32
|
self.logger: logging.LoggerAdapter
|
35
33
|
|
36
34
|
def setup(self, question: Question) -> None:
|
@@ -84,7 +82,7 @@ class QuestionParser:
|
|
84
82
|
It uses the data from ``self.rawInput`` if ``text`` is type``DFIndex``
|
85
83
|
Otherwise the value of ``text`` will be inserted.
|
86
84
|
"""
|
87
|
-
t = self.rawInput
|
85
|
+
t = self.rawInput.get(text) if isinstance(text, Tags) else text
|
88
86
|
if txtEle is False:
|
89
87
|
self.tmpEle.append(eth.getElement(eleName, t, **attribs))
|
90
88
|
elif txtEle is True:
|
@@ -111,12 +109,11 @@ class QuestionParser:
|
|
111
109
|
self.appendToTmpEle(XMLTags.NAME, text=Tags.NAME, txtEle=True)
|
112
110
|
self.appendToTmpEle(XMLTags.ID, text=self.question.id)
|
113
111
|
textRootElem = ET.Element(XMLTags.QTEXT, format="html")
|
114
|
-
mainTextEle = ET.SubElement(textRootElem, "text")
|
112
|
+
self.mainTextEle = ET.SubElement(textRootElem, "text")
|
115
113
|
self.tmpEle.append(textRootElem)
|
116
|
-
|
114
|
+
if self.question.qtype != "CLOZE":
|
115
|
+
self.appendToTmpEle(XMLTags.POINTS, text=str(self.question.points))
|
117
116
|
self._appendStandardTags()
|
118
|
-
for feedb in self.genFeedbacks:
|
119
|
-
self.tmpEle.append(eth.getFeedBEle(feedb))
|
120
117
|
|
121
118
|
self.htmlRoot = ET.Element("div")
|
122
119
|
self.htmlRoot.append(self.getMainTextElement())
|
@@ -139,10 +136,17 @@ class QuestionParser:
|
|
139
136
|
if ansList is not None:
|
140
137
|
for ele in ansList:
|
141
138
|
self.tmpEle.append(ele)
|
139
|
+
self._finalizeParsing()
|
140
|
+
|
141
|
+
def _finalizeParsing(self) -> None:
|
142
|
+
"""Pass the parsed element trees to the question.
|
143
|
+
|
144
|
+
Intended for the subclasses to do extra stuff.
|
145
|
+
"""
|
142
146
|
self.question._element = self.tmpEle
|
143
147
|
self.question.htmlRoot = self.htmlRoot
|
144
148
|
self.question.isParsed = True
|
145
|
-
self.question.textElement = mainTextEle
|
149
|
+
self.question.textElement = self.mainTextEle
|
146
150
|
self.logger.info("Sucessfully parsed")
|
147
151
|
|
148
152
|
def getFeedBEle(
|
@@ -153,7 +157,8 @@ class QuestionParser:
|
|
153
157
|
) -> ET.Element:
|
154
158
|
span = feedBElements[feedback] if style is None else style.create()
|
155
159
|
if text is None:
|
156
|
-
text
|
160
|
+
self.logger.error("Giving a feedback without providing text is nonsens")
|
161
|
+
text = self.rawInput.get(Tags.GENERALFB)
|
157
162
|
ele = ET.Element(feedback, format="html")
|
158
163
|
par = TextElements.PLEFT.create()
|
159
164
|
span.text = text
|
@@ -169,6 +174,7 @@ class QuestionParser:
|
|
169
174
|
self,
|
170
175
|
result: float = 0.0,
|
171
176
|
fraction: float = 100,
|
177
|
+
feedback: str | None = None,
|
172
178
|
format: str = "moodle_auto_format",
|
173
179
|
) -> ET.Element:
|
174
180
|
"""Get ``<answer/>`` Element specific for the numerical Question.
|
@@ -184,11 +190,13 @@ class QuestionParser:
|
|
184
190
|
fraction=str(fraction),
|
185
191
|
format=format,
|
186
192
|
)
|
193
|
+
if feedback is None:
|
194
|
+
feedback = self.rawInput.get(Tags.TRUEFB)
|
187
195
|
ansEle.append(
|
188
|
-
|
189
|
-
XMLTags.ANSFEEDBACK,
|
190
|
-
|
191
|
-
TextElements.SPANGREEN,
|
196
|
+
self.getFeedBEle(
|
197
|
+
feedback=XMLTags.ANSFEEDBACK,
|
198
|
+
text=feedback,
|
199
|
+
style=TextElements.SPANGREEN,
|
192
200
|
),
|
193
201
|
)
|
194
202
|
absTolerance = round(result * self.rawInput.get(Tags.TOLERANCE), 4)
|