Skip to content
Snippets Groups Projects
Commit a6356fc9 authored by Mathieu Loiseau's avatar Mathieu Loiseau
Browse files

with documentation

parent 87033cb7
No related branches found
No related tags found
No related merge requests found
...@@ -8,12 +8,14 @@ from io import BytesIO ...@@ -8,12 +8,14 @@ from io import BytesIO
from re import findall,DOTALL from re import findall,DOTALL
class Quizz: class Quizz:
def __init__(self, file_name, target_folder): def __init__(self, file_name, target_folder, extrafiles_folder, risky):
self.file_name = file_name self.file_name = file_name
self.tree = ET.parse(self.file_name) self.tree = ET.parse(self.file_name)
self.questions = {} self.questions = {}
self.categories = {} self.categories = {}
self.folder = target_folder self.folder = target_folder
self.xtra_f = extrafiles_folder
self.risky = risky
self.parse() self.parse()
def get_tree(self): def get_tree(self):
...@@ -24,17 +26,17 @@ class Quizz: ...@@ -24,17 +26,17 @@ class Quizz:
self.questions[c] = [] self.questions[c] = []
self.categories[c] = f"c{len(self.categories)}" self.categories[c] = f"c{len(self.categories)}"
if q.attrib['type'] == "multichoice": if q.attrib['type'] == "multichoice":
newQ = MCQ(q,self.categories[c],len(self.questions[c]),self.folder) newQ = MCQ(q,self.categories[c],len(self.questions[c]),self.folder,self.xtra_f, self.risky)
elif q.attrib['type'] == "shortanswer": elif q.attrib['type'] == "shortanswer":
newQ = ShortAnswer(q,self.categories[c],len(self.questions[c]),self.folder) newQ = ShortAnswer(q,self.categories[c],len(self.questions[c]),self.folder, self.xtra_f, self.risky)
elif q.attrib['type'] == "truefalse": elif q.attrib['type'] == "truefalse":
newQ = TF(q,self.categories[c],len(self.questions[c]),self.folder) newQ = TF(q,self.categories[c],len(self.questions[c]),self.folder, self.xtra_f, self.risky)
elif q.attrib['type'] == "cloze": elif q.attrib['type'] == "cloze":
newQ = Cloze(q, self.categories[c], len(self.questions[c]), self.folder) newQ = Cloze(q, self.categories[c], len(self.questions[c]), self.folder, self.xtra_f, self.risky)
elif q.attrib['type'] == "matching": elif q.attrib['type'] == "matching":
newQ = Question(q,self.categories[c],len(self.questions[c]),self.folder)#non traité newQ = Question(q,self.categories[c],len(self.questions[c]),self.folder, self.xtra_f, self.risky)#non traité
elif q.attrib['type'] == "ddimageortext": elif q.attrib['type'] == "ddimageortext":
newQ = Question(q,self.categories[c],len(self.questions[c]),self.folder)#non traité newQ = Question(q,self.categories[c],len(self.questions[c]),self.folder, self.xtra_f, self.risky)#non traité
else: else:
raise f"{q.attrib['type']} is not expected" raise f"{q.attrib['type']} is not expected"
if newQ.__class__.__name__ != "Question": if newQ.__class__.__name__ != "Question":
...@@ -67,10 +69,12 @@ class Quizz: ...@@ -67,10 +69,12 @@ class Quizz:
output.write(self.__str__()) output.write(self.__str__())
class Question: class Question:
def __init__(self,xmlQ,c,n,f): def __init__(self,xmlQ,c,n,f,x,r=False):
self.folder = f self.folder = f
self.xtra_f = x
self.risky = r
self.id = unidecode(c+"."+mlang_2_multiling(xmlQ.find("name/text").text.replace(" ",""),"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.q = self.strip_tags(xmlQ.find("questiontext/text").text)
self.category = c self.category = c
self.env = "todo:"+xmlQ.attrib["type"] self.env = "todo:"+xmlQ.attrib["type"]
if xmlQ.find("defaultgrade") != None: if xmlQ.find("defaultgrade") != None:
...@@ -82,7 +86,10 @@ class Question: ...@@ -82,7 +86,10 @@ class Question:
name = i.attrib["name"] name = i.attrib["name"]
im = Image.open(BytesIO(base64.b64decode(i.text))) im = Image.open(BytesIO(base64.b64decode(i.text)))
ext = name[name.rfind('.')+1:].upper() ext = name[name.rfind('.')+1:].upper()
im.save(self.folder+"/Images/"+name, ext) im.save(self.folder+self.xtra_f+name, ext)
def strip_tags(self, txt):
return strip_tags(mlang_2_multiling(txt), self.folder, self.xtra_f, self.risky, self.id)
def __str__(self): def __str__(self):
#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 "\\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"
...@@ -90,14 +97,15 @@ class Question: ...@@ -90,14 +97,15 @@ class Question:
class Cloze(Question): 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'] 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): PC = "øØ" #character that replaces "%" (cf. url decode pb)
super().__init__(xmlQ,c,n,f) def __init__(self, xmlQ,c,n,f,x,r):
super().__init__(xmlQ,c,n,f,x,r)
self.max = float(xmlQ.find("penalty").text) self.max = float(xmlQ.find("penalty").text)
self.choices = [] self.choices = []
self.choices_num = {} self.choices_num = {}
self.choice_type = MCQ.VERTICAL self.choice_type = MCQ.VERTICAL
self.env = "questionmult" self.env = "questionmult"
self.q = strip_tags(mlang_2_multiling(xmlQ.find("questiontext/text").text.replace("%","øØ")), self.folder,self.id) self.q = self.strip_tags(xmlQ.find("questiontext/text").text.replace("%",Cloze.PC))
if self.__class__.__name__ == "Cloze": if self.__class__.__name__ == "Cloze":
self.__parseAnswers(self.q) self.__parseAnswers(self.q)
...@@ -117,8 +125,8 @@ class Cloze(Question): ...@@ -117,8 +125,8 @@ class Cloze(Question):
self.choices_num[l] += 1 self.choices_num[l] += 1
if c[0] == "=": if c[0] == "=":
self.choices.append(Answer(f"{l}) {c[1:]}", 100/n, self.max)) self.choices.append(Answer(f"{l}) {c[1:]}", 100/n, self.max))
elif c[0:2] =="øØ": elif c[0:2] == Cloze.PC:
scores = c.split("øØ") scores = c.split(Cloze.PC)
self.choices.append(Answer(f"{l}) {scores[2]}", float(scores[1]), self.max)) self.choices.append(Answer(f"{l}) {scores[2]}", float(scores[1]), self.max))
else : else :
self.choices.append(Answer(f"{l}) {c}", 0, self.max)) self.choices.append(Answer(f"{l}) {c}", 0, self.max))
...@@ -166,8 +174,8 @@ class Answer: ...@@ -166,8 +174,8 @@ class Answer:
class MCQ(Question): class MCQ(Question):
HORIZONTAL = 1 HORIZONTAL = 1
VERTICAL = 0 VERTICAL = 0
def __init__(self, xmlQ,c,n,f): def __init__(self, xmlQ,c,n,f,x,r):
super().__init__(xmlQ,c,n,f) super().__init__(xmlQ,c,n,f,x,r)
self.choices = [] self.choices = []
self.choice_type = MCQ.VERTICAL self.choice_type = MCQ.VERTICAL
self.env = "questionmult" self.env = "questionmult"
...@@ -179,8 +187,8 @@ class MCQ(Question): ...@@ -179,8 +187,8 @@ class MCQ(Question):
for a in xmlQ.findall("answer"): for a in xmlQ.findall("answer"):
fb = a.find("feedback/text").text fb = a.find("feedback/text").text
if fb != None: if fb != None:
fb = strip_tags(mlang_2_multiling(fb), self.folder, self.id) fb = self.strip_tags(mlang_2_multiling(fb))
self.choices.append(Answer(strip_tags(mlang_2_multiling(a.find("text").text), self.folder,self.id), a.attrib['fraction'], self.max, fb)) self.choices.append(Answer(self.strip_tags(a.find("text").text), a.attrib['fraction'], self.max, fb))
def get_choice_env(self): def get_choice_env(self):
if self.choice_type == MCQ.VERTICAL: if self.choice_type == MCQ.VERTICAL:
...@@ -201,8 +209,8 @@ class MCQ(Question): ...@@ -201,8 +209,8 @@ class MCQ(Question):
return res return res
class TF(MCQ): class TF(MCQ):
def __init__(self, xmlQ,c,n,f): def __init__(self, xmlQ,c,n,f,x,r):
super().__init__(xmlQ,c,n,f) super().__init__(xmlQ,c,n,f,x,r)
self.choices = [] self.choices = []
self.env = "question" self.env = "question"
self.shuffle = False self.shuffle = False
...@@ -213,15 +221,15 @@ class TF(MCQ): ...@@ -213,15 +221,15 @@ class TF(MCQ):
for a in xmlQ.findall("answer"): for a in xmlQ.findall("answer"):
fb = a.find("feedback/text").text fb = a.find("feedback/text").text
if fb != None: if fb != None:
fb = strip_tags(mlang_2_multiling(fb), self.folder, self.id) fb = self.strip_tags(mlang_2_multiling(fb))
if a.find("text").text == "true": if a.find("text").text == "true":
self.choices.append(Answer("\\multiling{vrai}{true}", a.attrib['fraction'], self.max, fb)) self.choices.append(Answer("\\multiling{vrai}{true}", a.attrib['fraction'], self.max, fb))
else: else:
self.choices.append(Answer("\\multiling{faux}{false}", a.attrib['fraction'], self.max, fb)) self.choices.append(Answer("\\multiling{faux}{false}", a.attrib['fraction'], self.max, fb))
class ShortAnswer(TF): class ShortAnswer(TF):
def __init__(self, xmlQ,c,n,f,l=2): def __init__(self, xmlQ,c,n,f,x,r,l=2):
super().__init__(xmlQ,c,n,f) super().__init__(xmlQ,c,n,f,x,r)
self.nb_lines = l self.nb_lines = l
if self.__class__.__name__ == "ShortAnswer": if self.__class__.__name__ == "ShortAnswer":
self.__parseAnswers(xmlQ) self.__parseAnswers(xmlQ)
......
#!/usr/bin/env python3
import argparse
from argparse import RawTextHelpFormatter #pour le formattage de l'aide
#déclaration des arguments
parser = argparse.ArgumentParser(formatter_class=RawTextHelpFormatter,description="""Convertir un export de base de questions moodle en sujet AMC
\033[1m\033[32mex :\033[0m
\033[0m\033[32m./mood2amc.py -m base-de-questions_moodle.xml\033[0m
\033[0m\033[32m./mood2amc.py -u -m data/base-de-questions_moodle.xml -d data -s images\033[0m
\033[0m\033[32m./mood2amc.py -m bdm.xml -d /home/user/Documents/Exams/mon_super_exam \033[0m""")
parser.add_argument("-m","--moodledb", help="la base de questions moodle xml à convertir", type=str, default=None)
parser.add_argument("-u","--unsafe", help="convertir les <span> monospace en listings (dangereux)", action="store_true")
parser.add_argument("-d","--destination", help="dossier de destination", type=str, default="data")
parser.add_argument("-s","--sub_dir", help="Dossier où mettre les images et listings (sous-dossier de destination)", type=str, default="images")
config = vars(parser.parse_args())
from os import path, makedirs
if not path.isdir(config["destination"]):
makedirs(config["destination"],0o755)
if config["destination"][-1] != "/":
config["destination"]+="/"
if config["sub_dir"][-1] != "/":
config["sub_dir"]+="/"
if not path.isdir(config["destination"]+config["sub_dir"]):
makedirs(config["destination"]+config["sub_dir"],0o755)
from XML_Moodle import Quizz
Quizz(config["moodledb"], config["destination"], config["sub_dir"], config["unsafe"]).save()
...@@ -3,32 +3,33 @@ from re import compile,sub,escape,findall,DOTALL ...@@ -3,32 +3,33 @@ from re import compile,sub,escape,findall,DOTALL
from html import unescape from html import unescape
from urllib.parse import unquote from urllib.parse import unquote
unsafe = True def process_listings(txt, folder,subf, q):
def process_listings(txt, folder="",q="q"):
txt = txt.replace("\r\n","\n") txt = txt.replace("\r\n","\n")
listings = findall(r'\<pre\>(.*)\</pre\>', txt, DOTALL) listings = findall(r'\<pre\>(.*)\</pre\>', txt, DOTALL)
i=0 i=0
for l in listings: for l in listings:
if folder != "":
folder+="/"
i+=1 i+=1
filename = folder+"Images/"+q+str(i)+".txt" filename = folder+subf+q+str(i)+".txt"
f = open(filename, "w") f = open(filename, "w")
f.write(unescape(l)) f.write(unescape(l))
txt = txt.replace("<pre>"+l+"</pre>","\t\t\\lstinputlisting[language=Python]{"+filename.replace(folder,"")+"}") txt = txt.replace("<pre>"+l+"</pre>","\t\t\\lstinputlisting[language=Python]{"+filename.replace(folder,"")+"}")
return txt return txt
def remove_moodle_cdata(txt, folder, q): def preprocess_images(txt, folder, subf, q):
global unsafe #"$£a1@ø" inserted by regexp, to be replaced by variable (dirty)
txt = process_listings(txt, folder, q) txt = sub(r'<img src="[^/]*/([^"]*)" (alt="([^"]*)")?[^>]*>', r"\\\\\\includegraphics[width=0.8\\linewidth]{$£a1@ø\1}",txt).replace("$£a1@ø", subf)
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>","}") return txt
def remove_moodle_cdata(txt, folder, subf, unsafe, q):
txt = process_listings(txt.replace("<![CDATA[", "").replace("]]>", "").replace("<strong>", "\\emph{").replace("</strong>", "}").replace("<em>", "\\emph{").replace("</em>", "}"),
folder, subf, q)
txt = preprocess_images(txt, folder, subf, q)
if unsafe: 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>","|") txt = txt.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 return txt
def strip_tags(txt, folder,q="q"): def strip_tags(txt, folder, subf, unsafe, q="q"):
return unescape(sub(compile('<.*?>'), '',remove_moodle_cdata(unquote(txt.replace("&nbsp;","~")),folder,q))) return unescape(sub(compile('<.*?>'), '',remove_moodle_cdata(unquote(txt),folder,subf, unsafe, q)))
def mlang_2_multiling(txt, which = "both"): def mlang_2_multiling(txt, which = "both"):
if which == "fr": if which == "fr":
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment