excel2moodle 0.3.1__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 +113 -0
- excel2moodle/__main__.py +31 -0
- excel2moodle/core/__init__.py +13 -0
- excel2moodle/core/category.py +108 -0
- excel2moodle/core/dataStructure.py +140 -0
- excel2moodle/core/etHelpers.py +67 -0
- excel2moodle/core/exceptions.py +20 -0
- excel2moodle/core/globals.py +128 -0
- excel2moodle/core/numericMultiQ.py +76 -0
- excel2moodle/core/parser.py +322 -0
- excel2moodle/core/question.py +106 -0
- excel2moodle/core/questionValidator.py +124 -0
- excel2moodle/core/questionWriter.py +174 -0
- excel2moodle/core/stringHelpers.py +94 -0
- excel2moodle/extra/__init__.py +9 -0
- excel2moodle/extra/equationVerification.py +124 -0
- excel2moodle/ui/__init__.py +1 -0
- excel2moodle/ui/appUi.py +243 -0
- excel2moodle/ui/dialogs.py +80 -0
- excel2moodle/ui/questionPreviewDialog.py +115 -0
- excel2moodle/ui/settings.py +34 -0
- excel2moodle/ui/treewidget.py +65 -0
- excel2moodle/ui/variantDialog.py +132 -0
- excel2moodle/ui/windowDoc.py +35 -0
- excel2moodle/ui/windowEquationChecker.py +187 -0
- excel2moodle-0.3.1.dist-info/METADATA +63 -0
- excel2moodle-0.3.1.dist-info/RECORD +30 -0
- excel2moodle-0.3.1.dist-info/WHEEL +5 -0
- excel2moodle-0.3.1.dist-info/licenses/LICENSE +674 -0
- excel2moodle-0.3.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,174 @@
|
|
1
|
+
"""This Module holds the related Functions for writing the Questions to an xml-File
|
2
|
+
|
3
|
+
It is planned to rework those Functions, because they're not quite elegant.
|
4
|
+
"""
|
5
|
+
|
6
|
+
from .stringHelpers import get_bullet_string
|
7
|
+
|
8
|
+
def write_question_MC(save_dir, ID, name, s_1, s_2, s_3, points_avail, ans_type, true_ans, false_ans, pic):
|
9
|
+
"""Funktion schreibt MC-Frage auf Grundlage der übergebenen strings nach Pfad f_path"""
|
10
|
+
perc = ['100', '50', '33.33333', '25', '20', '16.66667', '14.28571', '12.5', '11.11111', '10']
|
11
|
+
num_true = int(len(true_ans))
|
12
|
+
perc_true = perc[num_true-1]
|
13
|
+
num_false = int(len(false_ans))
|
14
|
+
perc_false = '-'+perc_true
|
15
|
+
q_name = ID + '_' + name
|
16
|
+
f_path = ( save_dir / q_name ).with_suffix('.xml')
|
17
|
+
|
18
|
+
with open(f_path,'w', encoding='utf-8') as f:
|
19
|
+
# Text schreiben
|
20
|
+
f.write('<?xml version="1.0" encoding="UTF-8"?>\n')
|
21
|
+
f.write('<quiz>\n')
|
22
|
+
f.write('<question type="multichoice">\n')
|
23
|
+
f.write('<name>\n')
|
24
|
+
f.write('<text>' + q_name + '</text>\n')
|
25
|
+
f.write('</name>\n')
|
26
|
+
f.write('<questiontext format="html">\n')
|
27
|
+
if pic != 0:
|
28
|
+
f.write('<text><![CDATA[<p dir="ltr" style="text-align: left;"> <b>ID ' +str(ID)+ '</b> <br></p>'\
|
29
|
+
'<p dir="ltr" style="text-align: left;">' + s_1 + '<br></p>'\
|
30
|
+
'<p dir="ltr" style="text-align: left;">' + s_2 + '<br></p>'\
|
31
|
+
'<p dir="ltr" style="text-align: left;">' + s_3 + '<br><br></p>'\
|
32
|
+
'<br><img src="@@PLUGINFILE@@/'+ str(ID) + '.svg" alt="Bild" width="500"><br>'\
|
33
|
+
'<br></p>]]></text>\n')
|
34
|
+
f.write(str(pic))
|
35
|
+
else:
|
36
|
+
f.write('<text><![CDATA[<p dir="ltr" style="text-align: left;"> <b>ID '+q_name+' </b> <br></p>'\
|
37
|
+
'<p dir="ltr" style="text-align: left;">' + s_1 + '<br></p>'\
|
38
|
+
'<p dir="ltr" style="text-align: left;">' + s_2 + '<br></p>'\
|
39
|
+
'<p dir="ltr" style="text-align: left;">' + s_3 + '<br></p>'\
|
40
|
+
'<br></p>]]></text>\n')
|
41
|
+
|
42
|
+
f.write('</questiontext>\n')
|
43
|
+
f.write('<generalfeedback format="html">\n')
|
44
|
+
f.write('<text></text>\n')
|
45
|
+
f.write('</generalfeedback>\n')
|
46
|
+
f.write('<defaultgrade>' +str(float(points_avail)) +'</defaultgrade>\n')
|
47
|
+
f.write('<penalty>0.3333333</penalty>\n')
|
48
|
+
f.write('<hidden>0</hidden>\n')
|
49
|
+
f.write('<idnumber>' + ID +'</idnumber>\n')
|
50
|
+
f.write('<single>false</single>\n')
|
51
|
+
f.write('<shuffleanswers>true</shuffleanswers>\n')
|
52
|
+
f.write('<answernumbering>abc</answernumbering>\n')
|
53
|
+
f.write('<showstandardinstruction>0</showstandardinstruction>\n')
|
54
|
+
f.write('<correctfeedback format="html">\n')
|
55
|
+
f.write('<text>Die Frage wurde richtig beantwortet.</text>\n')
|
56
|
+
f.write('</correctfeedback>\n')
|
57
|
+
f.write('<partiallycorrectfeedback format="html">\n')
|
58
|
+
f.write('<text>Die Frage wurde teilweise richtig beantwortet.</text>\n')
|
59
|
+
f.write('</partiallycorrectfeedback>\n')
|
60
|
+
f.write('<incorrectfeedback format="html">\n')
|
61
|
+
f.write('<text>Die Frage wurde falsch beantwortet.</text>\n')
|
62
|
+
f.write('</incorrectfeedback>\n')
|
63
|
+
f.write('<shownumcorrect/>\n')
|
64
|
+
|
65
|
+
# Alle richtigen Antworten
|
66
|
+
for i in range (0, num_true):
|
67
|
+
|
68
|
+
if ans_type=='unit':
|
69
|
+
f.write('<answer fraction="' + perc_true +'" format="html">\n')
|
70
|
+
f.write('<text><![CDATA[<p dir="ltr" style="text-align: left;">\\(\\mathrm{' + true_ans[i] + '}\\)<br></p>]]></text>\n')
|
71
|
+
f.write('<feedback format="html">\n')
|
72
|
+
f.write('<text><![CDATA[<p dir="ltr" style="text-align: left;"><span class="" style="color: rgb(152, 202, 62);">richtig</span><br></p>]]></text>\n')
|
73
|
+
f.write('</feedback>\n')
|
74
|
+
f.write('</answer>\n')
|
75
|
+
|
76
|
+
elif ans_type=='math':
|
77
|
+
f.write('<answer fraction="' + perc_true +'" format="html">\n')
|
78
|
+
f.write('<text><![CDATA[<p dir="ltr" style="text-align: left;">\\(' + true_ans[i] + '\\)<br></p>]]></text>\n')
|
79
|
+
f.write('<feedback format="html">\n')
|
80
|
+
f.write('<text><![CDATA[<p dir="ltr" style="text-align: left;"><span class="" style="color: rgb(152, 202, 62);">richtig</span><br></p>]]></text>\n')
|
81
|
+
f.write('</feedback>\n')
|
82
|
+
f.write('</answer>\n')
|
83
|
+
|
84
|
+
|
85
|
+
elif ans_type=='text':
|
86
|
+
f.write('<answer fraction="' + perc_true +'" format="html">\n')
|
87
|
+
f.write('<text><![CDATA[<p dir="ltr" style="text-align: left;">' + true_ans[i] + '<br></p>]]></text>\n')
|
88
|
+
f.write('<feedback format="html">\n')
|
89
|
+
f.write('<text><![CDATA[<p dir="ltr" style="text-align: left;"><span class="" style="color: rgb(152, 202, 62);">richtig</span><br></p>]]></text>\n')
|
90
|
+
f.write('</feedback>\n')
|
91
|
+
f.write('</answer>\n')
|
92
|
+
|
93
|
+
# Alle falschen Antworten
|
94
|
+
for i in range (0, num_false):
|
95
|
+
if ans_type=='unit':
|
96
|
+
f.write('<answer fraction="' + perc_false +'" format="html">\n')
|
97
|
+
f.write('<text><![CDATA[<p dir="ltr" style="text-align: left;">\\(\\mathrm{' + false_ans[i] + '}\\)<br></p>]]></text>\n')
|
98
|
+
f.write('<feedback format="html">\n')
|
99
|
+
f.write('<text><![CDATA[<p dir="ltr" style="text-align: left;"><span class="" style="color: rgb(239, 69, 64);">falsch</span><br></p>]]></text>\n')
|
100
|
+
f.write('</feedback>\n')
|
101
|
+
f.write('</answer>\n')
|
102
|
+
|
103
|
+
elif ans_type=='math':
|
104
|
+
f.write('<answer fraction="' + perc_false +'" format="html">\n')
|
105
|
+
f.write('<text><![CDATA[<p dir="ltr" style="text-align: left;">\\(' + false_ans[i] + '\\)<br></p>]]></text>\n')
|
106
|
+
f.write('<feedback format="html">\n')
|
107
|
+
f.write('<text><![CDATA[<p dir="ltr" style="text-align: left;"><span class="" style="color: rgb(239, 69, 64);">falsch</span><br></p>]]></text>\n')
|
108
|
+
f.write('</feedback>\n')
|
109
|
+
f.write('</answer>\n')
|
110
|
+
|
111
|
+
elif ans_type=='text':
|
112
|
+
f.write('<answer fraction="' + perc_false +'" format="html">\n')
|
113
|
+
f.write('<text><![CDATA[<p dir="ltr" style="text-align: left;">' + false_ans[i] + '<br></p>]]></text>\n')
|
114
|
+
f.write('<feedback format="html">\n')
|
115
|
+
f.write('<text><![CDATA[<p dir="ltr" style="text-align: left;"><span class="" style="color: rgb(239, 69, 64);">falsch</span><br></p>]]></text>\n')
|
116
|
+
f.write('</feedback>\n')
|
117
|
+
f.write('</answer>\n')
|
118
|
+
|
119
|
+
f.write('</question>\n')
|
120
|
+
f.write('</quiz>\n')
|
121
|
+
return None
|
122
|
+
|
123
|
+
def write_question_NF(save_dir, ID, name, s_1, s_2, s_3, b_str, points_avail, result, pic, tol_abs, picID = None):
|
124
|
+
"""Funktion schreibt NF-Frage auf Grundlage der übergebenen strings nach Pfad f_path"""
|
125
|
+
if picID == None:
|
126
|
+
picID = ID
|
127
|
+
q_name = ID + '_' + name
|
128
|
+
f_path = (save_dir / q_name).with_suffix('.xml')
|
129
|
+
|
130
|
+
with open(f_path,'w', encoding='utf-8') as f:
|
131
|
+
# Text schreiben
|
132
|
+
f.write('<?xml version="1.0" encoding="UTF-8"?>\n')
|
133
|
+
f.write('<quiz>\n')
|
134
|
+
f.write('<question type="numerical">\n')
|
135
|
+
f.write('<name>\n')
|
136
|
+
f.write('<text>' + q_name + '</text>\n')
|
137
|
+
f.write('</name>\n')
|
138
|
+
f.write('<questiontext format="html">\n')
|
139
|
+
if pic != 0:
|
140
|
+
f.write('<text><![CDATA[<p dir="ltr" style="text-align: left;"> <b>ID '+ str(ID) +' </b> <br></p>'\
|
141
|
+
'<p dir="ltr" style="text-align: left;">' + s_1 + '<br></p>'\
|
142
|
+
'<p dir="ltr" style="text-align: left;">' + s_2 + '<br></p>'\
|
143
|
+
'<p dir="ltr" style="text-align: left;">' + s_3 + b_str +'<br><br></p>'\
|
144
|
+
'<br><img src="@@PLUGINFILE@@/'+ str(picID) + '.svg" alt="Bild" width="500"><br>'\
|
145
|
+
'<br></p>]]></text>\n')
|
146
|
+
f.write(pic)
|
147
|
+
else:
|
148
|
+
f.write('<text><![CDATA[<p dir="ltr" style="text-align: left;"> <b>ID '+ID+' </b> <br></p>'\
|
149
|
+
'<p dir="ltr" style="text-align: left;">' + s_1 + '<br></p>'\
|
150
|
+
'<p dir="ltr" style="text-align: left;">' + s_2 + '<br></p>'\
|
151
|
+
'<p dir="ltr" style="text-align: left;">' + s_3 + b_str+ ']]></text>\n')
|
152
|
+
|
153
|
+
f.write('</questiontext>\n')
|
154
|
+
f.write('<generalfeedback format="html">\n')
|
155
|
+
f.write('<text></text>\n')
|
156
|
+
f.write('</generalfeedback>\n')
|
157
|
+
f.write('<defaultgrade>' +str(float(points_avail)) +'</defaultgrade>\n')
|
158
|
+
f.write('<penalty>0.3333333</penalty>\n')
|
159
|
+
f.write('<hidden>0</hidden>\n')
|
160
|
+
f.write('<idnumber>' + ID +'</idnumber>\n')
|
161
|
+
f.write('<answer fraction="100" format="moodle_auto_format">\n')
|
162
|
+
f.write('<text>'+ str(result) +'</text>\n')
|
163
|
+
f.write('<feedback format="html">\n')
|
164
|
+
f.write('<text><![CDATA[<p dir="ltr" style="text-align: left;"><span class="" style="color: rgb(152, 202, 62);">Das Ergebnis ist im Rahmen der 1%-Toleranz korrekt.</span><br></p>]]></text>\n')
|
165
|
+
f.write('</feedback>\n')
|
166
|
+
f.write('<tolerance>'+ str(tol_abs)+'</tolerance>\n')
|
167
|
+
f.write('</answer>\n')
|
168
|
+
f.write('<unitgradingtype>0</unitgradingtype>\n')
|
169
|
+
f.write('<unitpenalty>0.1000000</unitpenalty>\n')
|
170
|
+
f.write('<showunits>3</showunits>\n')
|
171
|
+
f.write('<unitsleft>0</unitsleft>\n')
|
172
|
+
f.write('</question>\n')
|
173
|
+
f.write('</quiz>\n')
|
174
|
+
return None
|
@@ -0,0 +1,94 @@
|
|
1
|
+
"""This Module holds small Helperfunctions related to string manipulation
|
2
|
+
"""
|
3
|
+
|
4
|
+
from pathlib import Path
|
5
|
+
import lxml.etree as ET
|
6
|
+
import base64 as base64
|
7
|
+
|
8
|
+
def stripWhitespace(stringList):
|
9
|
+
stripped = []
|
10
|
+
for i in stringList:
|
11
|
+
stripped.append(i.strip())
|
12
|
+
return stripped
|
13
|
+
|
14
|
+
def get_bullet_string(s):
|
15
|
+
"""Formatiert die Angaben zum Statischen System hübsch"""
|
16
|
+
split = s.split(';')
|
17
|
+
s_spl = stripWhitespace(split)
|
18
|
+
name = []
|
19
|
+
var = []
|
20
|
+
quant = []
|
21
|
+
unit = []
|
22
|
+
for sc in s_spl:
|
23
|
+
sc_split = sc.split()
|
24
|
+
name.append(sc_split[0])
|
25
|
+
var.append(sc_split[1])
|
26
|
+
quant.append(sc_split[3])
|
27
|
+
unit.append(sc_split[4])
|
28
|
+
bulletString = ['</p><ul dir="ltr">']
|
29
|
+
for i in range(0, len(s_spl)):
|
30
|
+
num = quant[i].split(',')
|
31
|
+
if len(num)==2:
|
32
|
+
num_s = f"{str(num[0])},\\!{str(num[1])}~"
|
33
|
+
else: num_s = f"{str(num[0])},\\!0~"
|
34
|
+
bulletString.append('<li style="text-align: left;">')
|
35
|
+
bulletString.append(f"{ name[i] }: \\( {var[i]} = {num_s} \\mathrm{{ {unit[i]} }}\\) </li>\n")
|
36
|
+
bulletString.append('<br></ul>')
|
37
|
+
return "\n".join(bulletString)
|
38
|
+
|
39
|
+
def getBase64Img(imgPath):
|
40
|
+
with open(imgPath, 'rb') as img:
|
41
|
+
img64 = base64.b64encode(img.read()).decode('utf-8')
|
42
|
+
return img64
|
43
|
+
|
44
|
+
def getUnitsElementAsString(unit):
|
45
|
+
|
46
|
+
def __getUnitEle__(name, multipl):
|
47
|
+
unit = ET.Element("unit")
|
48
|
+
ET.SubElement(unit, "multiplier").text = multipl
|
49
|
+
ET.SubElement(unit, "unit_name").text = name
|
50
|
+
return unit
|
51
|
+
|
52
|
+
unitsEle = ET.Element("units")
|
53
|
+
|
54
|
+
def printDom(xmlElement:ET.Element, file:Path|None=None )->None:
|
55
|
+
"""Prints the document tree of ``xmlTree`` to the ``file``, if specified, else dumps to stdout"""
|
56
|
+
documentTree = ET.ElementTree(xmlElement)
|
57
|
+
if file is not None:
|
58
|
+
if file.parent.exists():
|
59
|
+
documentTree.write(file, xml_declaration=True, encoding="utf-8", pretty_print=True)
|
60
|
+
else:
|
61
|
+
msg =f" No output File specified, here is the Element:"
|
62
|
+
print(f'\n{ msg :=^80}')
|
63
|
+
print( ET.tostring(xmlElement, encoding="utf-8", pretty_print=True))
|
64
|
+
print(f'{" End of Element ":=^80}')
|
65
|
+
|
66
|
+
|
67
|
+
def texWrapper(text:str|list[str], style:str)->list[str]:
|
68
|
+
"""Puts the strings inside ``text`` into a LaTex environment
|
69
|
+
|
70
|
+
if ``style == unit``: inside ``\\mathrm{}``
|
71
|
+
if ``style == math``: inside ``\\( \\)``
|
72
|
+
"""
|
73
|
+
|
74
|
+
answers:list[str]=[]
|
75
|
+
begin =""
|
76
|
+
end = ""
|
77
|
+
if style == "math":
|
78
|
+
begin ="\\("
|
79
|
+
end = "\\)"
|
80
|
+
elif style == "unit":
|
81
|
+
begin ="\\(\\mathrm{"
|
82
|
+
end = "}\\)"
|
83
|
+
if isinstance(text, str):
|
84
|
+
li = [begin]
|
85
|
+
li.append(text)
|
86
|
+
li.append(end)
|
87
|
+
answers.append("".join(li))
|
88
|
+
elif isinstance(text, list):
|
89
|
+
for i in text:
|
90
|
+
li = [begin]
|
91
|
+
li.append(i)
|
92
|
+
li.append(end)
|
93
|
+
answers.append("".join(li))
|
94
|
+
return answers
|
@@ -0,0 +1,9 @@
|
|
1
|
+
"""This *extra* subpackage conains standalone scripts
|
2
|
+
|
3
|
+
The modules inside *extra* can be run standalone, but are planned to be available from the main Window as well
|
4
|
+
|
5
|
+
To run a script execute the following: ``python -m excel2moodle.extra.SCRIPT``
|
6
|
+
Note that there is no ``.py`` at the end!!
|
7
|
+
"""
|
8
|
+
|
9
|
+
from excel2moodle.core import numericMultiQ
|
@@ -0,0 +1,124 @@
|
|
1
|
+
"""Script for verifying the equations written into the ``result`` field of NFM-type Question
|
2
|
+
|
3
|
+
This script does two things.
|
4
|
+
|
5
|
+
#. It calculates all the answers obtained from the series of variables.
|
6
|
+
#. It compares the calculation of the first answer to the ``firstResult`` field.
|
7
|
+
|
8
|
+
Usage
|
9
|
+
=====
|
10
|
+
|
11
|
+
From the main UI
|
12
|
+
----------------
|
13
|
+
|
14
|
+
#. Start this tool from the top bar in the main Window under the *Tools* section
|
15
|
+
#. A new window will open inside you:
|
16
|
+
#. Enter the Number of the Category
|
17
|
+
#. Enter the Number of the Question
|
18
|
+
#. Click on ``Run check now`` and Inspect the results
|
19
|
+
#. Rinse and repeat
|
20
|
+
|
21
|
+
As Script
|
22
|
+
---------
|
23
|
+
|
24
|
+
#. Start this script with ``py -m excel2moodle.extra.equationVerification`` inside the top-level Directory
|
25
|
+
#. Enter the Number of the Category
|
26
|
+
#. Enter the Number of the Question
|
27
|
+
#. Inspect the results
|
28
|
+
#. Rinse and repeat
|
29
|
+
"""
|
30
|
+
|
31
|
+
import re as re
|
32
|
+
import pandas as pd
|
33
|
+
from pathlib import Path
|
34
|
+
from excel2moodle.core import numericMultiQ as nmq
|
35
|
+
|
36
|
+
# Hier Bitte die Frage angeben, die getestet Werden soll:
|
37
|
+
|
38
|
+
#===========================================================
|
39
|
+
def checkResult(checkerValue:float, calculation:float, tolerance = 0.01)-> bool:
|
40
|
+
"""Checks if the two Arguments are within the tolerance the same value
|
41
|
+
|
42
|
+
:param checkerValue: the value the calculation is compared against
|
43
|
+
:type checkerValue: fleat
|
44
|
+
:param calculation: the value to be compared against checker Value
|
45
|
+
:type calculation: float
|
46
|
+
:param tolerance: the standart tolerance is 0.01 -> 1%
|
47
|
+
:type tolerance: float, optional
|
48
|
+
|
49
|
+
:returns:
|
50
|
+
True if checkerValue == calculation
|
51
|
+
False if checkerValue != calculation
|
52
|
+
:rtype: bool
|
53
|
+
"""
|
54
|
+
|
55
|
+
upper = abs(checkerValue + checkerValue*tolerance)
|
56
|
+
lower = abs(checkerValue - checkerValue*tolerance)
|
57
|
+
if abs(calculation) > lower and abs(calculation) < upper:
|
58
|
+
return True
|
59
|
+
else :
|
60
|
+
return False
|
61
|
+
|
62
|
+
def equationChecker(categoryName: str, qNumber:int, spreadsheetFile)-> tuple[list[str], list[float], float]:
|
63
|
+
"""This Function calculates all Results an invokes the checkResult function
|
64
|
+
|
65
|
+
Parameters
|
66
|
+
----------
|
67
|
+
categoryName : str
|
68
|
+
The category in which the question to be tested is
|
69
|
+
This must match a sheet name of the spreadsheet
|
70
|
+
qNumber : int
|
71
|
+
The number of the question which results are tested
|
72
|
+
spreadsheetFile : Path
|
73
|
+
The Filepath to the spreadsheet
|
74
|
+
|
75
|
+
Returns
|
76
|
+
-------
|
77
|
+
bulletPointsString : list[str]
|
78
|
+
The string list with the bullet points, with numeric values instead of variables
|
79
|
+
results : list[str]
|
80
|
+
The list with the calculated results
|
81
|
+
checkerValue : float
|
82
|
+
The value taken from the ``firstResult`` field
|
83
|
+
"""
|
84
|
+
|
85
|
+
spreadsheetFile.resolve()
|
86
|
+
df = pd.read_excel(spreadsheetFile, sheet_name=categoryName, index_col=0)
|
87
|
+
eq = df.loc["result"][qNumber]
|
88
|
+
bps = df.loc["bulletPoints"][qNumber]
|
89
|
+
try:
|
90
|
+
res = float(df.loc["firstResult"][qNumber])
|
91
|
+
except Exception:
|
92
|
+
print(f"Es ist kein 'firstResult' gegeben, kann nichts überprüfen")
|
93
|
+
res = 0
|
94
|
+
bps, calcs = nmq.parseNumericMultiQuestion(df,bps,eq, qNumber)
|
95
|
+
return bps, calcs, res
|
96
|
+
|
97
|
+
|
98
|
+
def main(spreadsheetFile= Path("../Fragensammlung/Main_question_all.xlsx"), catN = None, qNumber = None)-> None:
|
99
|
+
"""Takes the Spreadsheet, and asks for a category and a question number
|
100
|
+
|
101
|
+
"""
|
102
|
+
if catN == None:
|
103
|
+
catN = input("Geben Sie die Kategorie an: KAT_")
|
104
|
+
categoryName = f"KAT_{catN}"
|
105
|
+
if qNumber == None:
|
106
|
+
qNumber = int(input("Geben Sie die Fragennummer an: "))
|
107
|
+
bullets, results, firstResult = equationChecker(categoryName, qNumber, spreadsheetFile=spreadsheetFile)
|
108
|
+
check = False
|
109
|
+
|
110
|
+
for i, calculation in enumerate(results):
|
111
|
+
if i == 0 and firstResult !=0:
|
112
|
+
check = checkResult(firstResult, calculation)
|
113
|
+
print(f"Ergebnis {i+1}: \t{calculation}\n\tMit den Werten: \n{bullets[i]}\n")
|
114
|
+
|
115
|
+
if check == True:
|
116
|
+
print(f"Das erste berechnete Ergebnis stimmt mit dem Wert in 'firstResult' überein\n")
|
117
|
+
else:
|
118
|
+
print(f"WARNUNG: Das erste berechnete Ergebnis weicht von dem Wert {firstResult = } ab.\n")
|
119
|
+
|
120
|
+
|
121
|
+
if __name__ =="__main__":
|
122
|
+
spreadsheet =input(f"Geben Sie den Pfad zu dem spreadsheet an:")
|
123
|
+
while True:
|
124
|
+
main(Path(spreadsheet))
|
@@ -0,0 +1 @@
|
|
1
|
+
"""Here is the relevant stuff for the UI"""
|
excel2moodle/ui/appUi.py
ADDED
@@ -0,0 +1,243 @@
|
|
1
|
+
"""This Module holds the extended class mainWindow() and any other main Windows
|
2
|
+
|
3
|
+
It needs to be seperated from ``windowMain.py`` because that file will be changed by tho ``pyside6-uic`` command,
|
4
|
+
which generates the python code from the ``.ui`` file
|
5
|
+
"""
|
6
|
+
|
7
|
+
from PySide6 import QtCore
|
8
|
+
from PySide6.QtCore import Qt
|
9
|
+
from PySide6 import QtWidgets
|
10
|
+
from pathlib import Path
|
11
|
+
from excel2moodle.ui.windowMain import Ui_MoodleTestGenerator
|
12
|
+
from .windowEquationChecker import Ui_EquationChecker
|
13
|
+
from excel2moodle.ui import windowEquationChecker, dialogs
|
14
|
+
from excel2moodle.core.dataStructure import QuestionDB
|
15
|
+
from excel2moodle.ui.treewidget import QuestionItem, CategoryItem
|
16
|
+
from excel2moodle.extra import equationVerification as eqVerif
|
17
|
+
from excel2moodle import qSignalLogger
|
18
|
+
from excel2moodle.ui.settings import Settings
|
19
|
+
from excel2moodle.ui.windowDoc import DocumentationWindow
|
20
|
+
import logging as logging
|
21
|
+
|
22
|
+
from excel2moodle import dirDocumentation
|
23
|
+
|
24
|
+
|
25
|
+
logger = logging.getLogger(__name__)
|
26
|
+
|
27
|
+
|
28
|
+
|
29
|
+
class MainWindow(QtWidgets.QMainWindow):
|
30
|
+
def __init__(self, settings:Settings, testDB:QuestionDB)->None:
|
31
|
+
super().__init__()
|
32
|
+
# self.questionGenerator = questionGenerator
|
33
|
+
# self.readSettings()
|
34
|
+
self.settings = settings
|
35
|
+
self.excelPath: Path|None = None
|
36
|
+
if self.excelPath is not None:
|
37
|
+
self.ui.buttonSpreadSheet.setText(self.excelPath.name)
|
38
|
+
self.mainPath = (self.excelPath.parent if self.excelPath is not None else None)
|
39
|
+
self.exportFile = Path()
|
40
|
+
self.testDB = testDB
|
41
|
+
self.ui = Ui_MoodleTestGenerator()
|
42
|
+
self.ui.setupUi(self)
|
43
|
+
|
44
|
+
# self.ui.buttonRefresh.clicked.connect(lambda: self.refreshList(self.test))
|
45
|
+
self.ui.treeWidget.setSelectionMode(QtWidgets.QAbstractItemView.MultiSelection)
|
46
|
+
self.ui.treeWidget.header().setSectionResizeMode(QtWidgets.QHeaderView.ResizeToContents)
|
47
|
+
|
48
|
+
self.ui.retranslateUi(self)
|
49
|
+
logger.info(f"Settings are stored under: {self.settings.fileName()}")
|
50
|
+
self.ui.pointCounter.setReadOnly(True)
|
51
|
+
self.setStatus("Wählen Sie bitte eine Excel Tabelle und einen Export Ordner für die Fragen aus")
|
52
|
+
try:
|
53
|
+
self.resize(self.settings.value("windowSize"))
|
54
|
+
self.move(self.settings.value("windowPosition"))
|
55
|
+
except Exception:
|
56
|
+
pass
|
57
|
+
self.connectEvents()
|
58
|
+
|
59
|
+
def connectEvents(self)->None:
|
60
|
+
self.ui.treeWidget.itemClicked.connect(self.onSelectionChanged)
|
61
|
+
self.ui.checkBoxQuestionListSelectAll.checkStateChanged.connect(self.toggleQuestionSelectionState)
|
62
|
+
qSignalLogger.emitter.signal.connect(self.updateLog)
|
63
|
+
self.ui.actionEquationChecker.triggered.connect(self.onButOpenEqChecker)
|
64
|
+
self.ui.checkBoxIncludeCategories.checkStateChanged.connect(self.setIncludeCategoriesSetting)
|
65
|
+
# self.ui.buttonRefresh.clicked.connect(self.refreshList)
|
66
|
+
self.ui.actionParseAll.triggered.connect(self.onParseAll )
|
67
|
+
self.ui.buttonSpreadSheet.clicked.connect(self.onButSpreadsheet)
|
68
|
+
self.ui.buttonTestGen.clicked.connect(self.onButGenTest)
|
69
|
+
self.testDB.dataChanged.signal.connect(self.refreshList)
|
70
|
+
self.ui.actionPreviewQ.triggered.connect(self.previewQ)
|
71
|
+
self.ui.actionDocumentation.triggered.connect(self.onOpenDocumentation)
|
72
|
+
self.settings.shPathChanged.connect(self.onSheetPathChanged)
|
73
|
+
|
74
|
+
|
75
|
+
@QtCore.Slot(Path)
|
76
|
+
def onSheetPathChanged(self, sheet:Path)->None:
|
77
|
+
logger.debug("Slot, new Spreadsheet triggered")
|
78
|
+
self.spreadSheetPath = sheet
|
79
|
+
self.mainPath = sheet.parent
|
80
|
+
|
81
|
+
def updateLog(self,log)->None:
|
82
|
+
self.ui.loggerWindow.append(log)
|
83
|
+
|
84
|
+
def setIncludeCategoriesSetting(self):
|
85
|
+
if self.ui.checkBoxIncludeCategories.isChecked():
|
86
|
+
self.settings.set("testGen/includeCats", True)
|
87
|
+
else:
|
88
|
+
self.settings.set("testGen/includeCats", False)
|
89
|
+
|
90
|
+
@QtCore.Slot()
|
91
|
+
def onOpenDocumentation(self):
|
92
|
+
documentationWindow = DocumentationWindow(dirDocumentation, self)
|
93
|
+
documentationWindow.show()
|
94
|
+
|
95
|
+
|
96
|
+
def closeEvent(self, event):
|
97
|
+
self.settings.setValue("windowSize", self.size())
|
98
|
+
self.settings.setValue("windowPosition", self.pos())
|
99
|
+
|
100
|
+
@QtCore.Slot()
|
101
|
+
def onSelectionChanged(self, item, col):
|
102
|
+
"""Whenever the selection changes the total of selected points needs to be recalculated"""
|
103
|
+
|
104
|
+
count: int = 0
|
105
|
+
questions: int = 0
|
106
|
+
selection = self.ui.treeWidget.selectedItems()
|
107
|
+
for q in selection:
|
108
|
+
questions += 1
|
109
|
+
count += q.getQuestion().points
|
110
|
+
|
111
|
+
logger.info(f'{questions} questions are selected with {count} points')
|
112
|
+
self.ui.pointCounter.setValue(count)
|
113
|
+
|
114
|
+
# val = item.text(2)
|
115
|
+
# if val == "nan":
|
116
|
+
# val = 0
|
117
|
+
# else: val = float(val)
|
118
|
+
# if item.text(0).startswith('KAT'):
|
119
|
+
# logger.debug(f'seems to be a Category, doing nothing\n')
|
120
|
+
# return None
|
121
|
+
# logger.debug(f'Current Item: {item.text(1)}, is Selected? {item.isSelected()}')
|
122
|
+
# if item.isSelected():
|
123
|
+
# count = count + val
|
124
|
+
# logger.debug(f'Selected, Count up: {count}')
|
125
|
+
# else:
|
126
|
+
# count = count - val
|
127
|
+
# logger.debug(f'unselected, Count down: {count}')
|
128
|
+
|
129
|
+
return None
|
130
|
+
|
131
|
+
@QtCore.Slot()
|
132
|
+
def toggleQuestionSelectionState(self, state):
|
133
|
+
if state == Qt.Checked:
|
134
|
+
setter = True
|
135
|
+
else: setter = False
|
136
|
+
root = self.ui.treeWidget.invisibleRootItem()
|
137
|
+
childN = root.childCount()
|
138
|
+
for i in range(childN):
|
139
|
+
qs = root.child(i).childCount()
|
140
|
+
for q in range(qs):
|
141
|
+
root.child(i).child(q).setSelected(setter)
|
142
|
+
|
143
|
+
@QtCore.Slot()
|
144
|
+
def onButGenTest(self)->None:
|
145
|
+
path = QtWidgets.QFileDialog.getSaveFileName(self, "Select Output File",
|
146
|
+
dir=f"{self.mainPath/"Testfile.xml"}",
|
147
|
+
filter="xml Files (*.xml)")
|
148
|
+
self.exportFile = Path(path[0])
|
149
|
+
logger.info(f"New Export File is set{self.exportFile=}")
|
150
|
+
selection:list[QuestionItem] = self.ui.treeWidget.selectedItems()
|
151
|
+
self.testDB.appendQuestions(selection, self.exportFile)
|
152
|
+
return None
|
153
|
+
|
154
|
+
@QtCore.Slot()
|
155
|
+
def refreshList(self):
|
156
|
+
logger.info("starting List refresh")
|
157
|
+
cats = self.testDB.categories
|
158
|
+
self.ui.treeWidget.clear()
|
159
|
+
for cat in cats.values():
|
160
|
+
catItem = CategoryItem(self.ui.treeWidget,cat)
|
161
|
+
catItem.setFlags(catItem.flags() & ~Qt.ItemIsSelectable)
|
162
|
+
for q in cat.questions.values():
|
163
|
+
QuestionItem(catItem,q)
|
164
|
+
self.setStatus("[OK] Fragen Liste wurde aktualisiert")
|
165
|
+
self.ui.buttonTestGen.setEnabled(True)
|
166
|
+
|
167
|
+
@QtCore.Slot()
|
168
|
+
def onButSpreadsheet(self):
|
169
|
+
file = QtWidgets.QFileDialog.getOpenFileName(self,
|
170
|
+
self.tr("Open Spreadsheet"),
|
171
|
+
dir = str(self.mainPath),
|
172
|
+
filter=self.tr("Spreadsheet(*.xlsx *.ods)"),
|
173
|
+
selectedFilter=("*.ods"))
|
174
|
+
self.excelPath = Path(file[0]).resolve()
|
175
|
+
self.settings.setSpreadsheet(self.excelPath)
|
176
|
+
self.mainPath = self.excelPath.parent
|
177
|
+
self.ui.buttonSpreadSheet.setText(self.excelPath.name)
|
178
|
+
logger.debug(f'Saved Spreadsheet Path: {self.excelPath}\n')
|
179
|
+
self.setStatus("[OK] Excel Tabelle wurde eingelesen")
|
180
|
+
return None
|
181
|
+
|
182
|
+
|
183
|
+
@QtCore.Slot()
|
184
|
+
def onParseAll (self)->None:
|
185
|
+
"""Event triggered by the *Tools/Parse all Questions* Event
|
186
|
+
|
187
|
+
It parses all the Questions found in the spreadsheet and then refreshes the list of questions.
|
188
|
+
If successful it prints out a list of all exported Questions
|
189
|
+
"""
|
190
|
+
self.testDB.parseAll()
|
191
|
+
self.setStatus("[OK] Alle Fragen wurden erfolgreich in XML-Dateien umgewandelt")
|
192
|
+
self.refreshList()
|
193
|
+
return None
|
194
|
+
|
195
|
+
@QtCore.Slot()
|
196
|
+
def previewQ(self)->None:
|
197
|
+
item = self.ui.treeWidget.currentItem()
|
198
|
+
if isinstance(item, QuestionItem):
|
199
|
+
dialog = dialogs.QuestinoPreviewDialog(self, item.getQuestion())
|
200
|
+
dialog.show()
|
201
|
+
else: logger.info(f"current Item is not a Question, can't preview")
|
202
|
+
|
203
|
+
def setStatus(self, status):
|
204
|
+
self.ui.statusbar.clearMessage()
|
205
|
+
self.ui.statusbar.showMessage(self.tr(status))
|
206
|
+
|
207
|
+
@QtCore.Slot()
|
208
|
+
def onButOpenEqChecker(self):
|
209
|
+
logger.debug(f"opening wEquationChecker \n")
|
210
|
+
self.uiEqChecker = EqCheckerWindow()
|
211
|
+
self.uiEqChecker.excelFile = self.excelPath
|
212
|
+
self.uiEqChecker.show()
|
213
|
+
|
214
|
+
|
215
|
+
class EqCheckerWindow(QtWidgets.QWidget):
|
216
|
+
def __init__(self):
|
217
|
+
super().__init__()
|
218
|
+
self.excelFile = Path()
|
219
|
+
self.ui = Ui_EquationChecker()
|
220
|
+
self.ui.setupUi(self)
|
221
|
+
self.ui.buttonRunCheck.clicked.connect(lambda: self.onButRunCheck(self.ui.catNumber.value(), self.ui.qNumber.value()))
|
222
|
+
|
223
|
+
def onButRunCheck(self, catN:int, qN:int)->None:
|
224
|
+
"""
|
225
|
+
Is Triggered by the ``Run Check now`` Button and runs the Equation Check
|
226
|
+
"""
|
227
|
+
|
228
|
+
self.ui.textResultsOutput.clear()
|
229
|
+
bullets, results, firstResult = eqVerif.equationChecker(f'KAT_{catN}',qN, self.excelFile)
|
230
|
+
check = False
|
231
|
+
self.ui.lineFirstResult.setText(f'{firstResult}')
|
232
|
+
for i, calculation in enumerate(results):
|
233
|
+
if i == 0 and firstResult !=0:
|
234
|
+
check = eqVerif.checkResult(firstResult, calculation)
|
235
|
+
self.ui.lineCalculatedRes.setText(f'{calculation}')
|
236
|
+
self.ui.textResultsOutput.append(f"Ergebnis {i+1}: \t{calculation}\n\tMit den Werten: \n{bullets[i]}\n")
|
237
|
+
|
238
|
+
if check == True:
|
239
|
+
self.ui.lineCheckResult.setText("[OK]")
|
240
|
+
logger.info(f"Das erste berechnete Ergebnis stimmt mit dem Wert in 'firstResult' überein\n")
|
241
|
+
else:
|
242
|
+
self.ui.lineCheckResult.setText("[ERROR]")
|
243
|
+
|