#!/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) # 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() suggestions = [] if suggestions_text != "Aucune suggestion": suggestions = [s.strip() for s in suggestions_text.split(',')] # 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' }) # 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) # 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)' for error_match in re.finditer(error_pattern, grammar_text, re.DOTALL): 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" # 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(',')] grammar_error = { 'message': message, 'context': clean_context, 'error_text': error_text, 'suggestions': suggestions, 'type': 'grammar' } # Ajouter le contexte étendu s'il existe if extended_context: grammar_error['extended_context'] = extended_context grammar_errors.append(grammar_error) 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; } h1, h2, h3, h4, h5 { 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; } .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; } .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; } .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; } .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'; }}); // 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; }} 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.'); }}); }} // 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); }} }}); }}); }} // 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')); }}); }}); // Configurer les champs de saisie personnalisée setupCustomInputs(); }}); """ 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""" Corrections Orthographiques et Grammaticales

Corrections Orthographiques et Grammaticales

Dernière mise à jour: {datetime.now().strftime('%Y-%m-%d %H:%M')}

Erreurs d'orthographe
Erreurs grammaticales

Erreurs d'orthographe

{generate_spelling_html(errors_by_chapter)}

Erreurs grammaticales

{generate_grammar_html(errors_by_chapter)}
""" 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"""

{chapter_title}

""" for i, error in enumerate(spelling_errors): word = error['word'] suggestions = error['suggestions'] html += f"""

Mot mal orthographié: {word}

""" # Ajouter le contexte s'il existe if 'context' in error: html += f"""
{error['context'].replace(word, f'{word}')}
""" # Ajouter le contexte étendu s'il existe if 'extended_context' in error: html += f"""
Contexte étendu:
{error['extended_context']}
""" html += f"""
""" if suggestions: for suggestion in suggestions: html += f""" """ else: html += """

Aucune suggestion disponible

""" html += f"""
Correction personnalisée:
""" html += """
""" 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"""

{chapter_title}

""" 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'{error_text}') html += f"""

{message}

{highlighted_context}
""" # Ajouter le contexte étendu s'il existe if 'extended_context' in error: html += f"""
Contexte étendu:
{error['extended_context']}
""" html += f"""
""" if suggestions: for suggestion in suggestions: html += f""" """ else: html += """

Aucune suggestion disponible

""" html += f"""
Correction personnalisée:
""" html += """
""" 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()