Créer un QCM et les corriger automatiquement, c’est une chose que l’on peut faire avec https://www.auto-multiple-choice.net/fr/install/, mais ceci n’est installable facilement que sur Linux ou MacOS. L’installer sous windows nécessite d’installer un émulateur Linux.

J’envisage donc de me lancer dans un projet, mais je ne sais pas trop où je vais aller…

Créer un QCM et les corriger automatiquement: l’idée générale

Dans cet article, je vous expliquais comment créer un QCM en \(\LaTeX\) à l’aide de Python, mais ce dernier n’était pas interactif.

Là, le projet est de créer des QCM interactifs (donc, sur lesquels ont peut choisir une réponse ou insérer un texte) qui pourraient être corriger automatiquement.

L’idée est de ne pas s’embêter, donc de partir d’un fichier texte classique avec une structure de la forme:

title=QCM sur n'importe quoi

style=text
Saisir un chiffre impair
1,3,5,7,9

style=qcm
Le triangle de dimensions 3 cm, 4 cm et 5 cm est-il rectangle ?
Oui,Non
Oui

Chaque question est déterminée par la ligne : « style=…« . On aurait le choix entre:

  • « text » si la réponse attendue est à saisir au clavier
  • « qcm » si la question est à choix multiple

Créer un QCM et les corriger automatiquement: le traitement en Python

Assumons le fait que le fichier précédent soit nommé questionnaire.txt.

Le premier script Python serait chargé de lire ce fichier, de construire un fichier \(\LaTeX\) et de le compiler.

Ensuite, il faut un second script Python pour lire le PDF complété et vérifier si les réponses sont correctes ou non.

Pour les deux scripts, il me faut une fonction commune, celle qui permet de lire le fichier texte (questionnaire.txt pour notre exemple), qui en extrait les données essentielles et les renvoie sous forme de dictionnaire. Ce sera la fonction suivante, que je vais mettre dans le fichier funcaux.py:

def parse_questionnaire(file_path):
    with open(file_path, 'r') as file:
        lines = file.readlines()

    data = {}
    current_style = None
    question = None
    options = None

    for line in lines:
        line = line.strip()
        if not line:
            continue

        if line.startswith('title='):
            data['title'] = line.split('=', 1)[1].strip()
        elif line.startswith('style='):
            current_style = line.split('=', 1)[1].strip()
        else:
            if current_style == 'text':
                if question is None:
                    question = line
                else:
                    data[question] = {'type': current_style, 'answers': line}
                    question = None
            elif current_style == 'qcm':
                if question is None:
                    question = line
                elif options is None:
                    options = line.split(',')
                else:
                    if question not in data:
                        data[question] = {'type': current_style, 'options': options, 'correct_answer': line}
                    question = None
                    options = None

    return data

Construction du QCM en \(\LaTeX\)

Le premier script Python sera mis dans le fichier create_qcm.py:

import os
from funcaux import parse_questionnaire


"""
Fonction qui créé le fichier tex
"""

def tex(dic, answer = False):
    title = False
    tex = r"""\documentclass{article}
\usepackage[T1]{fontenc}
\usepackage{fourier}
\usepackage{hyperref}
\usepackage{tabularx}
\usepackage{cellspace}
    \setlength{\cellspacetoplimit}{3mm}
    \setlength{\cellspacebottomlimit}{1mm}
\usepackage[hmargin=1cm, vmargin=2cm]{geometry}
\newcounter{questions}
\setcounter{questions}{0}
\newcommand{\question}{\stepcounter{questions}\arabic{questions}}

\setlength{\parindent}{0pt}
\begin{document}
"""
    
    if 'title' in questionnaire_data.keys():
        tex += "\\title{"+dic['title']+"}\n\\date{}\\maketitle\n\begin{Form}\n"
        del dic['title']
        
    elif answer == True:
        tex += "\\title{Correction}\n\\date{}\\maketitle\n"
    
    tex += r"""\begin{tabularx}{\linewidth}{|Sc|X|Sl|}
\hline"""
    
    for key, value in dic.items():
        if value['type'] == 'text':
            if answer == False:
                tex += "\\question & " + key +  " & \\TextField[name=q\\arabic{questions},align=1, height=0.4em]{} \\\\\n\hline\n"
            else:
                tex += "\\question & " + key +  " & "+value['answers']+" \\\\\n\hline\n"
        elif value['type'] == 'qcm':
            tex += "\\question & " + key + " & \\begin{minipage}{30mm}\n"
            if answer == False:
                tex += "\\ChoiceMenu[combo,name=q\\arabic{questions}, width=1em]{}{"
                for v in value['options']:
                    tex += v + ","
                
                tex = tex[:-1] # pour supprimer la dernière virgule
                tex += "}\n"
            else:
                tex += value['correct_answer'] + "\n"
            tex += "\\end{minipage}\\\\\n"
    
    tex += r"""\hline
\end{tabularx}"""
    
    if answer == False:
        tex += "\end{Form}\n"
        
    tex += "\end{document}"
    
    return tex

if __name__ == '__main__':
    file_path = 'questionnaire.txt'
    questionnaire_data = parse_questionnaire(file_path)
    doc_tex = tex(questionnaire_data)
    
    with open('questionnaire.tex', 'w', encoding='utf-8') as file:
        file.write(doc_tex)
        
    # compilation PdfLaTeX dans le répertoire courant
    cmd = 'echo Compilation du fichier '+doc_tex
    cmd = "pdflatex -shell-escape -synctex=1 -interaction=nonstopmode questionnaire.tex"
    os.system(cmd)
    
    # Pour le QCM corrigé
    doc_tex_corr = tex(questionnaire_data,answer=True)
    
    with open('questionnaire_corr.tex', 'w', encoding='utf-8') as file:
        file.write(doc_tex_corr)
        
    # compilation PdfLaTeX dans le répertoire courant
    cmd = 'echo Compilation du fichier '+doc_tex
    cmd = "pdflatex -shell-escape -synctex=1 -interaction=nonstopmode questionnaire_corr.tex"
    os.system(cmd)

Le script génère via pdfLaTeX le PDF. Avec le fichier texte précédent, cela donne:

Si on choisit de le sauvegarder sous le nom de questionnaire_complete.pdf, on aura ceci:

Traitement du PDF complété en Python

Le second script est pis dans le fichier correction.py:

import pypdf
import re
from funcaux import parse_questionnaire

def clean_value(value):
    # Supprimer les caractères de contrôle et les séquences d'échappement
    if isinstance(value, str):
        # Utiliser une expression régulière pour supprimer les caractères non alphabétiques
        value = re.sub(r'[^a-zA-Z]', '', value)
    return value

def format_pdf_fields(fields):
    formatted_output = {}
    for key, value in fields.items():
        if value.get('/FT') == '/Tx':  # Champ de texte
            formatted_output[key] = value.get('/V', '')
        elif value.get('/FT') == '/Ch':  # Champ de choix (combo ou liste)
            if '/V' in value:
                formatted_output[key] = value['/V']
    return formatted_output

def correction(file_path):
    # Récupération du formulaire du PDF rendu
    rendu = 'questionnaire_complete.pdf'
    reader = pypdf.PdfReader(rendu)
    output = reader.get_fields()

    # Formatage de la sortie
    formatted_output = format_pdf_fields(output)

    #print( formatted_output )

    # Lecture du fichier texte
    questionnaire_data = parse_questionnaire(file_path)

    #print(questionnaire_data)

    # Analyse des réponses
    dico_correction = {}
    question = 0

    for key, value in questionnaire_data.items():
        if key != 'title':
            question += 1
            key_output = f"q{question}"
            
            if value['type'] == 'text':
                possible_answers = [x for x in value['answers'].split(',')]
                if formatted_output[key_output] in possible_answers:
                    dico_correction[key_output] = True
                else:
                    dico_correction[key_output] = False
                    
            if value['type'] == 'qcm':
                if formatted_output[key_output] == value['correct_answer']:
                    dico_correction[key_output] = True
                else:
                    dico_correction[key_output] = False
                
    return dico_correction

if __name__ == '__main__':
    print( correction( 'questionnaire.txt' ) )

Ce dernier affiche:

{'q1': True, 'q2': True}

Et la suite…

Ce n’est qu’une base et si cela intéresse beaucoup de personnes, alors je pourrais faire évoluer ce projet.

On peut imaginer par exemple que chaque élève sauvegarde sont PDF avec le nom et le prénom, et l’envoie à l’enseignant(e). L’enseignant peut alors mettre les QCM dans un répertoire et faire tourner un script chargé de tout corriger.

Catégories : LaTeXPython

0 0 votes
Évaluation de l'article
S’abonner
Notification pour
guest
0 Commentaires
Le plus ancien
Le plus récent Le plus populaire
Commentaires en ligne
Afficher tous les commentaires
0
Nous aimerions avoir votre avis, veuillez laisser un commentaire.x