diff --git a/XML_Moodle.py b/XML_Moodle.py index b96ba3bc6d48d4468fa188ded4bd27cc9c5db820..24d1892db51fe2f33645d864ba312790d92d3df3 100755 --- a/XML_Moodle.py +++ b/XML_Moodle.py @@ -1,9 +1,11 @@ #!/usr/bin/env python3 import xml.etree.ElementTree as ET +from unidecode import unidecode from utils import strip_tags, mlang_2_multiling, score_2_str import base64 from PIL import Image from io import BytesIO +from re import findall,DOTALL class Quizz: def __init__(self, file_name, target_folder): @@ -27,6 +29,8 @@ class Quizz: newQ = ShortAnswer(q,self.categories[c],len(self.questions[c]),self.folder) elif q.attrib['type'] == "truefalse": newQ = TF(q,self.categories[c],len(self.questions[c]),self.folder) + elif q.attrib['type'] == "cloze": + newQ = Cloze(q, self.categories[c], len(self.questions[c]), self.folder) elif q.attrib['type'] == "matching": newQ = Question(q,self.categories[c],len(self.questions[c]),self.folder)#non traité elif q.attrib['type'] == "ddimageortext": @@ -65,11 +69,12 @@ class Quizz: class Question: def __init__(self,xmlQ,c,n,f): self.folder = f - self.id = c+"."+mlang_2_multiling(xmlQ.find("name/text").text,"en")+f".{n}" + self.id = unidecode(c+"."+mlang_2_multiling(xmlQ.find("name/text").text.replace(" ",""),"en")+f".{n}") self.q = strip_tags(mlang_2_multiling(xmlQ.find("questiontext/text").text), self.folder,self.id) self.category = c self.env = "todo:"+xmlQ.attrib["type"] - self.max = float(xmlQ.find("defaultgrade").text) + if xmlQ.find("defaultgrade") != None: + self.max = float(xmlQ.find("defaultgrade").text) self.parseImages(xmlQ.findall(".//file")) def parseImages(self, file_elements): @@ -83,11 +88,62 @@ class Question: #return "\\element{" + self.category + "}{\n\t\\begin{" + self.env + "}{" + self.id + "}\\nbpoints{" + score_2_str(self.max) + "}\n\t\t" + self.q + "\\end{" + self.env + "}\n" return "" +class Cloze(Question): + zones = ['A', 'B', 'C', 'D', 'E', 'F','G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'] + def __init__(self, xmlQ,c,n,f): + super().__init__(xmlQ,c,n,f) + self.max = float(xmlQ.find("penalty").text) + self.choices = [] + self.choices_num = {} + self.choice_type = MCQ.VERTICAL + self.env = "questionmult" + self.q = strip_tags(mlang_2_multiling(xmlQ.find("questiontext/text").text.replace("%","øØ")), self.folder,self.id) + if self.__class__.__name__ == "Cloze": + self.__parseAnswers(self.q) + + def __parseAnswers(self, txt): + questions = findall(r'\{:MCS?:([^\}]*)\}', txt, DOTALL) + i = 0 + for c in questions: + self.create_choices(c, Cloze.zones[i], len(questions)) + txt = txt.replace(r"{:MCS:"+c+"}", "\\underline{~~~~~~~~}("+Cloze.zones[i]+")").replace(r"{:MC:"+c+"}", "\\underline{~~~~~~~~}("+Cloze.zones[i]+")") + i += 1 + self.q = txt + + def create_choices(self, c_list, l, n): + self.choices_num[l] = 0 + choices = c_list.split("~") + for c in choices: + self.choices_num[l] += 1 + if c[0] == "=": + self.choices.append(Answer(f"{l}) {c[1:]}", 100/n, self.max)) + elif c[0:2] =="øØ": + scores = c.split("øØ") + self.choices.append(Answer(f"{l}) {scores[2]}", float(scores[1]), self.max)) + else : + self.choices.append(Answer(f"{l}) {c}", 0, self.max)) + + def __str__(self): + res = "\\element{" + self.category + "}{\n\t\\begin{" + self.env + "}{" + self.id + "}\\nbpoints{" + score_2_str(self.max) + "}\\\\\n" + self.q +"\n\t\t\\begin{multicols}{"+str(len(self.choices_num))+"}\n\t\t\t\\begin{choices}[o]\n\\AMCnoCompleteMulti" + res += "\n" + l = 0 + i = 0 + for c in self.choices: + i += 1 + res += str(c) + if self.choices_num[Cloze.zones[l]] == i: + i = 0 + l += 1 + if l < len(self.choices_num): + res += "\\vfill\\null\n\\columnbreak\n" + res += "\n\t\t\t\\end{choices} \n\t\t\\end{multicols} \n\t\\end{" + self.env + "}\n}\n\n" + return res + class Answer: - def __init__(self,t,b,max,fb): + def __init__(self,t,b,max,fb=None): self.status = float(b) > 0 self.text = t - self.points = round(float(b)/(100*float(max)),3) + self.points = round((float(b)/100)*float(max),3) self.feedback = fb def __str__(self): @@ -187,7 +243,7 @@ class ShortAnswer(TF): return res if __name__ == "__main__": - quizz = Quizz("data/SID questions.xml", "data") + quizz = Quizz("data/KNM questions.xml", "data") #quizz = Quizz("data/quiz-GI-4.xml" , "data") #print(quizz) quizz.save() diff --git a/utils.py b/utils.py index dd896b6e0608afd60b70fd54f280bd576173357a..9d1d9262155c62beebd7041f81c85a52ca5fb8ea 100644 --- a/utils.py +++ b/utils.py @@ -22,7 +22,7 @@ def process_listings(txt, folder="",q="q"): def remove_moodle_cdata(txt, folder, q): global unsafe txt = process_listings(txt, folder, q) - res = sub(r'<img src="[^/]*/([^"]*)" (alt="([^"]*)")?[^>]*>', r"""\\\\\\includegraphics[width=0.8\\linewidth]{Images/\1}""",txt).replace("<![CDATA[","").replace("]]>","").replace("<strong>","\\emph{").replace("</strong>","}") + res = sub(r'<img src="[^/]*/([^"]*)" (alt="([^"]*)")?[^>]*>', r"""\\\\\\includegraphics[width=0.8\\linewidth]{Images/\1}""",txt).replace("<![CDATA[","").replace("]]>","").replace("<strong>","\\emph{").replace("</strong>","}").replace("<em>","\\emph{").replace("</em>","}") if unsafe: res = res.replace('<span style="font:monospace">',"\lstinline[language=python]|").replace('<span style="font-family:monospace">',"\lstinline[language=python]|").replace('<span style="font-family=monospace">',"\lstinline[language=python]|").replace("</span>","|") return res @@ -43,5 +43,5 @@ def score_2_str(v): if int(v)==float(v): res = str(int(v)) else: - res = str(float(v)) + res = str(round(float(v),3)) return res