2025-08-30 22:41:51 +02:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
"""
|
|
|
|
Script pour générer une page web interactive de correction des erreurs orthographiques et grammaticales.
|
|
|
|
Ce script:
|
|
|
|
1. Lit le rapport d'erreurs et le fichier CSV des erreurs
|
|
|
|
2. Génère une page HTML interactive dans le dossier build
|
|
|
|
3. Permet de corriger les erreurs directement dans le fichier livre.org
|
|
|
|
4. Permet d'ajouter des mots au dictionnaire personnalisé
|
|
|
|
5. Permet de marquer des erreurs comme "à ne pas traiter"
|
|
|
|
"""
|
|
|
|
|
|
|
|
import os
|
|
|
|
import re
|
|
|
|
import json
|
|
|
|
import csv
|
|
|
|
import shutil
|
|
|
|
from datetime import datetime
|
|
|
|
|
|
|
|
# Créer le dossier build s'il n'existe pas
|
|
|
|
if not os.path.exists('build'):
|
|
|
|
os.makedirs('build')
|
|
|
|
|
|
|
|
# Créer le dossier pour les ressources statiques s'il n'existe pas
|
|
|
|
STATIC_DIR = os.path.join('build', 'static')
|
|
|
|
if not os.path.exists(STATIC_DIR):
|
|
|
|
os.makedirs(STATIC_DIR)
|
|
|
|
|
|
|
|
# Créer les sous-dossiers pour CSS et JS
|
|
|
|
CSS_DIR = os.path.join(STATIC_DIR, 'css')
|
|
|
|
JS_DIR = os.path.join(STATIC_DIR, 'js')
|
|
|
|
|
|
|
|
for directory in [CSS_DIR, JS_DIR]:
|
|
|
|
if not os.path.exists(directory):
|
|
|
|
os.makedirs(directory)
|
|
|
|
|
|
|
|
def parse_error_report(report_path):
|
|
|
|
"""
|
|
|
|
Parse le rapport d'erreurs pour extraire les informations sur les erreurs.
|
|
|
|
"""
|
|
|
|
with open(report_path, 'r', encoding='utf-8') as file:
|
|
|
|
content = file.read()
|
|
|
|
|
|
|
|
# Extraire les sections par chapitre
|
|
|
|
chapter_pattern = r'## Chapitre: (.*?)\n\n(.*?)(?=\n---|\Z)'
|
|
|
|
chapters = re.findall(chapter_pattern, content, re.DOTALL)
|
|
|
|
|
|
|
|
errors_by_chapter = {}
|
|
|
|
|
|
|
|
for chapter_title, chapter_content in chapters:
|
|
|
|
# Extraire les erreurs d'orthographe
|
|
|
|
spelling_pattern = r'### Erreurs d\'orthographe\n\n(.*?)(?=\n\n### Erreurs grammaticales|\Z)'
|
|
|
|
spelling_match = re.search(spelling_pattern, chapter_content, re.DOTALL)
|
|
|
|
|
|
|
|
spelling_errors = []
|
|
|
|
if spelling_match and "Aucune erreur d'orthographe détectée" not in spelling_match.group(1):
|
|
|
|
spelling_text = spelling_match.group(1)
|
2025-08-31 22:37:24 +02:00
|
|
|
|
|
|
|
# Nouveau motif pour extraire les erreurs d'orthographe avec contexte étendu
|
|
|
|
word_pattern = r'- \*\*(.*?)\*\*: (.*?)(?=\n- \*\*|\Z)'
|
|
|
|
for word_match in re.finditer(word_pattern, spelling_text, re.DOTALL):
|
|
|
|
word = word_match.group(1)
|
|
|
|
suggestions_text = word_match.group(2).strip()
|
2025-08-30 22:41:51 +02:00
|
|
|
suggestions = []
|
|
|
|
if suggestions_text != "Aucune suggestion":
|
|
|
|
suggestions = [s.strip() for s in suggestions_text.split(',')]
|
|
|
|
|
2025-08-31 22:37:24 +02:00
|
|
|
# Extraire les occurrences pour ce mot
|
|
|
|
occurrence_text = word_match.group(0)
|
|
|
|
occurrence_pattern = r' - \*\*Occurrence (\d+)\*\*:\n - \*\*Contexte\*\*: (.*?)\n - \*\*Contexte étendu\*\*: ```\n(.*?)\n```'
|
|
|
|
|
|
|
|
occurrences = re.finditer(occurrence_pattern, occurrence_text, re.DOTALL)
|
|
|
|
|
|
|
|
# S'il y a des occurrences, les traiter
|
|
|
|
occurrences_found = False
|
|
|
|
for occ_match in occurrences:
|
|
|
|
occurrences_found = True
|
|
|
|
occ_num = occ_match.group(1)
|
|
|
|
context = occ_match.group(2).strip()
|
|
|
|
extended_context = occ_match.group(3).strip()
|
|
|
|
|
|
|
|
# Extraire le texte en gras du contexte (le mot mal orthographié)
|
|
|
|
error_text_match = re.search(r'\*\*(.*?)\*\*', context)
|
|
|
|
error_text = error_text_match.group(1) if error_text_match else word
|
|
|
|
|
|
|
|
# Nettoyer le contexte pour l'affichage
|
|
|
|
clean_context = re.sub(r'\*\*(.*?)\*\*', r'\1', context)
|
|
|
|
|
|
|
|
spelling_errors.append({
|
|
|
|
'word': word,
|
|
|
|
'context': clean_context,
|
|
|
|
'extended_context': extended_context,
|
|
|
|
'error_text': error_text,
|
|
|
|
'suggestions': suggestions,
|
|
|
|
'type': 'spelling'
|
|
|
|
})
|
|
|
|
|
|
|
|
# Si aucune occurrence n'a été trouvée, ajouter une entrée simple
|
|
|
|
if not occurrences_found:
|
|
|
|
spelling_errors.append({
|
|
|
|
'word': word,
|
|
|
|
'suggestions': suggestions,
|
|
|
|
'type': 'spelling'
|
|
|
|
})
|
2025-08-30 22:41:51 +02:00
|
|
|
|
|
|
|
# Extraire les erreurs grammaticales
|
|
|
|
grammar_pattern = r'### Erreurs grammaticales\n\n(.*?)(?=\n\n---|\Z)'
|
|
|
|
grammar_match = re.search(grammar_pattern, chapter_content, re.DOTALL)
|
|
|
|
|
|
|
|
grammar_errors = []
|
|
|
|
if grammar_match and "Aucune erreur grammaticale détectée" not in grammar_match.group(1):
|
|
|
|
grammar_text = grammar_match.group(1)
|
2025-08-31 22:37:24 +02:00
|
|
|
|
|
|
|
# Nouveau motif pour extraire les erreurs grammaticales avec contexte étendu
|
|
|
|
error_pattern = r'- \*\*Erreur (\d+)\*\*: (.*?)\n - \*\*Contexte\*\*: (.*?)(?:\n - \*\*Contexte étendu\*\*: ```\n(.*?)\n```)?(?:\n - \*\*Suggestions\*\*: (.*?))?(?=\n\n- \*\*Erreur|\Z)'
|
|
|
|
|
2025-08-30 22:41:51 +02:00
|
|
|
for error_match in re.finditer(error_pattern, grammar_text, re.DOTALL):
|
2025-08-31 22:37:24 +02:00
|
|
|
error_num = error_match.group(1)
|
|
|
|
message = error_match.group(2).strip()
|
|
|
|
context = error_match.group(3).strip()
|
|
|
|
extended_context = error_match.group(4).strip() if error_match.group(4) else None
|
|
|
|
suggestions_text = error_match.group(5).strip() if error_match.group(5) else "Aucune suggestion"
|
2025-08-30 22:41:51 +02:00
|
|
|
|
|
|
|
# Extraire le texte en gras du contexte (l'erreur elle-même)
|
|
|
|
error_text_match = re.search(r'\*\*(.*?)\*\*', context)
|
|
|
|
error_text = error_text_match.group(1) if error_text_match else ""
|
|
|
|
|
|
|
|
# Nettoyer le contexte pour l'affichage
|
|
|
|
clean_context = re.sub(r'\*\*(.*?)\*\*', r'\1', context)
|
|
|
|
|
|
|
|
suggestions = []
|
|
|
|
if suggestions_text != "Aucune suggestion":
|
|
|
|
suggestions = [s.strip() for s in suggestions_text.split(',')]
|
|
|
|
|
2025-08-31 22:37:24 +02:00
|
|
|
grammar_error = {
|
2025-08-30 22:41:51 +02:00
|
|
|
'message': message,
|
|
|
|
'context': clean_context,
|
|
|
|
'error_text': error_text,
|
|
|
|
'suggestions': suggestions,
|
|
|
|
'type': 'grammar'
|
2025-08-31 22:37:24 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
# Ajouter le contexte étendu s'il existe
|
|
|
|
if extended_context:
|
|
|
|
grammar_error['extended_context'] = extended_context
|
|
|
|
|
|
|
|
grammar_errors.append(grammar_error)
|
2025-08-30 22:41:51 +02:00
|
|
|
|
|
|
|
errors_by_chapter[chapter_title] = {
|
|
|
|
'spelling': spelling_errors,
|
|
|
|
'grammar': grammar_errors
|
|
|
|
}
|
|
|
|
|
|
|
|
return errors_by_chapter
|
|
|
|
|
|
|
|
def create_css():
|
|
|
|
"""Crée le fichier CSS pour la page de corrections."""
|
|
|
|
css_content = """
|
|
|
|
body {
|
|
|
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
|
|
|
margin: 0;
|
|
|
|
padding: 0;
|
|
|
|
background-color: #f5f5f5;
|
|
|
|
color: #333;
|
|
|
|
}
|
|
|
|
|
|
|
|
.container {
|
|
|
|
max-width: 1200px;
|
|
|
|
margin: 0 auto;
|
|
|
|
padding: 20px;
|
|
|
|
}
|
|
|
|
|
|
|
|
header {
|
|
|
|
background-color: #2c3e50;
|
|
|
|
color: white;
|
|
|
|
padding: 20px;
|
|
|
|
text-align: center;
|
|
|
|
margin-bottom: 30px;
|
|
|
|
}
|
|
|
|
|
2025-08-31 22:37:24 +02:00
|
|
|
h1, h2, h3, h4, h5 {
|
2025-08-30 22:41:51 +02:00
|
|
|
margin-top: 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
.chapter-section {
|
|
|
|
background-color: white;
|
|
|
|
border-radius: 8px;
|
|
|
|
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
|
|
|
padding: 20px;
|
|
|
|
margin-bottom: 30px;
|
|
|
|
}
|
|
|
|
|
|
|
|
.error-card {
|
|
|
|
border: 1px solid #ddd;
|
|
|
|
border-radius: 5px;
|
|
|
|
padding: 15px;
|
|
|
|
margin-bottom: 15px;
|
|
|
|
background-color: #fff;
|
|
|
|
}
|
|
|
|
|
|
|
|
.error-card.spelling {
|
|
|
|
border-left: 5px solid #3498db;
|
|
|
|
}
|
|
|
|
|
|
|
|
.error-card.grammar {
|
|
|
|
border-left: 5px solid #e74c3c;
|
|
|
|
}
|
|
|
|
|
|
|
|
.error-context {
|
|
|
|
background-color: #f9f9f9;
|
|
|
|
padding: 10px;
|
|
|
|
border-radius: 5px;
|
|
|
|
margin: 10px 0;
|
|
|
|
font-family: monospace;
|
|
|
|
white-space: pre-wrap;
|
|
|
|
line-height: 1.5;
|
|
|
|
}
|
|
|
|
|
2025-08-31 22:37:24 +02:00
|
|
|
.extended-context {
|
|
|
|
background-color: #f0f0f0;
|
|
|
|
padding: 10px;
|
|
|
|
border-radius: 5px;
|
|
|
|
margin: 10px 0;
|
|
|
|
border-left: 3px solid #7f8c8d;
|
|
|
|
}
|
|
|
|
|
|
|
|
.extended-context h5 {
|
|
|
|
margin-top: 0;
|
|
|
|
color: #7f8c8d;
|
|
|
|
font-size: 14px;
|
|
|
|
}
|
|
|
|
|
|
|
|
.extended-context pre {
|
|
|
|
margin: 0;
|
|
|
|
font-family: monospace;
|
|
|
|
white-space: pre-wrap;
|
|
|
|
line-height: 1.5;
|
|
|
|
font-size: 13px;
|
|
|
|
color: #555;
|
|
|
|
}
|
|
|
|
|
2025-08-30 22:41:51 +02:00
|
|
|
.error-word {
|
|
|
|
font-weight: bold;
|
|
|
|
color: #e74c3c;
|
|
|
|
text-decoration: underline;
|
|
|
|
text-decoration-style: wavy;
|
|
|
|
text-decoration-color: #e74c3c;
|
|
|
|
}
|
|
|
|
|
|
|
|
.suggestion-buttons {
|
|
|
|
display: flex;
|
|
|
|
flex-wrap: wrap;
|
|
|
|
gap: 10px;
|
|
|
|
margin-top: 10px;
|
|
|
|
}
|
|
|
|
|
|
|
|
.suggestion-btn {
|
|
|
|
background-color: #3498db;
|
|
|
|
color: white;
|
|
|
|
border: none;
|
|
|
|
border-radius: 4px;
|
|
|
|
padding: 5px 10px;
|
|
|
|
cursor: pointer;
|
|
|
|
font-size: 14px;
|
|
|
|
transition: background-color 0.3s;
|
|
|
|
}
|
|
|
|
|
|
|
|
.suggestion-btn:hover {
|
|
|
|
background-color: #2980b9;
|
|
|
|
}
|
|
|
|
|
2025-08-31 22:37:24 +02:00
|
|
|
.custom-correction {
|
|
|
|
margin-top: 15px;
|
|
|
|
padding: 10px;
|
|
|
|
background-color: #f9f9f9;
|
|
|
|
border-radius: 5px;
|
|
|
|
}
|
|
|
|
|
|
|
|
.custom-correction h5 {
|
|
|
|
margin-top: 0;
|
|
|
|
margin-bottom: 10px;
|
|
|
|
color: #555;
|
|
|
|
font-size: 14px;
|
|
|
|
}
|
|
|
|
|
|
|
|
.input-group {
|
|
|
|
display: flex;
|
|
|
|
gap: 10px;
|
|
|
|
}
|
|
|
|
|
|
|
|
.custom-input {
|
|
|
|
flex-grow: 1;
|
|
|
|
padding: 8px;
|
|
|
|
border: 1px solid #ddd;
|
|
|
|
border-radius: 4px;
|
|
|
|
font-size: 14px;
|
|
|
|
}
|
|
|
|
|
|
|
|
.custom-btn {
|
|
|
|
background-color: #9b59b6;
|
|
|
|
color: white;
|
|
|
|
border: none;
|
|
|
|
border-radius: 4px;
|
|
|
|
padding: 8px 15px;
|
|
|
|
cursor: pointer;
|
|
|
|
font-size: 14px;
|
|
|
|
transition: background-color 0.3s;
|
|
|
|
}
|
|
|
|
|
|
|
|
.custom-btn:hover {
|
|
|
|
background-color: #8e44ad;
|
|
|
|
}
|
|
|
|
|
2025-08-30 22:41:51 +02:00
|
|
|
.action-buttons {
|
|
|
|
display: flex;
|
|
|
|
gap: 10px;
|
|
|
|
margin-top: 15px;
|
|
|
|
}
|
|
|
|
|
|
|
|
.action-btn {
|
|
|
|
background-color: #2ecc71;
|
|
|
|
color: white;
|
|
|
|
border: none;
|
|
|
|
border-radius: 4px;
|
|
|
|
padding: 8px 15px;
|
|
|
|
cursor: pointer;
|
|
|
|
font-size: 14px;
|
|
|
|
transition: background-color 0.3s;
|
|
|
|
}
|
|
|
|
|
|
|
|
.action-btn.ignore {
|
|
|
|
background-color: #95a5a6;
|
|
|
|
}
|
|
|
|
|
|
|
|
.action-btn.dictionary {
|
|
|
|
background-color: #f39c12;
|
|
|
|
}
|
|
|
|
|
|
|
|
.action-btn:hover {
|
|
|
|
opacity: 0.9;
|
|
|
|
}
|
|
|
|
|
|
|
|
.success-message {
|
|
|
|
display: none;
|
|
|
|
background-color: #2ecc71;
|
|
|
|
color: white;
|
|
|
|
padding: 10px;
|
|
|
|
border-radius: 5px;
|
|
|
|
margin-top: 10px;
|
|
|
|
text-align: center;
|
|
|
|
}
|
|
|
|
|
|
|
|
.error-message {
|
|
|
|
display: none;
|
|
|
|
background-color: #e74c3c;
|
|
|
|
color: white;
|
|
|
|
padding: 10px;
|
|
|
|
border-radius: 5px;
|
|
|
|
margin-top: 10px;
|
|
|
|
text-align: center;
|
|
|
|
}
|
|
|
|
|
|
|
|
.tabs {
|
|
|
|
display: flex;
|
|
|
|
margin-bottom: 20px;
|
|
|
|
}
|
|
|
|
|
|
|
|
.tab {
|
|
|
|
padding: 10px 20px;
|
|
|
|
background-color: #ddd;
|
|
|
|
cursor: pointer;
|
|
|
|
border-radius: 5px 5px 0 0;
|
|
|
|
margin-right: 5px;
|
|
|
|
}
|
|
|
|
|
|
|
|
.tab.active {
|
|
|
|
background-color: white;
|
|
|
|
border-bottom: 2px solid #3498db;
|
|
|
|
}
|
|
|
|
|
|
|
|
.tab-content {
|
|
|
|
display: none;
|
|
|
|
}
|
|
|
|
|
|
|
|
.tab-content.active {
|
|
|
|
display: block;
|
|
|
|
}
|
|
|
|
"""
|
|
|
|
|
|
|
|
with open(os.path.join(CSS_DIR, 'corrections.css'), 'w', encoding='utf-8') as f:
|
|
|
|
f.write(css_content)
|
|
|
|
|
|
|
|
def create_js(errors_by_chapter):
|
|
|
|
"""Crée le fichier JavaScript pour la page de corrections."""
|
|
|
|
# Convertir les données en JSON pour les utiliser dans le JavaScript
|
|
|
|
errors_json = json.dumps(errors_by_chapter, ensure_ascii=False)
|
|
|
|
|
|
|
|
js_content = f"""
|
|
|
|
// Données des erreurs
|
|
|
|
const errorsByChapter = {errors_json};
|
|
|
|
|
|
|
|
// Fonction pour appliquer une correction
|
|
|
|
function applyCorrection(errorType, chapterTitle, errorIndex, correction) {{
|
|
|
|
// Préparer les données à envoyer
|
|
|
|
const data = {{
|
|
|
|
action: 'apply_correction',
|
|
|
|
error_type: errorType,
|
|
|
|
chapter: chapterTitle,
|
|
|
|
error_index: errorIndex,
|
|
|
|
correction: correction
|
|
|
|
}};
|
|
|
|
|
|
|
|
// Envoyer la requête au serveur
|
|
|
|
fetch('/api/corrections', {{
|
|
|
|
method: 'POST',
|
|
|
|
headers: {{
|
|
|
|
'Content-Type': 'application/json',
|
|
|
|
}},
|
|
|
|
body: JSON.stringify(data)
|
|
|
|
}})
|
|
|
|
.then(response => response.json())
|
|
|
|
.then(data => {{
|
|
|
|
if (data.success) {{
|
|
|
|
// Afficher un message de succès
|
|
|
|
const card = document.querySelector(`#${{errorType}}-${{chapterTitle.replace(/\\s+/g, '-')}}-${{errorIndex}}`);
|
|
|
|
const successMsg = card.querySelector('.success-message');
|
|
|
|
successMsg.textContent = 'Correction appliquée avec succès!';
|
|
|
|
successMsg.style.display = 'block';
|
|
|
|
|
|
|
|
// Masquer le message après 3 secondes
|
|
|
|
setTimeout(() => {{
|
|
|
|
successMsg.style.display = 'none';
|
|
|
|
}}, 3000);
|
|
|
|
|
|
|
|
// Désactiver les boutons de suggestion
|
|
|
|
const suggestionBtns = card.querySelectorAll('.suggestion-btn');
|
|
|
|
suggestionBtns.forEach(btn => {{
|
|
|
|
btn.disabled = true;
|
|
|
|
btn.style.opacity = '0.5';
|
|
|
|
}});
|
2025-08-31 22:37:24 +02:00
|
|
|
|
|
|
|
// Désactiver le champ de correction personnalisée
|
|
|
|
const customInput = card.querySelector('.custom-input');
|
|
|
|
const customBtn = card.querySelector('.custom-btn');
|
|
|
|
if (customInput) customInput.disabled = true;
|
|
|
|
if (customBtn) customBtn.disabled = true;
|
2025-08-30 22:41:51 +02:00
|
|
|
}} else {{
|
|
|
|
// Afficher un message d'erreur
|
|
|
|
const card = document.querySelector(`#${{errorType}}-${{chapterTitle.replace(/\\s+/g, '-')}}-${{errorIndex}}`);
|
|
|
|
const errorMsg = card.querySelector('.error-message');
|
|
|
|
errorMsg.textContent = data.message || 'Erreur lors de l\\'application de la correction.';
|
|
|
|
errorMsg.style.display = 'block';
|
|
|
|
|
|
|
|
// Masquer le message après 3 secondes
|
|
|
|
setTimeout(() => {{
|
|
|
|
errorMsg.style.display = 'none';
|
|
|
|
}}, 3000);
|
|
|
|
}}
|
|
|
|
}})
|
|
|
|
.catch(error => {{
|
|
|
|
console.error('Erreur:', error);
|
|
|
|
alert('Une erreur est survenue lors de la communication avec le serveur.');
|
|
|
|
}});
|
|
|
|
}}
|
|
|
|
|
2025-08-31 22:37:24 +02:00
|
|
|
// Fonction pour appliquer une correction personnalisée
|
|
|
|
function applyCustomCorrection(errorType, chapterTitle, errorIndex) {{
|
|
|
|
// Récupérer la valeur de la correction personnalisée
|
|
|
|
const inputId = `custom-${{errorType}}-${{chapterTitle.replace(/\\s+/g, '-')}}-${{errorIndex}}`;
|
|
|
|
const customInput = document.getElementById(inputId);
|
|
|
|
|
|
|
|
if (!customInput || !customInput.value.trim()) {{
|
|
|
|
alert('Veuillez entrer une correction.');
|
|
|
|
return;
|
|
|
|
}}
|
|
|
|
|
|
|
|
// Appliquer la correction
|
|
|
|
applyCorrection(errorType, chapterTitle, errorIndex, customInput.value.trim());
|
|
|
|
}}
|
|
|
|
|
|
|
|
// Ajouter des écouteurs d'événements pour les champs de saisie personnalisée
|
|
|
|
function setupCustomInputs() {{
|
|
|
|
const customInputs = document.querySelectorAll('.custom-input');
|
|
|
|
customInputs.forEach(input => {{
|
|
|
|
input.addEventListener('keypress', (e) => {{
|
|
|
|
if (e.key === 'Enter') {{
|
|
|
|
// Extraire les informations de l'ID
|
|
|
|
const inputId = input.id;
|
|
|
|
const parts = inputId.replace('custom-', '').split('-');
|
|
|
|
const errorType = parts[0];
|
|
|
|
const errorIndex = parts[parts.length - 1];
|
|
|
|
const chapterTitle = parts.slice(1, parts.length - 1).join(' ');
|
|
|
|
|
|
|
|
// Appliquer la correction
|
|
|
|
applyCustomCorrection(errorType, chapterTitle, errorIndex);
|
|
|
|
}}
|
|
|
|
}});
|
|
|
|
}});
|
|
|
|
}}
|
|
|
|
|
2025-08-30 22:41:51 +02:00
|
|
|
// Fonction pour ignorer une erreur
|
|
|
|
function ignoreError(errorType, chapterTitle, errorIndex) {{
|
|
|
|
// Préparer les données à envoyer
|
|
|
|
const data = {{
|
|
|
|
action: 'ignore_error',
|
|
|
|
error_type: errorType,
|
|
|
|
chapter: chapterTitle,
|
|
|
|
error_index: errorIndex
|
|
|
|
}};
|
|
|
|
|
|
|
|
// Envoyer la requête au serveur
|
|
|
|
fetch('/api/corrections', {{
|
|
|
|
method: 'POST',
|
|
|
|
headers: {{
|
|
|
|
'Content-Type': 'application/json',
|
|
|
|
}},
|
|
|
|
body: JSON.stringify(data)
|
|
|
|
}})
|
|
|
|
.then(response => response.json())
|
|
|
|
.then(data => {{
|
|
|
|
if (data.success) {{
|
|
|
|
// Afficher un message de succès
|
|
|
|
const card = document.querySelector(`#${{errorType}}-${{chapterTitle.replace(/\\s+/g, '-')}}-${{errorIndex}}`);
|
|
|
|
const successMsg = card.querySelector('.success-message');
|
|
|
|
successMsg.textContent = 'Erreur ignorée avec succès!';
|
|
|
|
successMsg.style.display = 'block';
|
|
|
|
|
|
|
|
// Masquer le message après 3 secondes
|
|
|
|
setTimeout(() => {{
|
|
|
|
card.style.display = 'none';
|
|
|
|
}}, 1000);
|
|
|
|
}} else {{
|
|
|
|
// Afficher un message d'erreur
|
|
|
|
const card = document.querySelector(`#${{errorType}}-${{chapterTitle.replace(/\\s+/g, '-')}}-${{errorIndex}}`);
|
|
|
|
const errorMsg = card.querySelector('.error-message');
|
|
|
|
errorMsg.textContent = data.message || 'Erreur lors de l\\'ignorance de l\\'erreur.';
|
|
|
|
errorMsg.style.display = 'block';
|
|
|
|
|
|
|
|
// Masquer le message après 3 secondes
|
|
|
|
setTimeout(() => {{
|
|
|
|
errorMsg.style.display = 'none';
|
|
|
|
}}, 3000);
|
|
|
|
}}
|
|
|
|
}})
|
|
|
|
.catch(error => {{
|
|
|
|
console.error('Erreur:', error);
|
|
|
|
alert('Une erreur est survenue lors de la communication avec le serveur.');
|
|
|
|
}});
|
|
|
|
}}
|
|
|
|
|
|
|
|
// Fonction pour ajouter un mot au dictionnaire personnalisé
|
|
|
|
function addToDictionary(word) {{
|
|
|
|
// Préparer les données à envoyer
|
|
|
|
const data = {{
|
|
|
|
action: 'add_to_dictionary',
|
|
|
|
word: word
|
|
|
|
}};
|
|
|
|
|
|
|
|
// Envoyer la requête au serveur
|
|
|
|
fetch('/api/corrections', {{
|
|
|
|
method: 'POST',
|
|
|
|
headers: {{
|
|
|
|
'Content-Type': 'application/json',
|
|
|
|
}},
|
|
|
|
body: JSON.stringify(data)
|
|
|
|
}})
|
|
|
|
.then(response => response.json())
|
|
|
|
.then(data => {{
|
|
|
|
if (data.success) {{
|
|
|
|
// Masquer toutes les cartes d'erreur pour ce mot
|
|
|
|
const cards = document.querySelectorAll(`.error-card[data-word="${{word}}"]`);
|
|
|
|
cards.forEach(card => {{
|
|
|
|
const successMsg = card.querySelector('.success-message');
|
|
|
|
successMsg.textContent = 'Mot ajouté au dictionnaire personnalisé!';
|
|
|
|
successMsg.style.display = 'block';
|
|
|
|
|
|
|
|
// Masquer la carte après 1 seconde
|
|
|
|
setTimeout(() => {{
|
|
|
|
card.style.display = 'none';
|
|
|
|
}}, 1000);
|
|
|
|
}});
|
|
|
|
}} else {{
|
|
|
|
// Afficher un message d'erreur
|
|
|
|
alert(data.message || 'Erreur lors de l\\'ajout du mot au dictionnaire.');
|
|
|
|
}}
|
|
|
|
}})
|
|
|
|
.catch(error => {{
|
|
|
|
console.error('Erreur:', error);
|
|
|
|
alert('Une erreur est survenue lors de la communication avec le serveur.');
|
|
|
|
}});
|
|
|
|
}}
|
|
|
|
|
|
|
|
// Fonction pour changer d'onglet
|
|
|
|
function changeTab(tabName) {{
|
|
|
|
// Masquer tous les contenus d'onglets
|
|
|
|
const tabContents = document.querySelectorAll('.tab-content');
|
|
|
|
tabContents.forEach(content => {{
|
|
|
|
content.classList.remove('active');
|
|
|
|
}});
|
|
|
|
|
|
|
|
// Désactiver tous les onglets
|
|
|
|
const tabs = document.querySelectorAll('.tab');
|
|
|
|
tabs.forEach(tab => {{
|
|
|
|
tab.classList.remove('active');
|
|
|
|
}});
|
|
|
|
|
|
|
|
// Activer l'onglet sélectionné
|
|
|
|
document.getElementById(tabName).classList.add('active');
|
|
|
|
document.querySelector(`.tab[data-tab="${{tabName}}"]`).classList.add('active');
|
|
|
|
}}
|
|
|
|
|
|
|
|
// Initialiser la page quand elle est chargée
|
|
|
|
document.addEventListener('DOMContentLoaded', () => {{
|
|
|
|
// Activer le premier onglet par défaut
|
|
|
|
changeTab('spelling-tab');
|
|
|
|
|
|
|
|
// Ajouter des écouteurs d'événements pour les onglets
|
|
|
|
const tabs = document.querySelectorAll('.tab');
|
|
|
|
tabs.forEach(tab => {{
|
|
|
|
tab.addEventListener('click', () => {{
|
|
|
|
changeTab(tab.getAttribute('data-tab'));
|
|
|
|
}});
|
|
|
|
}});
|
2025-08-31 22:37:24 +02:00
|
|
|
|
|
|
|
// Configurer les champs de saisie personnalisée
|
|
|
|
setupCustomInputs();
|
2025-08-30 22:41:51 +02:00
|
|
|
}});
|
|
|
|
"""
|
|
|
|
|
|
|
|
with open(os.path.join(JS_DIR, 'corrections.js'), 'w', encoding='utf-8') as f:
|
|
|
|
f.write(js_content)
|
|
|
|
|
|
|
|
def create_html(errors_by_chapter):
|
|
|
|
"""Crée le fichier HTML pour la page de corrections."""
|
|
|
|
html_content = f"""<!DOCTYPE html>
|
|
|
|
<html lang="fr">
|
|
|
|
<head>
|
|
|
|
<meta charset="UTF-8">
|
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
|
|
<title>Corrections Orthographiques et Grammaticales</title>
|
|
|
|
<link rel="stylesheet" href="static/css/corrections.css">
|
|
|
|
</head>
|
|
|
|
<body>
|
|
|
|
<header>
|
|
|
|
<h1>Corrections Orthographiques et Grammaticales</h1>
|
|
|
|
<p>Dernière mise à jour: {datetime.now().strftime('%Y-%m-%d %H:%M')}</p>
|
|
|
|
</header>
|
|
|
|
|
|
|
|
<div class="container">
|
|
|
|
<div class="tabs">
|
|
|
|
<div class="tab active" data-tab="spelling-tab">Erreurs d'orthographe</div>
|
|
|
|
<div class="tab" data-tab="grammar-tab">Erreurs grammaticales</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div id="spelling-tab" class="tab-content active">
|
|
|
|
<h2>Erreurs d'orthographe</h2>
|
|
|
|
|
|
|
|
{generate_spelling_html(errors_by_chapter)}
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div id="grammar-tab" class="tab-content">
|
|
|
|
<h2>Erreurs grammaticales</h2>
|
|
|
|
|
|
|
|
{generate_grammar_html(errors_by_chapter)}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<script src="static/js/corrections.js"></script>
|
|
|
|
</body>
|
|
|
|
</html>
|
|
|
|
"""
|
|
|
|
|
|
|
|
with open(os.path.join('build', 'corrections.html'), 'w', encoding='utf-8') as f:
|
|
|
|
f.write(html_content)
|
|
|
|
|
|
|
|
def generate_spelling_html(errors_by_chapter):
|
|
|
|
"""Génère le HTML pour les erreurs d'orthographe."""
|
|
|
|
html = ""
|
|
|
|
|
|
|
|
for chapter_title, errors in errors_by_chapter.items():
|
|
|
|
spelling_errors = errors['spelling']
|
|
|
|
|
|
|
|
if spelling_errors:
|
|
|
|
html += f"""
|
|
|
|
<div class="chapter-section">
|
|
|
|
<h3>{chapter_title}</h3>
|
|
|
|
"""
|
|
|
|
|
|
|
|
for i, error in enumerate(spelling_errors):
|
|
|
|
word = error['word']
|
|
|
|
suggestions = error['suggestions']
|
|
|
|
|
|
|
|
html += f"""
|
|
|
|
<div id="spelling-{chapter_title.replace(' ', '-')}-{i}" class="error-card spelling" data-word="{word}">
|
|
|
|
<h4>Mot mal orthographié: <span class="error-word">{word}</span></h4>
|
2025-08-31 22:37:24 +02:00
|
|
|
"""
|
|
|
|
|
|
|
|
# Ajouter le contexte s'il existe
|
|
|
|
if 'context' in error:
|
|
|
|
html += f"""
|
|
|
|
<div class="error-context">
|
|
|
|
{error['context'].replace(word, f'<span class="error-word">{word}</span>')}
|
|
|
|
</div>
|
|
|
|
"""
|
|
|
|
|
|
|
|
# Ajouter le contexte étendu s'il existe
|
|
|
|
if 'extended_context' in error:
|
|
|
|
html += f"""
|
|
|
|
<div class="extended-context">
|
|
|
|
<h5>Contexte étendu:</h5>
|
|
|
|
<pre>{error['extended_context']}</pre>
|
|
|
|
</div>
|
|
|
|
"""
|
|
|
|
|
|
|
|
html += f"""
|
2025-08-30 22:41:51 +02:00
|
|
|
<div class="suggestion-buttons">
|
|
|
|
"""
|
|
|
|
|
|
|
|
if suggestions:
|
|
|
|
for suggestion in suggestions:
|
|
|
|
html += f"""
|
|
|
|
<button class="suggestion-btn" onclick="applyCorrection('spelling', '{chapter_title}', {i}, '{suggestion}')">{suggestion}</button>
|
|
|
|
"""
|
|
|
|
else:
|
|
|
|
html += """
|
|
|
|
<p>Aucune suggestion disponible</p>
|
|
|
|
"""
|
|
|
|
|
|
|
|
html += f"""
|
|
|
|
</div>
|
|
|
|
|
2025-08-31 22:37:24 +02:00
|
|
|
<div class="custom-correction">
|
|
|
|
<h5>Correction personnalisée:</h5>
|
|
|
|
<div class="input-group">
|
|
|
|
<input type="text" id="custom-spelling-{chapter_title.replace(' ', '-')}-{i}" class="custom-input" placeholder="Entrez votre correction">
|
|
|
|
<button class="custom-btn" onclick="applyCustomCorrection('spelling', '{chapter_title}', {i})">Appliquer</button>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
2025-08-30 22:41:51 +02:00
|
|
|
<div class="action-buttons">
|
|
|
|
<button class="action-btn ignore" onclick="ignoreError('spelling', '{chapter_title}', {i})">Ignorer cette erreur</button>
|
|
|
|
<button class="action-btn dictionary" onclick="addToDictionary('{word}')">Ajouter au dictionnaire</button>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="success-message"></div>
|
|
|
|
<div class="error-message"></div>
|
|
|
|
</div>
|
|
|
|
"""
|
|
|
|
|
|
|
|
html += """
|
|
|
|
</div>
|
|
|
|
"""
|
|
|
|
|
|
|
|
return html
|
|
|
|
|
|
|
|
def generate_grammar_html(errors_by_chapter):
|
|
|
|
"""Génère le HTML pour les erreurs grammaticales."""
|
|
|
|
html = ""
|
|
|
|
|
|
|
|
for chapter_title, errors in errors_by_chapter.items():
|
|
|
|
grammar_errors = errors['grammar']
|
|
|
|
|
|
|
|
if grammar_errors:
|
|
|
|
html += f"""
|
|
|
|
<div class="chapter-section">
|
|
|
|
<h3>{chapter_title}</h3>
|
|
|
|
"""
|
|
|
|
|
|
|
|
for i, error in enumerate(grammar_errors):
|
|
|
|
message = error['message']
|
|
|
|
context = error['context']
|
|
|
|
error_text = error['error_text']
|
|
|
|
suggestions = error['suggestions']
|
|
|
|
|
|
|
|
# Mettre en évidence l'erreur dans le contexte
|
|
|
|
highlighted_context = context.replace(error_text, f'<span class="error-word">{error_text}</span>')
|
|
|
|
|
|
|
|
html += f"""
|
|
|
|
<div id="grammar-{chapter_title.replace(' ', '-')}-{i}" class="error-card grammar">
|
|
|
|
<h4>{message}</h4>
|
|
|
|
|
|
|
|
<div class="error-context">
|
|
|
|
{highlighted_context}
|
|
|
|
</div>
|
2025-08-31 22:37:24 +02:00
|
|
|
"""
|
|
|
|
|
|
|
|
# Ajouter le contexte étendu s'il existe
|
|
|
|
if 'extended_context' in error:
|
|
|
|
html += f"""
|
|
|
|
<div class="extended-context">
|
|
|
|
<h5>Contexte étendu:</h5>
|
|
|
|
<pre>{error['extended_context']}</pre>
|
|
|
|
</div>
|
|
|
|
"""
|
|
|
|
|
|
|
|
html += f"""
|
2025-08-30 22:41:51 +02:00
|
|
|
<div class="suggestion-buttons">
|
|
|
|
"""
|
|
|
|
|
|
|
|
if suggestions:
|
|
|
|
for suggestion in suggestions:
|
|
|
|
html += f"""
|
|
|
|
<button class="suggestion-btn" onclick="applyCorrection('grammar', '{chapter_title}', {i}, '{suggestion}')">{suggestion}</button>
|
|
|
|
"""
|
|
|
|
else:
|
|
|
|
html += """
|
|
|
|
<p>Aucune suggestion disponible</p>
|
|
|
|
"""
|
|
|
|
|
|
|
|
html += f"""
|
|
|
|
</div>
|
|
|
|
|
2025-08-31 22:37:24 +02:00
|
|
|
<div class="custom-correction">
|
|
|
|
<h5>Correction personnalisée:</h5>
|
|
|
|
<div class="input-group">
|
|
|
|
<input type="text" id="custom-grammar-{chapter_title.replace(' ', '-')}-{i}" class="custom-input" placeholder="Entrez votre correction">
|
|
|
|
<button class="custom-btn" onclick="applyCustomCorrection('grammar', '{chapter_title}', {i})">Appliquer</button>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
2025-08-30 22:41:51 +02:00
|
|
|
<div class="action-buttons">
|
|
|
|
<button class="action-btn ignore" onclick="ignoreError('grammar', '{chapter_title}', {i})">Ignorer cette erreur</button>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="success-message"></div>
|
|
|
|
<div class="error-message"></div>
|
|
|
|
</div>
|
|
|
|
"""
|
|
|
|
|
|
|
|
html += """
|
|
|
|
</div>
|
|
|
|
"""
|
|
|
|
|
|
|
|
return html
|
|
|
|
|
|
|
|
def main():
|
|
|
|
print("Génération de la page de corrections...")
|
|
|
|
|
|
|
|
# Définir les chemins des fichiers
|
|
|
|
report_path = 'rapport_orthographe_grammaire.md'
|
|
|
|
|
|
|
|
# Vérifier si le rapport existe
|
|
|
|
if not os.path.exists(report_path):
|
|
|
|
print(f"Erreur: Le fichier {report_path} n'existe pas.")
|
|
|
|
return
|
|
|
|
|
|
|
|
# Parser le rapport d'erreurs
|
|
|
|
errors_by_chapter = parse_error_report(report_path)
|
|
|
|
|
|
|
|
# Créer les fichiers CSS et JS
|
|
|
|
create_css()
|
|
|
|
create_js(errors_by_chapter)
|
|
|
|
|
|
|
|
# Créer le fichier HTML
|
|
|
|
create_html(errors_by_chapter)
|
|
|
|
|
|
|
|
print("Page de corrections générée avec succès dans le dossier 'build'.")
|
|
|
|
print("Fichier généré: build/corrections.html")
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
main()
|