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
from re import findall,DOTALL
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.tree = ET.parse(self.file_name)
self.questions = {}
self.categories = {}
self.folder = target_folder
self.xtra_f = extrafiles_folder
self.risky = risky
self.parse()
def get_tree(self):
......@@ -24,17 +26,17 @@ class Quizz:
self.questions[c] = []
self.categories[c] = f"c{len(self.categories)}"
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":
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":
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":
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":
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":
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:
raise f"{q.attrib['type']} is not expected"
if newQ.__class__.__name__ != "Question":
......@@ -67,10 +69,12 @@ class Quizz:
output.write(self.__str__())
class Question:
def __init__(self,xmlQ,c,n,f):
def __init__(self,xmlQ,c,n,f,x,r=False):
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.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.env = "todo:"+xmlQ.attrib["type"]
if xmlQ.find("defaultgrade") != None:
......@@ -82,7 +86,10 @@ class Question:
name = i.attrib["name"]
im = Image.open(BytesIO(base64.b64decode(i.text)))
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):
#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:
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)
PC = "øØ" #character that replaces "%" (cf. url decode pb)
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.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)
self.q = self.strip_tags(xmlQ.find("questiontext/text").text.replace("%",Cloze.PC))
if self.__class__.__name__ == "Cloze":
self.__parseAnswers(self.q)
......@@ -117,8 +125,8 @@ class Cloze(Question):
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("øØ")
elif c[0:2] == Cloze.PC:
scores = c.split(Cloze.PC)
self.choices.append(Answer(f"{l}) {scores[2]}", float(scores[1]), self.max))
else :
self.choices.append(Answer(f"{l}) {c}", 0, self.max))
......@@ -166,8 +174,8 @@ class Answer:
class MCQ(Question):
HORIZONTAL = 1
VERTICAL = 0
def __init__(self, xmlQ,c,n,f):
super().__init__(xmlQ,c,n,f)
def __init__(self, xmlQ,c,n,f,x,r):
super().__init__(xmlQ,c,n,f,x,r)
self.choices = []
self.choice_type = MCQ.VERTICAL
self.env = "questionmult"
......@@ -179,8 +187,8 @@ class MCQ(Question):
for a in xmlQ.findall("answer"):
fb = a.find("feedback/text").text
if fb != None:
fb = strip_tags(mlang_2_multiling(fb), self.folder, self.id)
self.choices.append(Answer(strip_tags(mlang_2_multiling(a.find("text").text), self.folder,self.id), a.attrib['fraction'], self.max, fb))
fb = self.strip_tags(mlang_2_multiling(fb))
self.choices.append(Answer(self.strip_tags(a.find("text").text), a.attrib['fraction'], self.max, fb))
def get_choice_env(self):
if self.choice_type == MCQ.VERTICAL:
......@@ -201,8 +209,8 @@ class MCQ(Question):
return res
class TF(MCQ):
def __init__(self, xmlQ,c,n,f):
super().__init__(xmlQ,c,n,f)
def __init__(self, xmlQ,c,n,f,x,r):
super().__init__(xmlQ,c,n,f,x,r)
self.choices = []
self.env = "question"
self.shuffle = False
......@@ -213,15 +221,15 @@ class TF(MCQ):
for a in xmlQ.findall("answer"):
fb = a.find("feedback/text").text
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":
self.choices.append(Answer("\\multiling{vrai}{true}", a.attrib['fraction'], self.max, fb))
else:
self.choices.append(Answer("\\multiling{faux}{false}", a.attrib['fraction'], self.max, fb))
class ShortAnswer(TF):
def __init__(self, xmlQ,c,n,f,l=2):
super().__init__(xmlQ,c,n,f)
def __init__(self, xmlQ,c,n,f,x,r,l=2):
super().__init__(xmlQ,c,n,f,x,r)
self.nb_lines = l
if self.__class__.__name__ == "ShortAnswer":
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
from html import unescape
from urllib.parse import unquote
unsafe = True
def process_listings(txt, folder="",q="q"):
def process_listings(txt, folder,subf, q):
txt = txt.replace("\r\n","\n")
listings = findall(r'\<pre\>(.*)\</pre\>', txt, DOTALL)
i=0
for l in listings:
if folder != "":
folder+="/"
i+=1
filename = folder+"Images/"+q+str(i)+".txt"
filename = folder+subf+q+str(i)+".txt"
f = open(filename, "w")
f.write(unescape(l))
txt = txt.replace("<pre>"+l+"</pre>","\t\t\\lstinputlisting[language=Python]{"+filename.replace(folder,"")+"}")
return txt
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>","}").replace("<em>","\\emph{").replace("</em>","}")
def preprocess_images(txt, folder, subf, q):
#"$£a1@ø" inserted by regexp, to be replaced by variable (dirty)
txt = sub(r'<img src="[^/]*/([^"]*)" (alt="([^"]*)")?[^>]*>', r"\\\\\\includegraphics[width=0.8\\linewidth]{$£a1@ø\1}",txt).replace("$£a1@ø", subf)
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:
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
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 txt
def strip_tags(txt, folder,q="q"):
return unescape(sub(compile('<.*?>'), '',remove_moodle_cdata(unquote(txt.replace("&nbsp;","~")),folder,q)))
def strip_tags(txt, folder, subf, unsafe, q="q"):
return unescape(sub(compile('<.*?>'), '',remove_moodle_cdata(unquote(txt),folder,subf, unsafe, q)))
def mlang_2_multiling(txt, which = "both"):
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