diff --git a/.gitignore b/.gitignore index ecc788e80f915c0a2d02d35b9b5b50b8ea84bbda..6df43f12c119bbe2d7f219f5ef9d448cd0d05f30 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ quiz-*.xml __pycache__ +data diff --git a/XML_Moodle.py b/XML_Moodle.py index f9ad0d49f1f19b946bc4fd77614d41b583a97586..da3c8ba7fdb14ad6cf4973a88ca4ada70fb759f2 100755 --- a/XML_Moodle.py +++ b/XML_Moodle.py @@ -1,11 +1,16 @@ #!/usr/bin/env python3 import xml.etree.ElementTree as ET from utils import strip_tags, mlang_2_multiling, score_2_str +import base64 +from PIL import Image +from io import BytesIO + class Quizz: - def __init__(self, file_name): + def __init__(self, file_name, target_folder): self.file_name = file_name self.tree = ET.parse(self.file_name) self.questions = {} + self.folder = target_folder self.parse() def get_tree(self): @@ -15,15 +20,15 @@ class Quizz: if c not in self.questions.keys(): self.questions[c] = [] if q.attrib['type'] == "multichoice": - newQ = MCQ(q,c,len(self.questions[c])) + newQ = MCQ(q,c,len(self.questions[c]),self.folder) elif q.attrib['type'] == "shortanswer": - newQ = Question(q,c,len(self.questions[c])) + newQ = Question(q,c,len(self.questions[c]),self.folder) elif q.attrib['type'] == "truefalse": - newQ = TF(q,c,len(self.questions[c])) + newQ = TF(q,c,len(self.questions[c]),self.folder) elif q.attrib['type'] == "matching": - newQ = Question(q,c,len(self.questions[c])) + newQ = Question(q,c,len(self.questions[c]),self.folder) elif q.attrib['type'] == "ddimageortext": - newQ = Question(q,c,len(self.questions[c])) + newQ = Question(q,c,len(self.questions[c]),self.folder) else: raise f"{q.attrib['type']} is not expected" self.questions[c].append(newQ) @@ -43,17 +48,33 @@ class Quizz: res += str(q) return res + def save(self): + name = self.file_name[self.file_name.rfind('/')+1:self.file_name.rfind('.')] + output = open(self.folder+"/"+name+".tex", "w") + output.write(self.__str__()) + class Question: - def __init__(self,xmlQ,c,n): - self.q = mlang_2_multiling(strip_tags(xmlQ.find("questiontext/text").text)) + def __init__(self,xmlQ,c,n,f): + self.folder = f + self.q = mlang_2_multiling(strip_tags(xmlQ.find("questiontext/text").text, self.folder)) self.category = c self.id = f"{c[c.rfind(':')+1:]}_{n}" - self.env = "questionmult" + self.env = "todo:"+xmlQ.attrib["type"] self.max = float(xmlQ.find("defaultgrade").text) + self.parseImages(xmlQ.findall(".//file")) + + def parseImages(self, file_elements): + for i in file_elements: + name = i.attrib["name"] + print(name) + im = Image.open(BytesIO(base64.b64decode(i.text))) + ext = name[name.rfind('.')+1:].upper() + im.save(self.folder+"/"+name, ext) + def __str__(self): return """\\element{"""+self.category+"""}{ \\begin{"""+self.env+"""}{"""+self.id+"""}\\nbpoints{"""+score_2_str(self.max)+"""} - """+self.q+"\\end{"+self.env+"}" + """+self.q+"\\end{"+self.env+"}\n" class Answer: def __init__(self,t,b,max,fb): @@ -80,9 +101,10 @@ class Answer: class MCQ(Question): - def __init__(self, xmlQ,c,n): - super().__init__(xmlQ,c,n) + def __init__(self, xmlQ,c,n,f): + super().__init__(xmlQ,c,n,f) self.choices = [] + self.env = "questionmult" self.__parseAnswers(xmlQ) def __parseAnswers(self, xmlQ): @@ -90,8 +112,8 @@ class MCQ(Question): for a in xmlQ.findall("answer"): fb = a.find("feedback/text").text if fb != None: - fb = mlang_2_multiling(strip_tags(fb)) - self.choices.append(Answer(mlang_2_multiling(strip_tags(a.find("text").text)), a.attrib['fraction'], self.max, fb)) + fb = mlang_2_multiling(strip_tags(fb, self.folder)) + self.choices.append(Answer(mlang_2_multiling(strip_tags(a.find("text").text, self.folder)), a.attrib['fraction'], self.max, fb)) def __str__(self): res = """\\element{"""+self.category+"""}{ @@ -106,25 +128,25 @@ class MCQ(Question): return res class TF(MCQ): - def __init__(self, xmlQ,c,n): - super(MCQ, self).__init__(xmlQ,c,n) + def __init__(self, xmlQ,c,n,f): + super(MCQ, self).__init__(xmlQ,c,n,f) self.choices = [] self.env = "question" self.shuffle = False self.__parseAnswers(xmlQ) def __parseAnswers(self, xmlQ): - print("ok") for a in xmlQ.findall("answer"): fb = a.find("feedback/text").text if fb != None: - fb = mlang_2_multiling(strip_tags(fb)) + fb = mlang_2_multiling(strip_tags(fb, self.folder)) if a.find("text").text == "true": - self.choices.append(Answer("\\multi{vrai}{true}", a.attrib['fraction'], self.max, fb)) + self.choices.append(Answer("\\multiling{vrai}{true}", a.attrib['fraction'], self.max, fb)) else: - self.choices.append(Answer("\\multi{faux}{false}", a.attrib['fraction'], self.max, fb)) + self.choices.append(Answer("\\multiling{faux}{false}", a.attrib['fraction'], self.max, fb)) if __name__ == "__main__": - quizz = Quizz("quiz-GI-4-SID-S1-top-20201204-1620.xml") - #quizz = Quizz("quiz-GI-4.xml") - print(quizz) + quizz = Quizz("data/quiz-GI-4-SID-S1-top-20201204-1620.xml", "data") + #quizz = Quizz("data/quiz-GI-4.xml" , "data") + #print(quizz) + quizz.save() diff --git a/utils.py b/utils.py index 0ca123697dcbcffe9e3a03da78d58ee02576dd5c..22b2e54d4e2030f09c4a9cdb65c8156c7d8f0ee3 100644 --- a/utils.py +++ b/utils.py @@ -1,22 +1,32 @@ #!/usr/bin/env python3 from re import compile,sub,escape +from html import unescape +from urllib.parse import unquote def html_2_tex(text): #https://code.activestate.com/recipes/81330-single-pass-multiple-replace/ - dict = {' ': '~', '>':'>', '<': '<'} - """ Replace in 'text' all occurences of any key in the given - dictionary by its corresponding value. Returns the new tring.""" - # Create a regular expression from the dictionary keys - regex = compile("(%s)" % "|".join(map(escape, dict.keys()))) + # dict = {' ': '~', '>':'>', '<': '<'} + # """ Replace in 'text' all occurences of any key in the given + # dictionary by its corresponding value. Returns the new tring.""" + # # Create a regular expression from the dictionary keys + # regex = compile("(%s)" % "|".join(map(escape, dict.keys()))) + # + # # For each match, look-up corresponding value in dictionary + # return regex.sub(lambda mo: dict[mo.string[mo.start():mo.end()]], text) + return unescape(text) - # For each match, look-up corresponding value in dictionary - return regex.sub(lambda mo: dict[mo.string[mo.start():mo.end()]], text) -def remove_moodle_cdata(txt): - return txt.replace("<![CDATA[","").replace("]]>","") +def remove_moodle_cdata(txt, folder): + return sub(r'<img src="[^/]*/([^"]*)" (alt="([^"]*)")?[^>]*>', r"""\\begin{figure}[h!] + \\begin{center} + \\includegraphics[width=0.8\\linewidth]{$$$$folder$$$$/\1} + \\end{center} + \\caption{\3} +\\end{figure} +""",txt).replace("<![CDATA[","").replace("]]>","").replace("$$$$folder$$$$", folder) -def strip_tags(txt): - return html_2_tex(sub(compile('<.*?>'), '',remove_moodle_cdata(txt))) +def strip_tags(txt, folder): + return html_2_tex(sub(compile('<.*?>'), '',remove_moodle_cdata(unquote(txt), folder))) def mlang_2_multiling(txt): return sub(r"\{mlang en\}(.*?)\{mlang\}((\{mlang other\})|(\{mlang fr\}))(.*?)\{mlang\}", r"\\multiling{\5}{\1}",txt)