excel2moodle 0.3.6__py3-none-any.whl → 0.3.7__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.
@@ -0,0 +1,30 @@
1
+ """Numerical question implementation."""
2
+
3
+ import lxml.etree as ET
4
+
5
+ from excel2moodle.core.globals import (
6
+ DFIndex,
7
+ XMLTags,
8
+ )
9
+ from excel2moodle.core.parser import QuestionParser
10
+ from excel2moodle.core.question import Question
11
+
12
+
13
+ class NFQuestion(Question):
14
+ def __init__(self, *args, **kwargs) -> None:
15
+ super().__init__(*args, **kwargs)
16
+
17
+
18
+ class NFQuestionParser(QuestionParser):
19
+ """Subclass for parsing numeric questions."""
20
+
21
+ def __init__(self) -> None:
22
+ super().__init__()
23
+ self.genFeedbacks = [XMLTags.GENFEEDB]
24
+
25
+ def setAnswers(self) -> list[ET.Element]:
26
+ result = self.rawInput[DFIndex.RESULT]
27
+ ansEle: list[ET.Element] = []
28
+ tol = self.rawInput[DFIndex.TOLERANCE]
29
+ ansEle.append(self.getNumericAnsElement(result=result, tolerance=tol))
30
+ return ansEle
@@ -0,0 +1,92 @@
1
+ """Numerical question multi implementation."""
2
+
3
+ import re
4
+ from typing import TYPE_CHECKING
5
+
6
+ import lxml.etree as ET
7
+ from asteval import Interpreter
8
+
9
+ from excel2moodle.core import stringHelpers
10
+ from excel2moodle.core.globals import (
11
+ DFIndex,
12
+ XMLTags,
13
+ )
14
+ from excel2moodle.core.parser import QuestionParser
15
+ from excel2moodle.core.question import Question
16
+
17
+ if TYPE_CHECKING:
18
+ import lxml.etree as ET
19
+
20
+
21
+ class NFMQuestion(Question):
22
+ def __init__(self, *args, **kwargs) -> None:
23
+ super().__init__(*args, **kwargs)
24
+
25
+
26
+ class NFMQuestionParser(QuestionParser):
27
+ def __init__(self) -> None:
28
+ super().__init__()
29
+ self.genFeedbacks = [XMLTags.GENFEEDB]
30
+ self.astEval = Interpreter()
31
+
32
+ def setAnswers(self) -> None:
33
+ equation = self.rawInput[DFIndex.RESULT]
34
+ bps = str(self.rawInput[DFIndex.BPOINTS])
35
+ ansElementsList: list[ET.Element] = []
36
+ varNames: list[str] = self._getVarsList(bps)
37
+ self.question.variables, number = self._getVariablesDict(varNames)
38
+ for n in range(number):
39
+ self._setupAstIntprt(self.question.variables, n)
40
+ result = self.astEval(equation)
41
+ if isinstance(result, float):
42
+ tol = self.rawInput[DFIndex.TOLERANCE]
43
+ ansElementsList.append(
44
+ self.getNumericAnsElement(result=round(result, 3), tolerance=tol),
45
+ )
46
+ self.question.answerVariants = ansElementsList
47
+ self.setVariants(len(ansElementsList))
48
+
49
+ def setVariants(self, number: int) -> None:
50
+ self.question.variants = number
51
+ mvar = self.question.category.maxVariants
52
+ if mvar is None:
53
+ self.question.category.maxVariants = number
54
+ else:
55
+ self.question.category.maxVariants = min(number, mvar)
56
+
57
+ def _setupAstIntprt(self, var: dict[str, list[float | int]], index: int) -> None:
58
+ """Setup the asteval Interpreter with the variables."""
59
+ for name, value in var.items():
60
+ self.astEval.symtable[name] = value[index]
61
+
62
+ def _getVariablesDict(self, keyList: list) -> tuple[dict[str, list[float]], int]:
63
+ """Liest alle Variablen-Listen deren Name in ``keyList`` ist aus dem DataFrame im Column[index]."""
64
+ dic: dict = {}
65
+ num: int = 0
66
+ for k in keyList:
67
+ val = self.rawInput[k]
68
+ if isinstance(val, str):
69
+ li = stringHelpers.getListFromStr(val)
70
+ num = len(li)
71
+ variables: list[float] = [float(i.replace(",", ".")) for i in li]
72
+ dic[str(k)] = variables
73
+ else:
74
+ dic[str(k)] = [str(val)]
75
+ num = 1
76
+ self.logger.debug("The following variables were provided: %s", dic)
77
+ return dic, num
78
+
79
+ @staticmethod
80
+ def _getVarsList(bps: str | list[str]) -> list:
81
+ """Durchsucht den bulletPoints String nach den Variablen ``{var}``.
82
+
83
+ It only finds variables after the ``=`` sign, to not catch LaTex.
84
+ """
85
+ varNames = []
86
+ regexFinder = re.compile(r"=\s*\{(\w+)\}")
87
+ if isinstance(bps, list):
88
+ for _p in bps:
89
+ varNames.extend(regexFinder.findall(str(_p)))
90
+ else:
91
+ varNames = regexFinder.findall(str(bps))
92
+ return varNames
excel2moodle/ui/appUi.py CHANGED
@@ -10,9 +10,11 @@ from pathlib import Path
10
10
  from PySide6 import QtCore, QtWidgets
11
11
  from PySide6.QtCore import QRunnable, Qt, QThreadPool
12
12
 
13
- from excel2moodle import qSignalLogger
13
+ from excel2moodle import mainLogger
14
+ from excel2moodle.core.category import Category
14
15
  from excel2moodle.core.dataStructure import QuestionDB
15
16
  from excel2moodle.extra import equationVerification as eqVerif
17
+ from excel2moodle.logger import LogWindowHandler
16
18
  from excel2moodle.ui import dialogs
17
19
  from excel2moodle.ui.settings import Settings, SettingsKey
18
20
  from excel2moodle.ui.treewidget import CategoryItem, QuestionItem
@@ -22,6 +24,9 @@ from .windowEquationChecker import Ui_EquationChecker
22
24
 
23
25
  logger = logging.getLogger(__name__)
24
26
 
27
+ loggerSignal = LogWindowHandler()
28
+ mainLogger.addHandler(loggerSignal)
29
+
25
30
 
26
31
  class MainWindow(QtWidgets.QMainWindow):
27
32
  def __init__(self, settings: Settings, testDB: QuestionDB) -> None:
@@ -58,17 +63,17 @@ class MainWindow(QtWidgets.QMainWindow):
58
63
  self.threadPool = QThreadPool()
59
64
 
60
65
  def connectEvents(self) -> None:
61
- self.ui.treeWidget.itemClicked.connect(self.onSelectionChanged)
66
+ self.ui.treeWidget.itemSelectionChanged.connect(self.onSelectionChanged)
62
67
  self.ui.checkBoxQuestionListSelectAll.checkStateChanged.connect(
63
68
  self.toggleQuestionSelectionState,
64
69
  )
65
- qSignalLogger.emitter.signal.connect(self.updateLog)
70
+ loggerSignal.emitter.signal.connect(self.updateLog)
66
71
  self.ui.actionEquationChecker.triggered.connect(self.openEqCheckerDlg)
67
72
  self.ui.checkBoxIncludeCategories.checkStateChanged.connect(
68
73
  self.setIncludeCategoriesSetting,
69
74
  )
70
- self.ui.actionParseAll.triggered.connect(self.onParseAll)
71
- # self.testDB.dataChanged.signal.connect(self.refreshList)
75
+ self.ui.actionParseAll.triggered.connect(self.parseSpreadsheetAll)
76
+ self.testDB.signals.categoryQuestionsReady.connect(self.treeRefreshCategory)
72
77
  self.ui.buttonSpreadSheet.clicked.connect(self.onButSpreadsheet)
73
78
  self.ui.buttonTestGen.clicked.connect(self.onButGenTest)
74
79
  self.ui.actionPreviewQ.triggered.connect(self.openPreviewQuestionDlg)
@@ -81,7 +86,7 @@ class MainWindow(QtWidgets.QMainWindow):
81
86
  self.settings.set(SettingsKey.QUESTIONVARIANT, value)
82
87
 
83
88
  @QtCore.Slot()
84
- def onParseAll(self) -> None:
89
+ def parseSpreadsheetAll(self) -> None:
85
90
  """Event triggered by the *Tools/Parse all Questions* Event.
86
91
 
87
92
  It parses all the Questions found in the spreadsheet
@@ -89,7 +94,7 @@ class MainWindow(QtWidgets.QMainWindow):
89
94
  If successful it prints out a list of all exported Questions
90
95
  """
91
96
  self.ui.treeWidget.clear()
92
- process = ParseSpreadsheetThread(self.testDB, self)
97
+ process = ParseAllThread(self.testDB, self)
93
98
  self.threadPool.start(process)
94
99
 
95
100
  @QtCore.Slot(Path)
@@ -101,7 +106,7 @@ class MainWindow(QtWidgets.QMainWindow):
101
106
  svgFolder.resolve()
102
107
  self.settings.set(SettingsKey.PICTUREFOLDER, svgFolder)
103
108
  self.ui.buttonSpreadSheet.setText(str(sheet.name))
104
- self.onParseAll()
109
+ self.parseSpreadsheetAll()
105
110
 
106
111
  def updateLog(self, log) -> None:
107
112
  self.ui.loggerWindow.append(log)
@@ -125,8 +130,6 @@ class MainWindow(QtWidgets.QMainWindow):
125
130
  for q in selection:
126
131
  questions += 1
127
132
  count += q.getQuestion().points
128
-
129
- logger.info("%s questions are selected with %s points", questions, count)
130
133
  self.ui.pointCounter.setValue(count)
131
134
  self.ui.questionCounter.setValue(questions)
132
135
 
@@ -163,25 +166,22 @@ class MainWindow(QtWidgets.QMainWindow):
163
166
  filter=self.tr("Spreadsheet(*.xlsx *.ods)"),
164
167
  selectedFilter=("*.ods"),
165
168
  )
166
- self.excelPath = Path(file[0]).resolve()
167
- self.settings.setSpreadsheet(self.excelPath)
168
- self.setStatus("[OK] Excel Tabelle wurde eingelesen")
169
+ path = Path(file[0]).resolve(strict=True)
170
+ if path.is_file():
171
+ self.excelPath = path
172
+ self.settings.setSpreadsheet(self.excelPath)
173
+ self.setStatus("[OK] Excel Tabelle wurde eingelesen")
174
+ else:
175
+ self.setStatus("[ERROR] keine Tabelle angegeben")
169
176
 
170
- def refreshList(self) -> None:
171
- """Refresh the question overview in the main window.
172
-
173
- Enable the export Button afterwards.
174
- """
175
- logger.info("starting List refresh")
176
- cats = self.testDB.categories
177
- self.ui.treeWidget.clear()
178
- for cat in cats.values():
179
- catItem = CategoryItem(self.ui.treeWidget, cat)
180
- catItem.setFlags(catItem.flags() & ~Qt.ItemIsSelectable)
181
- for q in cat.questions.values():
182
- QuestionItem(catItem, q)
183
- self.setStatus("[OK] Fragen Liste wurde aktualisiert")
184
- self.ui.buttonTestGen.setEnabled(True)
177
+ @QtCore.Slot(Category)
178
+ def treeRefreshCategory(self, cat: Category) -> None:
179
+ """Append Category with its Questions to the treewidget."""
180
+ catItem = CategoryItem(self.ui.treeWidget, cat)
181
+ catItem.setFlags(catItem.flags() & ~Qt.ItemIsSelectable)
182
+ for q in cat.questions.values():
183
+ QuestionItem(catItem, q)
184
+ self.ui.treeWidget.sortItems(0, Qt.SortOrder.AscendingOrder)
185
185
 
186
186
  @QtCore.Slot()
187
187
  def openPreviewQuestionDlg(self) -> None:
@@ -209,7 +209,12 @@ class MainWindow(QtWidgets.QMainWindow):
209
209
  about.exec()
210
210
 
211
211
 
212
- class ParseSpreadsheetThread(QRunnable):
212
+ class ParseAllThread(QRunnable):
213
+ """Parse the whole Spreadsheet.
214
+ Start by reading the spreadsheet asynchron.
215
+ When finished parse all Categories subsequently.
216
+ """
217
+
213
218
  def __init__(self, questionDB: QuestionDB, mainApp: MainWindow) -> None:
214
219
  super().__init__()
215
220
  self.testDB = questionDB
@@ -217,11 +222,11 @@ class ParseSpreadsheetThread(QRunnable):
217
222
 
218
223
  @QtCore.Slot()
219
224
  def run(self) -> None:
220
- self.testDB.readSpreadsheetData(self.mainApp.spreadSheetPath)
225
+ self.testDB.readCategoriesMetadata(self.mainApp.spreadSheetPath)
226
+ self.testDB.asyncInitAllCategories(self.mainApp.spreadSheetPath)
221
227
  self.mainApp.setStatus("[OK] Tabellen wurde eingelesen")
222
- self.testDB.parseAll()
223
- self.mainApp.setStatus("[OK] Alle Fragen wurden erfolgreich geparsed")
224
- self.mainApp.refreshList()
228
+ self.testDB.parseAllQuestions()
229
+ self.mainApp.ui.buttonTestGen.setEnabled(True)
225
230
 
226
231
 
227
232
  class EqCheckerWindow(QtWidgets.QWidget):
@@ -81,21 +81,22 @@ class QuestinoPreviewDialog(QtWidgets.QDialog):
81
81
  def setAnswers(self) -> None:
82
82
  if self.question.qtype == "NFM":
83
83
  for i, ans in enumerate(self.question.answerVariants):
84
- a = ans.find("text").text
85
- text = QtWidgets.QLineEdit(a, self)
84
+ t = ans.find("text").text
85
+ text = QtWidgets.QLineEdit(t, self)
86
86
  self.ui.answersFormLayout.addRow(f"Answer {i + 1}", text)
87
87
 
88
88
  elif self.question.qtype == "NF":
89
89
  ans = self.question.element.find(XMLTags.ANSWER)
90
- a = ans.find("text").text
91
- text = QtWidgets.QLineEdit(a, self)
90
+ t = ans.find("text").text
91
+ text = QtWidgets.QLineEdit(t, self)
92
92
  self.ui.answersFormLayout.addRow("Result", text)
93
93
 
94
94
  elif self.question.qtype == "MC":
95
95
  for i, ans in enumerate(self.question.element.findall(XMLTags.ANSWER)):
96
- a = ans.find("text").text
96
+ pEle = ans.find("text").text
97
+ t = ET.fromstring(pEle).text
97
98
  frac = ans.get("fraction")
98
- text = QtWidgets.QLineEdit(a, self)
99
+ text = QtWidgets.QLineEdit(t, self)
99
100
  self.ui.answersFormLayout.addRow(f"Fraction: {frac}", text)
100
101
 
101
102
 
@@ -107,7 +108,7 @@ class AboutDialog(QtWidgets.QMessageBox):
107
108
  self.setStandardButtons(QtWidgets.QMessageBox.StandardButton.Close)
108
109
 
109
110
  self.aboutMessage: str = f"""
110
- <h1> About {e2mMetadata["name"]}</h1><br>
111
+ <h1> About {e2mMetadata["name"]} v {e2mMetadata["version"]}</h1><br>
111
112
  <p style="text-align:center">
112
113
 
113
114
  <b><a href="{e2mMetadata["homepage"]}">{e2mMetadata["name"]}</a> - {e2mMetadata["description"]}</b>
@@ -3,7 +3,7 @@
3
3
  import logging
4
4
  from enum import StrEnum
5
5
  from pathlib import Path
6
- from typing import Literal, overload
6
+ from typing import ClassVar, Literal, overload
7
7
 
8
8
  from PySide6.QtCore import QSettings, QTimer, Signal
9
9
 
@@ -11,48 +11,76 @@ logger = logging.getLogger(__name__)
11
11
 
12
12
 
13
13
  class SettingsKey(StrEnum):
14
+ """Settings Keys are needed to always acess the correct Value.
15
+
16
+ As the QSettings settings are accesed via strings, which could easily gotten wrong.
17
+ Further, this Enum defines, which type a setting has to be.
18
+ """
19
+
14
20
  def __new__(
15
21
  cls,
16
22
  key: str,
23
+ place: str,
17
24
  typ: type,
18
25
  default: str | float | Path | bool | None,
19
26
  ):
20
27
  """Define new settings class."""
21
28
  obj = str.__new__(cls, key)
22
29
  obj._value_ = key
30
+ obj._place_ = place
23
31
  obj._default_ = default
24
32
  obj._typ_ = typ
25
33
  return obj
26
34
 
35
+ def __init__(
36
+ self, _, place: str, typ: type, default: str | float | Path | None
37
+ ) -> None:
38
+ self._typ_ = typ
39
+ self._place_ = place
40
+ self._default_ = default
41
+ self._full_ = f"{self._place_}/{self._value_}"
42
+
27
43
  @property
28
44
  def default(self) -> str | int | float | Path | bool | None:
29
45
  """Get default value for the key."""
30
46
  return self._default_
31
47
 
48
+ @property
49
+ def place(self) -> str:
50
+ return self._place_
51
+
52
+ @property
53
+ def full(self) -> str:
54
+ return self._full_
55
+
32
56
  def typ(self) -> type:
33
57
  """Get default value for the key."""
34
58
  return self._typ_
35
59
 
36
- QUESTIONVARIANT = "testgen/defaultQuestionVariant", int, 0
37
- INCLUDEINCATS = "testgen/includeCats", bool, False
38
- PARSERNF_TOLERANCE = "parser/nf/tolerance", int, 1
39
- PICTURESUBFOLDER = "core/pictureSubFolder", str, "Abbildungen"
40
- PICTUREFOLDER = "core/pictureFolder", Path, None
41
- SPREADSHEETFOLDER = "core/spreadsheetFolder", Path, None
42
- LOGLEVEL = "core/loglevel", str, "INFO"
43
- LOGFILE = "core/logfile", str, "excel2moodleLogFile.log"
60
+ QUESTIONVARIANT = "defaultQuestionVariant", "testgen", int, 0
61
+ INCLUDEINCATS = "includeCats", "tesgen", bool, False
62
+ PARSERNF_TOLERANCE = "tolerance", "parser/nf", int, 1
63
+ PICTURESUBFOLDER = "imgSubFolder", "core", str, "Abbildungen"
64
+ PICTUREFOLDER = "pictureFolder", "core", Path, None
65
+ SPREADSHEETFOLDER = "spreadsheetFolder", "core", Path, None
66
+ LOGLEVEL = "loglevel", "core", str, "INFO"
67
+ LOGFILE = "logfile", "core", str, "excel2moodleLogFile.log"
68
+ CATEGORIESSHEET = "categoriesDataSheet", "core", str, "Kategorien"
69
+ VERSION = "version", "project", int, 1
70
+ POINTS = "points", "project", float, 1.0
44
71
 
45
72
 
46
73
  class Settings(QSettings):
47
74
  """Settings for Excel2moodle."""
48
75
 
49
76
  shPathChanged = Signal(Path)
77
+ localSettings: ClassVar[dict[str, str | float | Path]] = {}
50
78
 
51
79
  def __init__(self) -> None:
52
80
  """Instantiate the settings."""
53
81
  super().__init__("jbosse3", "excel2moodle")
54
82
  logger.info("Settings are stored under: %s", self.fileName())
55
- if self.contains(SettingsKey.SPREADSHEETFOLDER):
83
+ if self.contains(SettingsKey.SPREADSHEETFOLDER.full):
56
84
  self.sheet = self.get(SettingsKey.SPREADSHEETFOLDER)
57
85
  if self.sheet.is_file():
58
86
  QTimer.singleShot(300, self._emitSpreadsheetChanged)
@@ -63,54 +91,89 @@ class Settings(QSettings):
63
91
  @overload
64
92
  def get(
65
93
  self,
66
- value: Literal[SettingsKey.QUESTIONVARIANT, SettingsKey.PARSERNF_TOLERANCE],
94
+ key: Literal[
95
+ SettingsKey.QUESTIONVARIANT,
96
+ SettingsKey.PARSERNF_TOLERANCE,
97
+ SettingsKey.VERSION,
98
+ SettingsKey.POINTS,
99
+ ],
67
100
  ) -> int: ...
68
101
  @overload
69
- def get(self, value: Literal[SettingsKey.INCLUDEINCATS]) -> bool: ...
102
+ def get(self, key: Literal[SettingsKey.INCLUDEINCATS]) -> bool: ...
70
103
  @overload
71
104
  def get(
72
105
  self,
73
- value: Literal[
74
- SettingsKey.PICTURESUBFOLDER, SettingsKey.LOGLEVEL, SettingsKey.LOGFILE
106
+ key: Literal[
107
+ SettingsKey.PICTURESUBFOLDER,
108
+ SettingsKey.LOGLEVEL,
109
+ SettingsKey.LOGFILE,
110
+ SettingsKey.CATEGORIESSHEET,
75
111
  ],
76
112
  ) -> str: ...
77
113
  @overload
78
114
  def get(
79
115
  self,
80
- value: Literal[SettingsKey.PICTUREFOLDER, SettingsKey.SPREADSHEETFOLDER],
116
+ key: Literal[SettingsKey.PICTUREFOLDER, SettingsKey.SPREADSHEETFOLDER],
81
117
  ) -> Path: ...
82
118
 
83
- def get(self, value: SettingsKey):
119
+ def get(self, key: SettingsKey):
84
120
  """Get the typesafe settings value."""
85
- logger.debug("entering get method. searched typ: %s", value.typ())
86
- if value.typ() is Path:
87
- logger.debug("trying to acess a path object from settings")
88
- path = self.value(value, defaultValue=value.default)
121
+ logger.debug("LocalSettings: %s", self.localSettings)
122
+ if key in self.localSettings:
123
+ val = key.typ()(self.localSettings[key])
124
+ logger.debug("Returning project setting: %s = %s", key, val)
125
+ return val
126
+ if key.typ() is Path:
127
+ path: Path = self.value(key.full, defaultValue=key.default)
89
128
  try:
90
129
  path.resolve(strict=True)
91
130
  except ValueError:
92
131
  logger.warning(
93
- f"The settingsvalue {value} couldn't be fetched with correct typ",
132
+ f"The settingsvalue {key} couldn't be fetched with correct typ",
94
133
  )
95
- return value.default
134
+ return key.default
135
+ logger.debug("Returning path setting: %s = %s", key, path)
96
136
  return path
97
- raw = self.value(value, defaultValue=value.default, type=value.typ())
98
- logger.debug("read a settings Value: %s of type: %s", value, value.typ())
137
+ raw = self.value(key.full, defaultValue=key.default, type=key.typ())
138
+ logger.debug("read a settings Value: %s of type: %s", key, key.typ())
99
139
  try:
100
- return value.typ()(raw)
140
+ logger.debug("Returning global setting: %s = %s", key, raw)
141
+ return key.typ()(raw)
101
142
  except (ValueError, TypeError):
102
143
  logger.warning(
103
- f"The settingsvalue {value} couldn't be fetched with correct typ",
144
+ f"The settingsvalue {key} couldn't be fetched with correct typ",
104
145
  )
105
- return value.default
106
-
107
- def set(self, settingKey: SettingsKey, value: float | bool | Path | str) -> None:
108
- """Set the setting to value."""
109
- # if isinstance(value, SettingsKey.type):
110
- self.setValue(settingKey, value)
111
- logger.info("Saved the Setting %s = %s", settingKey, value)
112
- # else:
113
- # logger.error("trying to save setting with wrong type not possible")
146
+ return key.default
147
+
148
+ def set(
149
+ self,
150
+ key: SettingsKey | str,
151
+ value: float | bool | Path | str,
152
+ local: bool = False,
153
+ ) -> None:
154
+ """Set the setting to value.
155
+
156
+ Parameters
157
+ ----------
158
+ local
159
+ True saves local project specific settings.
160
+ Defaults to False
161
+ The local settings are meant to be set in the first sheet ``settings``
162
+
163
+ """
164
+ if local:
165
+ if key in SettingsKey:
166
+ self.localSettings[key] = value
167
+ logger.info("Saved the project setting %s = %s", key, value)
168
+ else:
169
+ logger.warning("got invalid local Setting %s = %s", key, value)
170
+ return
171
+ if not local and isinstance(key, SettingsKey):
172
+ if not isinstance(value, key.typ()):
173
+ logger.error("trying to save setting with wrong type not possible")
174
+ return
175
+ self.setValue(key.full, value)
176
+ logger.info("Saved the global setting %s = %s", key, value)
114
177
 
115
178
  def setSpreadsheet(self, sheet: Path) -> None:
116
179
  """Save spreadsheet path and emit the changed event."""
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: excel2moodle
3
- Version: 0.3.6
3
+ Version: 0.3.7
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
7
7
  Project-URL: Repository, https://gitlab.com/jbosse3/excel2moodle.git
8
8
  Project-URL: Documentation, https://jbosse3.gitlab.io/excel2moodle
9
- Keywords: moodle,XML,teaching,question,converter
9
+ Keywords: moodle,XML,teaching,question,converter,open educational Ressource
10
10
  Classifier: Programming Language :: Python :: 3
11
11
  Classifier: Operating System :: OS Independent
12
12
  Requires-Python: >=3.10
@@ -0,0 +1,37 @@
1
+ excel2moodle/__init__.py,sha256=SUSyvbQCcfOtTr00HvHpjziNy-flEdWanVFCM9F9yX0,2223
2
+ excel2moodle/__main__.py,sha256=VGOlPEmC4bhNNIt8uuuG80DZ5unUvPjm1PoVf0yH1FA,756
3
+ excel2moodle/logger.py,sha256=WomQ_EAiqZ2oVHNReG4KdfGRMrJl3jC3LIr8cbvOlbY,3084
4
+ excel2moodle/core/__init__.py,sha256=H4Bt6u076RKb6BH5F58nHLQvYPDUoayosM_Onyr9yT0,398
5
+ excel2moodle/core/category.py,sha256=QIl8nh1ryvlVMtNy8hWRfhZZMgGOhQZ3rwDFqf4woa4,1809
6
+ excel2moodle/core/dataStructure.py,sha256=krsoifn6fUDBcbp8CHRAvL8Zfbm1nAXwjoESK_2Jeh8,13052
7
+ excel2moodle/core/etHelpers.py,sha256=i8DAx7YBxrQqzbXFsU-pIvYMPHSRhYci-JvuzY1MzeI,2299
8
+ excel2moodle/core/exceptions.py,sha256=VgbxrnoR9RRnmDYK2rbB_Bv00r7NLWET6FgddPwo3uw,748
9
+ excel2moodle/core/globals.py,sha256=cB5kstn57UYYiUJgLxNnqAGNAKVTolUjHPTt7XIA4G8,3294
10
+ excel2moodle/core/numericMultiQ.py,sha256=vr-gYogu2sf2a_Bhvhnu1ZSZFZXM32MfhJesjTkoOQM,2618
11
+ excel2moodle/core/parser.py,sha256=eVSHUihgW_Zk-mIffGL1kWJnHeSn9J76MfrgOzg0gNg,8991
12
+ excel2moodle/core/question.py,sha256=gF7dbbNgchoBWSti_kWlAHW17REYc5jpdtTjmNl2iU8,7157
13
+ excel2moodle/core/questionWriter.py,sha256=UiWbrtNSiQiYY_x3sF3nz6Ic0b_lL0uWHyeDNDtFvPM,10985
14
+ excel2moodle/core/stringHelpers.py,sha256=P7xr5LM2r7qfKzEPBVUSMMDsiVB6jpFAUuhOhGkOMQY,2284
15
+ excel2moodle/core/validator.py,sha256=-ogarWJ1tNrWg457SZtfDqwc8ToDAxgyLM6GW4rvI7g,4829
16
+ excel2moodle/extra/__init__.py,sha256=PM-id60HD21A3IcGC_fCYFihS8osBGZMIJCcN-ZRsIM,293
17
+ excel2moodle/extra/equationVerification.py,sha256=GLJl1r90d8AAiNy0H2hooZrg3D6aEwNfifYKAe3aGxM,3921
18
+ excel2moodle/question_types/__init__.py,sha256=ZYGg_tYop9c5yUfN8St6FddeaDxbyE2hneKKUFVzcGM,955
19
+ excel2moodle/question_types/mc.py,sha256=N0BREdzg6jcVq1CZMg_G8L4yAU1_1QKeDMaMrfmOBCU,3151
20
+ excel2moodle/question_types/nf.py,sha256=r1qSqYl31yUrmvzUxs2Dkkn3pBmFGN1aO7HQYIoOqkA,835
21
+ excel2moodle/question_types/nfm.py,sha256=gv3LvB6M9UI6U2PlpGerGlDVXzz4TZEudc8IsqdIru0,3301
22
+ excel2moodle/ui/__init__.py,sha256=4EdGtpzwH3rgw4xW9E5x9kdPQYwKbo9rehHRZTNxCrQ,44
23
+ excel2moodle/ui/appUi.py,sha256=sZ92bS1DBgoK3Chg1Nu_FQ2ry3Iu_ITKgpwqt4zLVAk,10582
24
+ excel2moodle/ui/dialogs.py,sha256=HkIe3CgSpBb_shbnNEfNJs82OVm1xUA53dsjDE8wwO0,5395
25
+ excel2moodle/ui/questionPreviewDialog.py,sha256=_rJvz1GM90aNnj3P6SugEezK7JW6m74ZALgkChohWLM,4980
26
+ excel2moodle/ui/settings.py,sha256=XRnCUgYrF9cz_bv91Zj965SJw3vJ0QlS0dgzzGeee-Y,6331
27
+ excel2moodle/ui/treewidget.py,sha256=mTRqmZHMZT3QTTvt5Gw9L-yl4KdhhulkF7O1KBwW-_E,2449
28
+ excel2moodle/ui/variantDialog.py,sha256=snVaF3_YAc7NWjMRg7NzbjL_PzNbOpt4eiqElkE46io,5414
29
+ excel2moodle/ui/windowDoc.py,sha256=IciZpwrLnGzIQV1aCdKQBg6km3oufHGs8havTFzNJyU,1055
30
+ excel2moodle/ui/windowEquationChecker.py,sha256=fLyal3sbJwpthWCAxLB5vbSFOX23JoivoYksNp3mZVY,7925
31
+ excel2moodle/ui/windowMain.py,sha256=FsaApCyWgngnWPb46R8_01z8q4XeWxRFVqewSIt742c,19027
32
+ excel2moodle-0.3.7.dist-info/licenses/LICENSE,sha256=ywQqe6Sitymkf2lV2NRcx_aGsaC-KbSl_EfEsRXmNRw,35135
33
+ excel2moodle-0.3.7.dist-info/METADATA,sha256=RmY9IFvitRlUaI6EugujfhXdZe8IFAsUdpJBmp_Oq-o,2972
34
+ excel2moodle-0.3.7.dist-info/WHEEL,sha256=zaaOINJESkSfm_4HQVc5ssNzHCPXhJm0kEUakpsEHaU,91
35
+ excel2moodle-0.3.7.dist-info/entry_points.txt,sha256=myfMLDThuGgWHMJDPPfILiZqo_7D3fhmDdJGqWOAjPw,60
36
+ excel2moodle-0.3.7.dist-info/top_level.txt,sha256=5V1xRUQ9o7UmOCmNoWCZPAuy5nXp3Qbzyqch8fUGT_c,13
37
+ excel2moodle-0.3.7.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.4.0)
2
+ Generator: setuptools (80.8.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,33 +0,0 @@
1
- excel2moodle/__init__.py,sha256=tJopLP1Oks1wtiFPiW25qKwgJWa-X_C0UNQijM-pRnw,2236
2
- excel2moodle/__main__.py,sha256=gPhAh5GmRw-atxu-3JCdElFBKFw5ZB-G_G3xoPPJQVM,812
3
- excel2moodle/logger.py,sha256=WomQ_EAiqZ2oVHNReG4KdfGRMrJl3jC3LIr8cbvOlbY,3084
4
- excel2moodle/core/__init__.py,sha256=H4Bt6u076RKb6BH5F58nHLQvYPDUoayosM_Onyr9yT0,398
5
- excel2moodle/core/category.py,sha256=VunNsfRUv7O8L3XMe1BB2EFxtfCAJFaRafRYRKF15kE,3006
6
- excel2moodle/core/dataStructure.py,sha256=G-XpkcmP4ZpySvpi9ykvETrP-U69BNtjTR762ZekuBk,6217
7
- excel2moodle/core/etHelpers.py,sha256=i8DAx7YBxrQqzbXFsU-pIvYMPHSRhYci-JvuzY1MzeI,2299
8
- excel2moodle/core/exceptions.py,sha256=VgbxrnoR9RRnmDYK2rbB_Bv00r7NLWET6FgddPwo3uw,748
9
- excel2moodle/core/globals.py,sha256=OGBxkgD19etaMP6sTtsTZ_u8NXbnHYdCQYAu9E3pcgE,3692
10
- excel2moodle/core/numericMultiQ.py,sha256=vr-gYogu2sf2a_Bhvhnu1ZSZFZXM32MfhJesjTkoOQM,2618
11
- excel2moodle/core/parser.py,sha256=jsBMjluc5OICCkrtT4WNVwO26-MaBOTuiOUbQhN2d6g,15232
12
- excel2moodle/core/question.py,sha256=ZubBhS3n7wRFgtcKZ4AoUBSsxLyIM1wsmzsBqbSab-I,6489
13
- excel2moodle/core/questionValidator.py,sha256=a12bPd6hdE5vuj8aoXA0qXVZ0pI98X-uUBzF0oIbzYk,4810
14
- excel2moodle/core/questionWriter.py,sha256=UiWbrtNSiQiYY_x3sF3nz6Ic0b_lL0uWHyeDNDtFvPM,10985
15
- excel2moodle/core/stringHelpers.py,sha256=XZAyXKZcQT_bAQSb9tBQs5tMC5soJf_ZhYFHrDX9ck4,2994
16
- excel2moodle/extra/__init__.py,sha256=PM-id60HD21A3IcGC_fCYFihS8osBGZMIJCcN-ZRsIM,293
17
- excel2moodle/extra/equationVerification.py,sha256=GLJl1r90d8AAiNy0H2hooZrg3D6aEwNfifYKAe3aGxM,3921
18
- excel2moodle/ui/__init__.py,sha256=4EdGtpzwH3rgw4xW9E5x9kdPQYwKbo9rehHRZTNxCrQ,44
19
- excel2moodle/ui/appUi.py,sha256=wbvAPrPFAZaXP7sPQmGt9P4KoKgzhAAwnxdh6gN1Gak,10355
20
- excel2moodle/ui/dialogs.py,sha256=EgAKcydF4clyEweocjmtyCdsQJdQmvY-LVzOlu_cl_8,5320
21
- excel2moodle/ui/questionPreviewDialog.py,sha256=_rJvz1GM90aNnj3P6SugEezK7JW6m74ZALgkChohWLM,4980
22
- excel2moodle/ui/settings.py,sha256=eN5h1YeSYJ84-hDe13ief31Am5y46CxahoRx5Cn8usQ,4360
23
- excel2moodle/ui/treewidget.py,sha256=mTRqmZHMZT3QTTvt5Gw9L-yl4KdhhulkF7O1KBwW-_E,2449
24
- excel2moodle/ui/variantDialog.py,sha256=snVaF3_YAc7NWjMRg7NzbjL_PzNbOpt4eiqElkE46io,5414
25
- excel2moodle/ui/windowDoc.py,sha256=IciZpwrLnGzIQV1aCdKQBg6km3oufHGs8havTFzNJyU,1055
26
- excel2moodle/ui/windowEquationChecker.py,sha256=fLyal3sbJwpthWCAxLB5vbSFOX23JoivoYksNp3mZVY,7925
27
- excel2moodle/ui/windowMain.py,sha256=FsaApCyWgngnWPb46R8_01z8q4XeWxRFVqewSIt742c,19027
28
- excel2moodle-0.3.6.dist-info/licenses/LICENSE,sha256=ywQqe6Sitymkf2lV2NRcx_aGsaC-KbSl_EfEsRXmNRw,35135
29
- excel2moodle-0.3.6.dist-info/METADATA,sha256=O1NBWfHhc6EMKGMaBJRHW9DmPWotmbJKUzN16gv3wsA,2945
30
- excel2moodle-0.3.6.dist-info/WHEEL,sha256=DnLRTWE75wApRYVsjgc6wsVswC54sMSJhAEd4xhDpBk,91
31
- excel2moodle-0.3.6.dist-info/entry_points.txt,sha256=myfMLDThuGgWHMJDPPfILiZqo_7D3fhmDdJGqWOAjPw,60
32
- excel2moodle-0.3.6.dist-info/top_level.txt,sha256=5V1xRUQ9o7UmOCmNoWCZPAuy5nXp3Qbzyqch8fUGT_c,13
33
- excel2moodle-0.3.6.dist-info/RECORD,,