excel2moodle 0.3.2__py3-none-any.whl → 0.3.3__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 CHANGED
@@ -17,12 +17,27 @@ Functionality
17
17
  * Parse Numeric Questions, each into one XML file
18
18
  * create single XML File from a selection of questions
19
19
  """
20
+ from importlib import metadata
20
21
  from importlib.metadata import version
21
22
  try:
22
23
  __version__ = version("excel2moodle")
23
24
  except Exception:
24
25
  __version__ = "unknown"
25
26
 
27
+
28
+ if not __package__ == None:
29
+ meta = metadata.metadata(__package__)
30
+ e2mMetadata:dict = {
31
+ "version": __version__,
32
+ "name": meta['name'],
33
+ "description": meta['summary'],
34
+ "author": meta['author'],
35
+ "license": meta['license-expression'],
36
+ "documentation": "https://jbosse3.gitlab.io/excel2moodle",
37
+ "homepage": meta['project-url'].split()[1],
38
+ "issues": "https://gitlab.com/jbosse3/excel2moodle/issues",
39
+ }
40
+
26
41
  # from excel2moodle.core import klausurGenerator
27
42
  # from excel2moodle.core import numericMultiQ
28
43
  # from excel2moodle.core import questionWriter
@@ -36,7 +51,6 @@ from excel2moodle.ui import settings
36
51
  from PySide6.QtCore import QObject, Signal
37
52
  import logging as logging
38
53
  from logging import config as logConfig
39
- from pathlib import Path
40
54
 
41
55
  loggerConfig = {
42
56
  'version': 1,
@@ -108,6 +122,6 @@ logging.config.dictConfig(config=loggerConfig)
108
122
  qSignalLogger = LogHandler()
109
123
  logger.addHandler(qSignalLogger)
110
124
 
111
- p = Path(__file__).parent
112
- dirProjectRoot = p.parent.resolve()
113
- dirDocumentation = (dirProjectRoot / "docs/_build/html")
125
+
126
+ for k,v in e2mMetadata.items():
127
+ print(f"{k}: \t {v}\n")
excel2moodle/__main__.py CHANGED
@@ -1,21 +1,11 @@
1
1
  """Main Function to make the Package executable"""
2
2
 
3
- from pathlib import Path
4
-
5
3
  from PySide6 import QtWidgets, sys
6
- import xml.etree.ElementTree as xmlET
7
4
 
8
5
  from excel2moodle.core import dataStructure
9
6
  from excel2moodle.ui import appUi as ui
10
7
  from excel2moodle.ui.settings import Settings
11
8
 
12
- import logging
13
-
14
- katOutPath = None
15
- excelFile = None
16
-
17
- logger = logging.getLogger(__name__)
18
-
19
9
 
20
10
  def main()->None:
21
11
  app = QtWidgets.QApplication(sys.argv)
@@ -39,48 +39,19 @@ class Category():
39
39
  return self.NAME == other.NAME
40
40
  return False
41
41
 
42
- def _getQuestions(self)->None:
43
- self.questions:dict[int,Question] = {}
44
- validator = Validator(self)
45
- for q in self.dataframe.columns:
46
- logger.debug(f"Starting to check Validity of {q}")
47
- qdat = self.dataframe[q]
48
- if isinstance(qdat, pd.Series):
49
- validator.setup(qdat, q)
50
- check = False
51
- try:
52
- check = validator.validate()
53
- except InvalidFieldException as e:
54
- logger.error(f"Frage {self.id}{q:02d} ist invalid.", exc_info=e)
55
- if check:
56
- self.questions[q]=validator.question
57
- try:
58
- self.parseQ(self.questions[q])
59
- except QNotParsedException as e:
60
- logger.error(f"Frage {self.questions[q].id} konnte nicht erstellt werden", exc_info=e)
61
- return None
62
-
63
- def parsAll(self, tree: ET.Element = None)->None:
64
- if tree is None:
65
- tree = ET.Element("quiz")
66
- tree.append(self.getCategoryHeader())
67
- for q in self.questions.values():
68
- self.parseQ(q, tree)
69
-
70
- def parseQ(self, q:Question, xmlTree:ET._Element|None=None)->bool:
42
+ def parseQ(self, q:Question, questionData:dict|None=None, xmlTree:ET._Element|None=None)->bool:
71
43
  if q.element is not None:
72
44
  logger.info(f"Question {q.id} is already parsed")
73
45
  return True
74
46
  else:
75
- series = self.dataframe[q.number]
76
47
  if q.qtype == "NF":
77
- parser = NFQuestionParser( q, series)
48
+ parser = NFQuestionParser( q, questionData)
78
49
  logger.debug(f"setup a new NF parser ")
79
50
  elif q.qtype == "MC":
80
- parser = MCQuestionParser( q, series)
51
+ parser = MCQuestionParser( q, questionData)
81
52
  logger.debug(f"setup a new MC parser ")
82
53
  elif q.qtype == "NFM":
83
- parser = NFMQuestionParser( q, series)
54
+ parser = NFMQuestionParser( q, questionData)
84
55
  logger.debug(f"setup a new NFM parser ")
85
56
  else:
86
57
  logger.error(f"ERROR, couldn't setup Parser")
@@ -63,6 +63,7 @@ class QuestionDB():
63
63
  index_col=0)
64
64
  logger.info("Sucessfully read categoriesMetaData")
65
65
  print(self.categoriesMetaData)
66
+ self.categories = {}
66
67
  for sh in excelFile.sheet_names:
67
68
  if sh.startswith("KAT"):
68
69
  n = int(sh[4:])
@@ -98,7 +99,7 @@ class QuestionDB():
98
99
  if check:
99
100
  c.questions[q]=validator.question
100
101
  try:
101
- c.parseQ(c.questions[q])
102
+ c.parseQ(c.questions[q], validator.qdata)
102
103
  except QNotParsedException as e:
103
104
  logger.error(f"Frage {c.questions[q].id} konnte nicht erstellt werden", exc_info=e)
104
105
 
@@ -120,6 +121,7 @@ class QuestionDB():
120
121
  def appendQElements(self,cat:Category, qList:list[Question], tree:ET.Element, includeHeader:bool=True)->None:
121
122
  if includeHeader:
122
123
  tree.append( cat.getCategoryHeader())
124
+ logger.debug(f"Appended a new category item {cat=}")
123
125
  sameVariant = False
124
126
  variant = 1
125
127
  for q in qList:
@@ -133,7 +135,8 @@ class QuestionDB():
133
135
  logger.debug(f"Die Fragen-Variante {variant} wurde gewählt")
134
136
  q.assemble(variant)
135
137
  else: print("skipping this question")
136
- q.assemble
138
+ else:
139
+ q.assemble()
137
140
  tree.append(q.element)
138
141
  else: logger.warning(f"Frage {q} wurde nicht erstellt")
139
142
  return None
@@ -1,12 +1,6 @@
1
-
2
-
3
- from unicodedata import category
4
1
  from asteval import Interpreter
5
2
  import lxml.etree as ET
6
- # import xml.etree.ElementTree as ET
7
- from typing import Union
8
3
  from pathlib import Path
9
- from numpy import isin
10
4
  import pandas as pd
11
5
  import base64 as base64
12
6
  import logging as logging
@@ -20,21 +14,27 @@ from excel2moodle.core import stringHelpers
20
14
  from excel2moodle.core.question import Picture, Question
21
15
  import re as re
22
16
 
17
+ from excel2moodle import settings
18
+
23
19
 
24
20
  logger = logging.getLogger(__name__)
25
- svgFolder = Path("../Fragensammlung/Abbildungen_SVG/")
21
+ f = Path("../Fragensammlung/Abbildungen_SVG/").resolve()
22
+ settings.set("core/pictureFolder", f)
23
+ svgFolder = settings.get("core/pictureFolder", default=f)
24
+ print(svgFolder)
26
25
 
27
26
  class QuestionParser():
28
- def __init__(self, question:Question, dataframe:pd.Series):
27
+ def __init__(self, question:Question, data:dict):
29
28
  self.question:Question = question
30
- self.df = dataframe
29
+ self.rawInput = data
30
+ logger.debug(f"The following Data was provided for the question {self.question.id}:\n {self.rawInput =}")
31
31
  self.genFeedbacks:list[XMLTags] = []
32
32
 
33
33
  def hasPicture(self)->bool:
34
34
  """Creates a ``Picture`` object inside ``question``, if the question needs a pic"""
35
35
 
36
- picKey = self.df.get(DFIndex.PICTURE)
37
- if picKey != 0 and not pd.isna(picKey):
36
+ picKey = self.rawInput[DFIndex.PICTURE]
37
+ if picKey != 0 and picKey != 'nan':
38
38
  if not hasattr(self.question, 'picture'):
39
39
  self.question.picture = Picture(picKey, svgFolder, self.question)
40
40
  if self.question.picture.ready:
@@ -44,31 +44,32 @@ class QuestionParser():
44
44
  def setMainText(self)->None:
45
45
  paragraphs:list[ET._Element]=[TextElements.PLEFT.create()]
46
46
  ET.SubElement(paragraphs[0],"b").text = f"ID {self.question.id}"
47
- text = self.df.get(DFIndex.TEXT)
47
+ text = self.rawInput[DFIndex.TEXT]
48
48
  pcount = 0
49
49
  for t in text:
50
50
  if not pd.isna(t):
51
51
  pcount +=1
52
52
  paragraphs.append(TextElements.PLEFT.create())
53
53
  paragraphs[-1].text = t
54
- self.question.qtextElements = paragraphs
54
+ self.question.qtextParagraphs = paragraphs
55
55
  logger.debug(f"Created main Text {self.question.id} with:{pcount} paragraphs")
56
56
  return None
57
-
57
+
58
58
  def setBPoints(self)->None:
59
59
  """If there bulletPoints are set in the Spreadsheet it creates an unordered List-Element in ``Question.bulletList``"""
60
- if DFIndex.BPOINTS in self.df.index:
61
- bps = self.df.get(DFIndex.BPOINTS)
60
+ if DFIndex.BPOINTS in self.rawInput:
61
+ bps:str = self.rawInput[DFIndex.BPOINTS]
62
62
  try:
63
63
  bulletList = self.formatBulletList(bps)
64
64
  except IndexError as e:
65
65
  raise QNotParsedException(f"konnt Bullet Liste {self.question.id} nicht generieren", self.question.id, exc_info=e)
66
- self.question.bulletList.append(bulletList)
67
- logger.debug(f"appendet Bullet List {bulletList = }")
66
+ logger.debug(f"Generated BPoint List: \n {ET.tostring(bulletList, encoding='unicode')}")
67
+ self.question.bulletList = bulletList
68
68
  return None
69
69
 
70
70
  def formatBulletList(self,bps:str)->ET.Element:
71
- li:list[str] =stringHelpers.stripWhitespace( bps.split(';'))
71
+ logger.debug(f"Formatting the bulletpoint list")
72
+ li:list[str] = stringHelpers.stripWhitespace( bps.split(';'))
72
73
  name = []
73
74
  var = []
74
75
  quant = []
@@ -81,17 +82,23 @@ class QuestionParser():
81
82
  quant.append(sc_split[3])
82
83
  unit.append(sc_split[4])
83
84
  for i in range(0, len(name)):
84
- num = quant[i].split(',')
85
- if len(num)==2:
86
- num_s = f"{str(num[0])},\\!{str(num[1])}~"
87
- else: num_s = f"{str(num[0])},\\!0~"
85
+ if re.fullmatch(r"{\w+}", quant[i]):
86
+ logger.debug(f"Got an variable bulletItem")
87
+ num_s = quant[i]
88
+ else:
89
+ logger.debug(f"Got a normal bulletItem")
90
+ num = quant[i].split(',')
91
+ if len(num)==2:
92
+ num_s = f"{str(num[0])},\\!{str(num[1])}~"
93
+ else: num_s = f"{str(num[0])},\\!0~"
88
94
  bullet = TextElements.LISTITEM.create()
89
95
  bullet.text=(f"{ name[i] }: \\( {var[i]} = {num_s} \\mathrm{{ {unit[i]} }}\\)\n")
90
96
  unorderedList.append(bullet)
91
97
  return unorderedList
92
98
 
93
- def appendToQuestion(self, eleName: str, text:str|DFIndex, txtEle=False, **attribs ):
94
- t = (self.df.get(text) if isinstance(text, DFIndex) else text)
99
+ def appendToTmpEle(self, eleName: str, text:str|DFIndex, txtEle=False, **attribs ):
100
+ """Appends the text to the temporary Element"""
101
+ t = self.rawInput[text] if isinstance(text, DFIndex) else text
95
102
  if txtEle is False:
96
103
  self.tmpEle.append(eth.getElement(eleName, t, **attribs))
97
104
  elif txtEle is True:
@@ -107,7 +114,7 @@ class QuestionParser():
107
114
  for p in parser:
108
115
  try:
109
116
  for k, v in parserSettings[p][key].items():
110
- self.appendToQuestion(k, text=v)
117
+ self.appendToTmpEle(k, text=v)
111
118
  except KeyError as e:
112
119
  msg = f"Invalider Input aus den Einstellungen Parser: {type(p) = }"
113
120
  logger.error(msg, exc_info=e)
@@ -120,15 +127,16 @@ class QuestionParser():
120
127
  Generates an new Question Element stored as ``self.tmpEle:ET.Element``
121
128
  if no Exceptions are raised, ``self.tmpEle`` is passed to ``self.question.element``
122
129
  """
130
+ logger.info(f"Starting to parse {self.question.id}")
123
131
  self.tmpEle = ET.Element(XMLTags.QUESTION, type = self.question.moodleType)
124
132
  # self.tmpEle.set(XMLTags.TYPE, self.question.moodleType)
125
- self.appendToQuestion(XMLTags.NAME, text=DFIndex.NAME, txtEle=True)
126
- self.appendToQuestion(XMLTags.ID, text=self.question.id)
133
+ self.appendToTmpEle(XMLTags.NAME, text=DFIndex.NAME, txtEle=True)
134
+ self.appendToTmpEle(XMLTags.ID, text=self.question.id)
127
135
  if self.hasPicture() :
128
136
  self.tmpEle.append(self.question.picture.element)
129
137
  self.tmpEle.append(ET.Element(XMLTags.QTEXT, format = "html"))
130
- self.appendToQuestion(XMLTags.POINTS, text=str(self.question.points))
131
- self.appendToQuestion(XMLTags.PENALTY, text="0.3333")
138
+ self.appendToTmpEle(XMLTags.POINTS, text=str(self.question.points))
139
+ self.appendToTmpEle(XMLTags.PENALTY, text="0.3333")
132
140
  self.appendFromSettings()
133
141
  for feedb in self.genFeedbacks:
134
142
  self.tmpEle.append(eth.getFeedBEle(feedb))
@@ -186,34 +194,30 @@ class NFQuestionParser(QuestionParser):
186
194
  self.genFeedbacks=[XMLTags.GENFEEDB]
187
195
 
188
196
  def setAnswers(self)->list[ET.Element]:
189
- result = self.df.get(DFIndex.RESULT)
197
+ result = self.rawInput[DFIndex.RESULT]
190
198
  ansEle:list[ET.Element]=[]
191
199
  ansEle.append(self.getNumericAnsElement( result = result ))
192
200
  return ansEle
193
201
 
194
202
  class NFMQuestionParser(QuestionParser):
195
- def __init__(self, question: Question, dataframe: pd.Series):
196
- super().__init__(question, dataframe)
203
+ def __init__(self, *args):
204
+ super().__init__(*args)
197
205
  self.genFeedbacks=[XMLTags.GENFEEDB]
198
206
  self.astEval = Interpreter()
199
207
 
200
208
  def setAnswers(self)->None:
201
- equation = self.df.get(DFIndex.RESULT)
202
- bps = self.df.get(DFIndex.BPOINTS)
209
+ equation = self.rawInput[DFIndex.RESULT]
210
+ bps = str( self.rawInput[DFIndex.BPOINTS] )
203
211
  ansElementsList:list[ET.Element]=[]
204
- varNames:list[str]= self.getVarsList(bps)
205
- varsDict, number = self.getVariablesDict(varNames)
206
- bulletPoints:list[ET.Element] = []
212
+ varNames:list[str]= self._getVarsList(bps)
213
+ self.question.variables, number = self._getVariablesDict(varNames)
207
214
  for n in range(number):
208
- self._setupAstIntprt(varsDict, n)
215
+ self._setupAstIntprt(self.question.variables, n)
209
216
  result = self.astEval(equation)
210
217
  if isinstance(result, float):
211
218
  ansElementsList.append(self.getNumericAnsElement( result = round(result,3) ))
212
- bpli = self.insertVariablesToBPoints(varsDict, bps, n)
213
- bulletPoints.append(self.formatBulletList(bpli))
214
219
  self.question.answerVariants = ansElementsList
215
- self.question.bulletList = bulletPoints
216
- self.setVariants(len(bulletPoints))
220
+ self.setVariants(len(ansElementsList))
217
221
  return None
218
222
 
219
223
  def setVariants(self, number:int):
@@ -225,39 +229,26 @@ class NFMQuestionParser(QuestionParser):
225
229
  self.question.category.maxVariants = number if number <= mvar else mvar
226
230
 
227
231
 
228
-
229
- @staticmethod
230
- def insertVariablesToBPoints(varDict: dict, bulletPoints: str, index: int)-> str:
231
- """
232
- Für jeden Eintrag im varDict, wird im bulletPoints String der Substring "{key}" durch value[index] ersetzt
233
- """
234
- for k, v in varDict.items():
235
- s = r"{" + str(k) + r"}"
236
- matcher = re.compile(s)
237
- bulletPoints = matcher.sub(str(v[index]), bulletPoints)
238
- return bulletPoints
239
-
240
- def _setupAstIntprt(self, var:dict[str, list[str]], index:int)->None:
232
+ def _setupAstIntprt(self, var:dict[str, list[float|int]], index:int)->None:
241
233
  """Ubergibt die Parameter mit entsprechenden Variablen-Namen an den asteval-Interpreter.
242
234
 
243
235
  Dann kann dieser die equation lesen.
244
236
  """
245
- for k,v in var.items():
246
- comma = re.compile(r",")
247
- value = comma.sub(".",v[index])
248
- self.astEval.symtable[k] = float(value)
237
+ for name,value in var.items():
238
+ self.astEval.symtable[name] = value[index]
249
239
  return None
250
240
 
251
- def getVariablesDict(self, keyList: list)-> tuple[dict[str,list[str]],int]:
241
+ def _getVariablesDict(self, keyList: list)-> tuple[dict[str,list[float]],int]:
252
242
  """Liest alle Variablen-Listen deren Name in ``keyList`` ist aus dem DataFrame im Column[index]"""
253
243
  dic:dict = {}
254
244
  num:int = 0
255
245
  for k in keyList:
256
- val = self.df.get(k)
246
+ val = self.rawInput[k]
257
247
  if isinstance(val, str) :
258
- li = val.split(";")
248
+ li = stringHelpers.stripWhitespace(val.split(";"))
259
249
  num = len(li)
260
- dic[str(k)] = li
250
+ vars:list[float] = [ float(i.replace(",",".")) for i in li ]
251
+ dic[str(k)] = vars
261
252
  else:
262
253
  dic[str(k)] = [str(val)]
263
254
  num = 1
@@ -265,7 +256,7 @@ class NFMQuestionParser(QuestionParser):
265
256
  return dic, num
266
257
 
267
258
  @staticmethod
268
- def getVarsList(bps: str|list[str])->list:
259
+ def _getVarsList(bps: str|list[str])->list:
269
260
  """
270
261
  Durchsucht den bulletPoints String nach den Variablen, die als "{var}" gekennzeichnet sind
271
262
  """
@@ -309,10 +300,10 @@ class MCQuestionParser(QuestionParser):
309
300
 
310
301
 
311
302
  def setAnswers(self)->list[ET.Element]:
312
- ansStyle = self.df.get(DFIndex.ANSTYPE)
313
- true = stringHelpers.stripWhitespace(self.df.get(DFIndex.TRUE).split(';'))
303
+ ansStyle = self.rawInput[DFIndex.ANSTYPE]
304
+ true = stringHelpers.stripWhitespace(self.rawInput[DFIndex.TRUE].split(';'))
314
305
  trueAnsList = stringHelpers.texWrapper(true, style=ansStyle)
315
- false = stringHelpers.stripWhitespace(self.df.get(DFIndex.FALSE).split(';'))
306
+ false = stringHelpers.stripWhitespace(self.rawInput[DFIndex.FALSE].split(';'))
316
307
  falseAnsList= stringHelpers.texWrapper(false, style=ansStyle)
317
308
  truefrac = 1/len(trueAnsList)*100
318
309
  falsefrac = 1/len(trueAnsList)*(-100)
@@ -6,6 +6,8 @@ import base64 as base64
6
6
  from excel2moodle.core import category, etHelpers
7
7
  from excel2moodle.core.globals import XMLTags, TextElements, DFIndex, questionTypes, parserSettings
8
8
  from excel2moodle.core.exceptions import QNotParsedException
9
+ from typing import Match
10
+ import re as re
9
11
 
10
12
 
11
13
  logger = logging.getLogger(__name__)
@@ -19,14 +21,15 @@ class Question():
19
21
  self.parent = parent
20
22
  self.qtype: str = qtype
21
23
  self.moodleType = questionTypes[qtype]
22
- self.points = ( points if points is not 0 else self.category.points)
24
+ self.points = ( points if points != 0 else self.category.points)
23
25
  self.element: ET.Element|None=None
24
26
  self.picture:Picture
25
27
  self.id:str
26
- self.qtextElements: list[ET.Element] = []
27
- self.bulletList: list[ET.Element] = []
28
+ self.qtextParagraphs: list[ET.Element] = []
29
+ self.bulletList: ET.Element|None = None
28
30
  self.answerVariants: list[ET.Element] = []
29
31
  self.variants:int|None = None
32
+ self.variables: dict[str, list[float|int]] = {}
30
33
  self.setID()
31
34
  self.standardTags = {
32
35
  "hidden":"false"
@@ -41,17 +44,32 @@ class Question():
41
44
  return "\n".join(li)
42
45
 
43
46
  def assemble(self, variant:int=1)->None:
47
+ textElements:list[ET.Element] = []
48
+ textElements.extend(self.qtextParagraphs)
49
+ logger.debug(f"Starting assembly of { self.id }")
44
50
  if self.element is not None:
45
51
  mainText = self.element.find(XMLTags.QTEXT)
52
+ logger.debug(f"found existing Text in element {mainText = }")
53
+ txtele = mainText.find("text")
54
+ if txtele is not None:
55
+ mainText.remove(txtele)
56
+ logger.debug(f"removed prevously existing questiontext")
46
57
  else: raise QNotParsedException("Cant assamble, if element is none", self.id)
47
- if len(self.bulletList)>0:
48
- self.qtextElements.append(self.bulletList[variant-1])
58
+ if self.variants is not None:
59
+ textElements.append(self.getBPointVariant(variant-1))
60
+ else:
61
+ textElements.append(self.bulletList)
49
62
  if hasattr(self, "picture") and self.picture.ready:
50
- self.qtextElements.append(self.picture.htmlTag)
63
+ textElements.append(self.picture.htmlTag)
51
64
  mainText.append(self.picture.element)
52
- mainText.append(etHelpers.getCdatTxtElement(self.qtextElements))
53
- self.element.insert(3, mainText)
65
+ mainText.append(etHelpers.getCdatTxtElement(textElements))
66
+ # self.element.insert(3, mainText)
67
+ logger.debug(f"inserted MainText to question element")
54
68
  if len( self.answerVariants ) > 0:
69
+ ans = self.element.find(XMLTags.ANSWER)
70
+ if ans is not None:
71
+ self.element.remove(ans)
72
+ logger.debug("removed previous answer element")
55
73
  self.element.insert(5, self.answerVariants[variant-1])
56
74
  return None
57
75
 
@@ -60,6 +78,28 @@ class Question():
60
78
  self.id: str = f"{self.category.id}{self.number:02d}"
61
79
  else: self.id:str = str(id)
62
80
 
81
+ def getBPointVariant(self, variant:int)->ET.Element:
82
+ if self.bulletList is None:
83
+ return None
84
+ varPlaceholder = re.compile(r"{(\w+)}") # matches {a}, {some_var}, etc.
85
+
86
+ def replaceMatch(match: Match[str])->str|int|float:
87
+ key = match.group(1)
88
+ if key in self.variables:
89
+ value = self.variables[key][variant]
90
+ return f"{value}".replace(".",",\\!")
91
+ return match.group(0) # keep original if no match
92
+
93
+ unorderedList = TextElements.ULIST.create()
94
+ for li in self.bulletList:
95
+ listItemText = li.text or ""
96
+ bullet = TextElements.LISTITEM.create()
97
+ bullet.text = varPlaceholder.sub(replaceMatch, listItemText)
98
+ logger.debug(f"Inserted Variables into List: {bullet}")
99
+ unorderedList.append(bullet)
100
+ return unorderedList
101
+
102
+
63
103
  class Picture():
64
104
  def __init__(self, picKey:str, imgFolder:Path, question:Question):
65
105
  self.pic = picKey
@@ -17,6 +17,8 @@ from excel2moodle.core.exceptions import InvalidFieldException, NanException
17
17
  import pandas as pd
18
18
  import logging
19
19
 
20
+ logger = logging.getLogger(__name__)
21
+
20
22
 
21
23
  class Validator():
22
24
  def __init__(self, category) -> None:
@@ -76,8 +78,24 @@ class Validator():
76
78
  if missing is not None:
77
79
  raise InvalidFieldException(msg, id, missing)
78
80
  self._getQuestion()
81
+ self._getData()
79
82
  return True
80
83
 
84
+ def _getData(self)->None:
85
+ self.qdata:dict[str, str|float|int|list]={}
86
+ for idx, val in self.df.items():
87
+ if not isinstance(idx, str):
88
+ logger.debug(f"Got a non String key in the spreadsheet, skipping it")
89
+ continue
90
+ if idx in self.qdata:
91
+ if isinstance(self.qdata[idx], list):
92
+ self.qdata[idx].append(val)
93
+ else:
94
+ existing = self.qdata[idx]
95
+ self.qdata[idx] = [existing, val]
96
+ else:
97
+ self.qdata[idx]=val
98
+
81
99
  def _mandatory(self)->tuple[bool,DFIndex|None]:
82
100
  """detects if all keys of mandatory are filled with values"""
83
101
  checker = pd.Series.notna(self.df)
@@ -8,9 +8,16 @@ import base64 as base64
8
8
  def stripWhitespace(stringList):
9
9
  stripped = []
10
10
  for i in stringList:
11
- stripped.append(i.strip())
11
+ s = i.strip()
12
+ if s:
13
+ stripped.append(s)
12
14
  return stripped
13
15
 
16
+ def stringToFloat(string:str)->float:
17
+ string.replace(",",".")
18
+ return float(string)
19
+
20
+
14
21
  def get_bullet_string(s):
15
22
  """Formatiert die Angaben zum Statischen System hübsch"""
16
23
  split = s.split(';')
excel2moodle/ui/appUi.py CHANGED
@@ -8,6 +8,7 @@ from PySide6 import QtCore
8
8
  from PySide6.QtCore import Qt
9
9
  from PySide6 import QtWidgets
10
10
  from pathlib import Path
11
+ from excel2moodle import e2mMetadata
11
12
  from excel2moodle.ui.windowMain import Ui_MoodleTestGenerator
12
13
  from .windowEquationChecker import Ui_EquationChecker
13
14
  from excel2moodle.ui import windowEquationChecker, dialogs
@@ -16,11 +17,8 @@ from excel2moodle.ui.treewidget import QuestionItem, CategoryItem
16
17
  from excel2moodle.extra import equationVerification as eqVerif
17
18
  from excel2moodle import qSignalLogger
18
19
  from excel2moodle.ui.settings import Settings
19
- from excel2moodle.ui.windowDoc import DocumentationWindow
20
20
  import logging as logging
21
21
 
22
- from excel2moodle import dirDocumentation
23
-
24
22
 
25
23
  logger = logging.getLogger(__name__)
26
24
 
@@ -29,25 +27,22 @@ logger = logging.getLogger(__name__)
29
27
  class MainWindow(QtWidgets.QMainWindow):
30
28
  def __init__(self, settings:Settings, testDB:QuestionDB)->None:
31
29
  super().__init__()
32
- # self.questionGenerator = questionGenerator
33
- # self.readSettings()
34
30
  self.settings = settings
35
31
  self.excelPath: Path|None = None
36
- if self.excelPath is not None:
37
- self.ui.buttonSpreadSheet.setText(self.excelPath.name)
38
32
  self.mainPath = (self.excelPath.parent if self.excelPath is not None else None)
39
33
  self.exportFile = Path()
40
34
  self.testDB = testDB
41
35
  self.ui = Ui_MoodleTestGenerator()
42
36
  self.ui.setupUi(self)
43
37
 
44
- # self.ui.buttonRefresh.clicked.connect(lambda: self.refreshList(self.test))
45
38
  self.ui.treeWidget.setSelectionMode(QtWidgets.QAbstractItemView.MultiSelection)
46
39
  self.ui.treeWidget.header().setSectionResizeMode(QtWidgets.QHeaderView.ResizeToContents)
40
+ self.ui.checkBoxIncludeCategories.setChecked(self.settings.value("testGen/includeCats", type=bool))
47
41
 
48
42
  self.ui.retranslateUi(self)
49
43
  logger.info(f"Settings are stored under: {self.settings.fileName()}")
50
44
  self.ui.pointCounter.setReadOnly(True)
45
+ self.ui.questionCounter.setReadOnly(True)
51
46
  self.setStatus("Wählen Sie bitte eine Excel Tabelle und einen Export Ordner für die Fragen aus")
52
47
  try:
53
48
  self.resize(self.settings.value("windowSize"))
@@ -68,7 +63,7 @@ class MainWindow(QtWidgets.QMainWindow):
68
63
  self.ui.buttonSpreadSheet.clicked.connect(self.onButSpreadsheet)
69
64
  self.ui.buttonTestGen.clicked.connect(self.onButGenTest)
70
65
  self.ui.actionPreviewQ.triggered.connect(self.previewQ)
71
- self.ui.actionDocumentation.triggered.connect(self.onOpenDocumentation)
66
+ self.ui.actionAbout.triggered.connect(self.onAbout)
72
67
  self.settings.shPathChanged.connect(self.onSheetPathChanged)
73
68
 
74
69
 
@@ -77,6 +72,7 @@ class MainWindow(QtWidgets.QMainWindow):
77
72
  logger.debug("Slot, new Spreadsheet triggered")
78
73
  self.spreadSheetPath = sheet
79
74
  self.mainPath = sheet.parent
75
+ self.ui.buttonSpreadSheet.setText(str(sheet.name))
80
76
 
81
77
  def updateLog(self,log)->None:
82
78
  self.ui.loggerWindow.append(log)
@@ -84,14 +80,41 @@ class MainWindow(QtWidgets.QMainWindow):
84
80
  def setIncludeCategoriesSetting(self):
85
81
  if self.ui.checkBoxIncludeCategories.isChecked():
86
82
  self.settings.set("testGen/includeCats", True)
83
+ logger.debug("set includeCats to True")
87
84
  else:
88
85
  self.settings.set("testGen/includeCats", False)
86
+ logger.debug("set includeCats to False")
89
87
 
90
- @QtCore.Slot()
91
- def onOpenDocumentation(self):
92
- documentationWindow = DocumentationWindow(dirDocumentation, self)
93
- documentationWindow.show()
94
88
 
89
+ @QtCore.Slot()
90
+ def onAbout(self):
91
+ aboutMessage: str = f"""
92
+ <h1> About {e2mMetadata['name']}</h1><br>
93
+ <p style="text-align:center">
94
+
95
+ <b><a href="{e2mMetadata['homepage']}">{e2mMetadata['name']}</a> - {e2mMetadata['description']}</b>
96
+ </p>
97
+ <p style="text-align:center">
98
+ The documentation can be found under <b><a href="{e2mMetadata['documentation']}">{e2mMetadata['documentation']}</a></b>
99
+ </br>
100
+ </br>
101
+ If you encounter any issues please report them under the <a href="{e2mMetadata['issues']}"> repositories issues page</a>
102
+ </br>
103
+ </p>
104
+ <p style="text-align:center">
105
+ This project is maintained by {e2mMetadata['author']}.
106
+ <br>
107
+ Development takes place at <a href="{e2mMetadata['homepage']}">{e2mMetadata['homepage']}</a>
108
+ Contributions are very welcome
109
+ <br>
110
+ </p>
111
+ <p style="text-align:center">
112
+ <i>This project is published under {e2mMetadata['license']}, you are welcome, to share, modify and reuse the code.</i>
113
+ </p>
114
+ """
115
+ QtWidgets.QMessageBox.information(self,
116
+ f"About {e2mMetadata['name']}",
117
+ aboutMessage)
95
118
 
96
119
  def closeEvent(self, event):
97
120
  self.settings.setValue("windowSize", self.size())
@@ -145,8 +168,6 @@ class MainWindow(QtWidgets.QMainWindow):
145
168
  selectedFilter=("*.ods"))
146
169
  self.excelPath = Path(file[0]).resolve()
147
170
  self.settings.setSpreadsheet(self.excelPath)
148
- self.mainPath = self.excelPath.parent
149
- self.ui.buttonSpreadSheet.setText(self.excelPath.name)
150
171
  logger.debug(f'Saved Spreadsheet Path: {self.excelPath}\n')
151
172
  self.setStatus("[OK] Excel Tabelle wurde eingelesen")
152
173
  return None
@@ -65,9 +65,9 @@ class QuestinoPreviewDialog(QtWidgets.QDialog):
65
65
 
66
66
  def setText(self)->None:
67
67
  t = []
68
- for text in self.question.qtextElements:
68
+ for text in self.question.qtextParagraphs:
69
69
  t.append(ET.tostring(text, encoding='unicode'))
70
-
70
+ t.append(ET.tostring(self.question.bulletList, encoding='unicode'))
71
71
  self.ui.questionText.setText("\n".join(t))
72
72
 
73
73
  def setAnswers(self)->None:
@@ -5,8 +5,8 @@ class Settings(QSettings):
5
5
  shPathChanged = Signal(Path)
6
6
  def __init__(self):
7
7
  super().__init__("jbosse3", "excel2moodle")
8
- if self.contains("spreadsheet"):
9
- self.sheet = self.value("spreadsheet")
8
+ if self.contains("core/spreadsheet"):
9
+ self.sheet = self.value("core/spreadsheet")
10
10
  try:
11
11
  self.sheet.resolve(strict=True)
12
12
  if self.sheet.is_file():
@@ -20,7 +20,8 @@ class Settings(QSettings):
20
20
 
21
21
 
22
22
  def get(self, value, default=None):
23
- self.value(value, default)
23
+ return self.value(value, default)
24
+
24
25
 
25
26
  def set(self, setting, value):
26
27
  self.setValue(setting, value)
@@ -28,7 +29,7 @@ class Settings(QSettings):
28
29
  def setSpreadsheet(self, sheet:Path)->None:
29
30
  if isinstance(sheet, Path):
30
31
  self.sheet = sheet.resolve(strict=True)
31
- self.setValue("spreadsheet", self.sheet)
32
+ self.setValue("core/spreadsheet", self.sheet)
32
33
  self.shPathChanged.emit(sheet)
33
34
  return None
34
35
 
@@ -1,11 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: excel2moodle
3
- Version: 0.3.2
4
- Summary: A Package for Converting Questions input into a spreadsheet, to valid moodle-xml
3
+ Version: 0.3.3
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
- Project-URL: Homepage, https://gitlab.com/jbosse3/excel2moodle
8
- Project-URL: documentation, https://jbosse3.gitlab.io/excel2moodle
7
+ Project-URL: Repository, https://gitlab.com/jbosse3/excel2moodle.git
8
+ Project-URL: Documentation, https://jbosse3.gitlab.io/excel2moodle
9
9
  Keywords: moodle,XML,teaching,question,converter
10
10
  Classifier: Programming Language :: Python :: 3
11
11
  Classifier: Operating System :: OS Independent
@@ -1,31 +1,31 @@
1
- excel2moodle/__init__.py,sha256=nb37g9Zga4KqTA5r-GFei9_XCunoPESxww9BL8zzMBI,3841
2
- excel2moodle/__main__.py,sha256=yLls1C3w070wtrrRl3_HXs5TNaO8l0yRDGtIAcHmeDk,695
1
+ excel2moodle/__init__.py,sha256=dsK3KrorPt06FUp18_dw2m4p9P8E7rFf9I0BR-4I28k,4263
2
+ excel2moodle/__main__.py,sha256=SsulFcOSlRBF2tLTUIdFgB0EJdSLGBIQ_HO53S4Sih8,541
3
3
  excel2moodle/core/__init__.py,sha256=E7smxf7ESHlucvho4Gb5oaTHA7IsOv9J-UDIsoKwp84,399
4
- excel2moodle/core/category.py,sha256=6U10HgQQVi0BYO28XrsLbYFqr0GbxEN9U2-fPDTM1Ng,4149
5
- excel2moodle/core/dataStructure.py,sha256=8cLqGZLhHRscohrstvsYARWvKekGAfdoMV1zWuUpHCA,6200
4
+ excel2moodle/core/category.py,sha256=rZsQW5-Ud182xCrS4R8B0vrPMrI-azwtfzlTdy89SR4,2982
5
+ excel2moodle/core/dataStructure.py,sha256=h82r4wO2PhhHA4EqROjfkqWGUpg6UKQhkyrdjdisLxg,6343
6
6
  excel2moodle/core/etHelpers.py,sha256=_fd-fGMVNWt1l8FY7xcA2uXOBahxtq8ggYxRYm4SwdA,2354
7
7
  excel2moodle/core/exceptions.py,sha256=3OLVHMCBgETiOxSsirlsCPvlMN5UgEQcMkRDkCfrUs4,675
8
8
  excel2moodle/core/globals.py,sha256=9V9-KBSsHMdsRZWg2wGrvjAd6dc3XZG4pNPnun0AHGE,3595
9
9
  excel2moodle/core/numericMultiQ.py,sha256=InBrn-tsCCYwDb78sGR3FyFxVeN0GfIg63JGtr_tqg4,2751
10
- excel2moodle/core/parser.py,sha256=ruSFHKjwpXggaETrtsUBdZK1BENCe2wppmOLdpKrAAs,13455
11
- excel2moodle/core/question.py,sha256=4mbzy6ZodvdM5QfBZltR950BmMdXqSBe9V7HBLZMewM,4198
12
- excel2moodle/core/questionValidator.py,sha256=6leCAvMFOieiVJtQEYx4iihiaKuuSBlCpBkpqbzKa3M,4139
10
+ excel2moodle/core/parser.py,sha256=0bLP_bkTFu3URw08sn1zJCvA3YCKxDAC4a6I14koHho,13408
11
+ excel2moodle/core/question.py,sha256=zzdL_7BdWs0wUVl8-06gLkKu0Is3n0zU-2ZkaYhU504,5913
12
+ excel2moodle/core/questionValidator.py,sha256=taOrrR6pd5mN7SGpET9eLOvb1v5NWvg_VgMj8opQlZ4,4791
13
13
  excel2moodle/core/questionWriter.py,sha256=kyUTrnjWrLEWdez5_7FPNJ0Cs_fF5d16oOQiVvHv3zA,10242
14
- excel2moodle/core/stringHelpers.py,sha256=c92YYFluZ6-NIM7K1k0SzN_zB82hh3huNZMH0NnlDLg,3009
14
+ excel2moodle/core/stringHelpers.py,sha256=9BqR7yx2bL8WknVMPuNlmWz05bgi4qWjFYHsgaftpZw,3141
15
15
  excel2moodle/extra/__init__.py,sha256=e70OmuW3j7N4ErIIHlK4vkmCKcpRbitJCdJvzjego8c,339
16
16
  excel2moodle/extra/equationVerification.py,sha256=FOvVBQZ357fbA5nYkHjvvWWVP3pQpo4RK3H7vru9v3A,4278
17
17
  excel2moodle/ui/__init__.py,sha256=4EdGtpzwH3rgw4xW9E5x9kdPQYwKbo9rehHRZTNxCrQ,44
18
- excel2moodle/ui/appUi.py,sha256=gWLJ3vU-KcHAz0HQXrOk2hGi3LD_8SSC5aPVEpayMEA,9518
19
- excel2moodle/ui/dialogs.py,sha256=C58PzPLfkzglsRl-x8Y23TC08_JG03JgFCVFfEi_od8,2961
18
+ excel2moodle/ui/appUi.py,sha256=TEqslEDExDOyYwCh7czg3YZggPdZewFiImbpzACLYLU,10533
19
+ excel2moodle/ui/dialogs.py,sha256=5dnzRN9cwaxjJsxA7I9v1imH4k6XVzzievYi3PR32XI,3038
20
20
  excel2moodle/ui/questionPreviewDialog.py,sha256=_rJvz1GM90aNnj3P6SugEezK7JW6m74ZALgkChohWLM,4980
21
- excel2moodle/ui/settings.py,sha256=Zz4zxdGC55Ma6pB1HWdPdiAsN0fnqrPfgOFgSkVnKNA,1092
21
+ excel2moodle/ui/settings.py,sha256=PB0uBMCd8DIRSFfvUAPs5hK4UEcb0y7dG9ZJ--kOvuQ,1115
22
22
  excel2moodle/ui/treewidget.py,sha256=xiYWhJeJ9o9Iubtr44HcyJas9v4m8aBsHNdokQAHi80,2357
23
23
  excel2moodle/ui/variantDialog.py,sha256=snVaF3_YAc7NWjMRg7NzbjL_PzNbOpt4eiqElkE46io,5414
24
24
  excel2moodle/ui/windowDoc.py,sha256=IciZpwrLnGzIQV1aCdKQBg6km3oufHGs8havTFzNJyU,1055
25
25
  excel2moodle/ui/windowEquationChecker.py,sha256=fLyal3sbJwpthWCAxLB5vbSFOX23JoivoYksNp3mZVY,7925
26
26
  excel2moodle/ui/windowMain.py,sha256=sB1ahkAwxFxO3EYP3X_MuB6ohgXwK5NUQHWeFo4eqrI,21062
27
- excel2moodle-0.3.2.dist-info/licenses/LICENSE,sha256=ywQqe6Sitymkf2lV2NRcx_aGsaC-KbSl_EfEsRXmNRw,35135
28
- excel2moodle-0.3.2.dist-info/METADATA,sha256=f_TnbqQEmg8wy6WuvMuYOTd8u_-h5qw4aKADlcvtzsM,2904
29
- excel2moodle-0.3.2.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
30
- excel2moodle-0.3.2.dist-info/top_level.txt,sha256=5V1xRUQ9o7UmOCmNoWCZPAuy5nXp3Qbzyqch8fUGT_c,13
31
- excel2moodle-0.3.2.dist-info/RECORD,,
27
+ excel2moodle-0.3.3.dist-info/licenses/LICENSE,sha256=ywQqe6Sitymkf2lV2NRcx_aGsaC-KbSl_EfEsRXmNRw,35135
28
+ excel2moodle-0.3.3.dist-info/METADATA,sha256=i7lC9k3j8ErKfWNnPAngdrnDAWIqC5V6egSB5uWivW8,2904
29
+ excel2moodle-0.3.3.dist-info/WHEEL,sha256=pxyMxgL8-pra_rKaQ4drOZAegBVuX-G_4nRHjjgWbmo,91
30
+ excel2moodle-0.3.3.dist-info/top_level.txt,sha256=5V1xRUQ9o7UmOCmNoWCZPAuy5nXp3Qbzyqch8fUGT_c,13
31
+ excel2moodle-0.3.3.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (78.1.0)
2
+ Generator: setuptools (79.0.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5