diff --git a/CORRECTIONS_ENHANCEMENTS.md b/CORRECTIONS_ENHANCEMENTS.md new file mode 100644 index 0000000..5fc49b3 --- /dev/null +++ b/CORRECTIONS_ENHANCEMENTS.md @@ -0,0 +1,79 @@ +# Améliorations du système de corrections + +Ce document décrit les améliorations apportées au système de correction orthographique et grammaticale. + +## Fonctionnalités ajoutées + +### 1. Contexte étendu des erreurs + +Le système affiche maintenant plus de contexte autour des erreurs détectées, ce qui facilite la compréhension du problème et la prise de décision pour la correction. + +- **Contexte standard** : La ligne contenant l'erreur +- **Contexte étendu** : Les lignes précédente et suivante, en plus de la ligne contenant l'erreur + +Cette amélioration permet de voir l'erreur dans son contexte plus large, ce qui est particulièrement utile pour les erreurs grammaticales qui peuvent dépendre du contexte. + +### 2. Corrections personnalisées + +Une nouvelle fonctionnalité permet d'entrer des corrections personnalisées au lieu de se limiter aux suggestions automatiques. + +- Chaque erreur dispose maintenant d'un champ de saisie pour entrer une correction personnalisée +- Un bouton "Appliquer" permet d'envoyer cette correction au serveur +- La touche Entrée dans le champ de saisie applique également la correction + +Cette fonctionnalité offre plus de flexibilité pour corriger les erreurs, notamment lorsque les suggestions automatiques ne sont pas satisfaisantes. + +### 3. Gestion améliorée des dépendances + +Le système gère maintenant gracieusement l'absence des modules de vérification orthographique et grammaticale. + +- Détection automatique de la disponibilité des modules `spellchecker` et `language_tool_python` +- Messages d'avertissement clairs lorsque les modules ne sont pas disponibles +- Fonctionnement dégradé mais sans erreur en l'absence des modules + +Cette amélioration rend le système plus robuste et évite les erreurs fatales lorsque les dépendances ne sont pas installées. + +## Modifications techniques + +### Fichiers modifiés + +1. **analyse_orthographe_grammaire.py** + - Ajout de la détection des modules disponibles + - Extraction de contexte étendu pour les erreurs orthographiques et grammaticales + - Gestion des erreurs pour éviter les plantages + +2. **generate_corrections_page.py** + - Mise à jour du parser pour extraire le contexte étendu + - Ajout des champs de saisie pour les corrections personnalisées + - Mise à jour du CSS pour styliser les nouveaux éléments + +3. **CSS et JavaScript** + - Nouveaux styles pour le contexte étendu et les champs de correction personnalisée + - Nouvelle fonction JavaScript `applyCustomCorrection` pour gérer les corrections personnalisées + - Gestion des événements pour les champs de saisie (touche Entrée) + +## Utilisation + +Pour utiliser ces nouvelles fonctionnalités : + +1. Exécutez `python analyse_orthographe_grammaire.py` pour générer le rapport d'erreurs +2. Exécutez `python generate_corrections_page.py` pour générer la page de corrections +3. Lancez le serveur Flask avec `python app.py` +4. Accédez à http://localhost:5000/build/corrections.html dans votre navigateur + +Sur la page de corrections : +- Utilisez les onglets pour naviguer entre les erreurs orthographiques et grammaticales +- Cliquez sur les boutons de suggestion pour appliquer une correction suggérée +- Utilisez le champ de saisie personnalisée pour entrer votre propre correction +- Cliquez sur "Ignorer cette erreur" pour ignorer une erreur +- Pour les erreurs orthographiques, vous pouvez également ajouter le mot au dictionnaire personnalisé + +## Dépendances + +Pour profiter de toutes les fonctionnalités, assurez-vous d'installer les modules suivants : + +```bash +pip install pyspellchecker language-tool-python +``` + +Le système fonctionnera même sans ces modules, mais ne détectera pas les erreurs. \ No newline at end of file diff --git a/analyse_frequence_mots.py b/analyse_frequence_mots.py old mode 100644 new mode 100755 diff --git a/analyse_intrigues.py b/analyse_intrigues.py old mode 100644 new mode 100755 diff --git a/analyse_orthographe_grammaire.py b/analyse_orthographe_grammaire.py old mode 100644 new mode 100755 index 5154dc7..2d6a96f --- a/analyse_orthographe_grammaire.py +++ b/analyse_orthographe_grammaire.py @@ -15,8 +15,21 @@ import re import os import csv import argparse -import language_tool_python -from spellchecker import SpellChecker + +# Vérifier si les modules nécessaires sont disponibles +try: + import language_tool_python + LANGUAGE_TOOL_AVAILABLE = True +except ImportError: + print("AVERTISSEMENT: Module language_tool_python non disponible. La vérification grammaticale sera désactivée.") + LANGUAGE_TOOL_AVAILABLE = False + +try: + from spellchecker import SpellChecker + SPELLCHECKER_AVAILABLE = True +except ImportError: + print("AVERTISSEMENT: Module spellchecker non disponible. La vérification orthographique sera désactivée.") + SPELLCHECKER_AVAILABLE = False # Définir les arguments en ligne de commande parser = argparse.ArgumentParser(description='Analyser les fautes d\'orthographe et de grammaire dans un fichier Org-mode.') @@ -94,68 +107,136 @@ def check_spelling(text, lang='fr', custom_dict_path='dictionnaire_personnalise. Vérifie l'orthographe d'un texte et retourne les mots mal orthographiés. Utilise un dictionnaire personnalisé pour exclure certains mots de la vérification. """ - spell = SpellChecker(language=lang) + # Vérifier si le module spellchecker est disponible + if not SPELLCHECKER_AVAILABLE: + print("Vérification orthographique désactivée car le module spellchecker n'est pas disponible.") + return [] + + try: + spell = SpellChecker(language=lang) - # Charger le dictionnaire personnalisé - custom_words = load_custom_dictionary(custom_dict_path) + # Charger le dictionnaire personnalisé + custom_words = load_custom_dictionary(custom_dict_path) - # Ajouter les mots du dictionnaire personnalisé au dictionnaire du vérificateur - if custom_words: - spell.word_frequency.load_words(custom_words) + # Ajouter les mots du dictionnaire personnalisé au dictionnaire du vérificateur + if custom_words: + spell.word_frequency.load_words(custom_words) - # Diviser le texte en mots - words = re.findall(r'\b\w+\b', text.lower()) + # Diviser le texte en mots + words = re.findall(r'\b\w+\b', text.lower()) - # Trouver les mots mal orthographiés - misspelled = spell.unknown(words) + # Trouver les mots mal orthographiés + misspelled = spell.unknown(words) - # Créer un dictionnaire avec les mots mal orthographiés et leurs suggestions - spelling_errors = {} - for word in misspelled: - # Vérifier si le mot est dans le dictionnaire personnalisé - if word in custom_words: - continue + # Créer un dictionnaire avec les mots mal orthographiés et leurs suggestions + spelling_errors = [] + for word in misspelled: + # Vérifier si le mot est dans le dictionnaire personnalisé + if word in custom_words: + continue - # Obtenir les suggestions de correction - suggestions = spell.candidates(word) - # Limiter à 5 suggestions maximum - suggestions_list = list(suggestions) if suggestions is not None else [] - suggestions_list = suggestions_list[:5] - spelling_errors[word] = suggestions_list + # Obtenir les suggestions de correction + suggestions = spell.candidates(word) + # Limiter à 5 suggestions maximum + suggestions_list = list(suggestions) if suggestions is not None else [] + suggestions_list = suggestions_list[:5] + + # Trouver toutes les occurrences du mot dans le texte original + for match in re.finditer(r'\b' + re.escape(word) + r'\b', text, re.IGNORECASE): + # Extraire le contexte autour du mot + word_start = match.start() + word_end = match.end() + + # Trouver les limites des lignes contenant le mot + line_start = text.rfind('\n', 0, word_start) + 1 if text.rfind('\n', 0, word_start) >= 0 else 0 + line_end = text.find('\n', word_end) if text.find('\n', word_end) >= 0 else len(text) + + # Extraire les lignes précédentes et suivantes pour plus de contexte + prev_line_start = text.rfind('\n', 0, line_start - 1) + 1 if text.rfind('\n', 0, line_start - 1) >= 0 else 0 + next_line_end = text.find('\n', line_end + 1) if text.find('\n', line_end + 1) >= 0 else len(text) + + # Créer le contexte standard et étendu + context = text[line_start:line_end] + extended_context = text[prev_line_start:next_line_end] + + # Calculer les offsets pour les contextes + context_offset = word_start - line_start + extended_offset = word_start - prev_line_start + + spelling_errors.append({ + 'word': word, + 'context': context, + 'extended_context': extended_context, + 'context_offset': context_offset, + 'extended_offset': extended_offset, + 'suggestions': suggestions_list + }) - return spelling_errors + return spelling_errors + except Exception as e: + print(f"Erreur lors de la vérification orthographique: {str(e)}") + return [] def check_grammar(text, lang='fr'): """ Vérifie la grammaire d'un texte et retourne les erreurs grammaticales. """ - # Initialiser l'outil de vérification grammaticale - tool = language_tool_python.LanguageTool(lang) + # Vérifier si le module language_tool_python est disponible + if not LANGUAGE_TOOL_AVAILABLE: + print("Vérification grammaticale désactivée car le module language_tool_python n'est pas disponible.") + return [] + + try: + # Initialiser l'outil de vérification grammaticale + tool = language_tool_python.LanguageTool(lang) - # Vérifier le texte - matches = tool.check(text) + # Vérifier le texte + matches = tool.check(text) - # Créer une liste d'erreurs grammaticales - grammar_errors = [] - for match in matches: - # Ignorer les erreurs d'orthographe (déjà traitées par le vérificateur d'orthographe) - if match.ruleId.startswith('MORFOLOGIK_RULE'): - continue + # Créer une liste d'erreurs grammaticales + grammar_errors = [] + for match in matches: + # Ignorer les erreurs d'orthographe (déjà traitées par le vérificateur d'orthographe) + if match.ruleId.startswith('MORFOLOGIK_RULE'): + continue - error = { - 'message': match.message, - 'context': match.context, - 'suggestions': match.replacements, - 'offset': match.offset, - 'length': match.errorLength, - 'rule': match.ruleId - } - grammar_errors.append(error) + # Extraire plus de contexte autour de l'erreur + error_start = match.offset + error_end = match.offset + match.errorLength + + # Trouver les limites des lignes contenant l'erreur + line_start = text.rfind('\n', 0, error_start) + 1 if text.rfind('\n', 0, error_start) >= 0 else 0 + line_end = text.find('\n', error_end) if text.find('\n', error_end) >= 0 else len(text) + + # Extraire les lignes précédentes et suivantes pour plus de contexte + prev_line_start = text.rfind('\n', 0, line_start - 1) + 1 if text.rfind('\n', 0, line_start - 1) >= 0 else 0 + next_line_end = text.find('\n', line_end + 1) if text.find('\n', line_end + 1) >= 0 else len(text) + + # Créer le contexte étendu + extended_context = text[prev_line_start:next_line_end] + + # Ajuster les offsets pour le contexte étendu + extended_offset = error_start - prev_line_start + + error = { + 'message': match.message, + 'context': match.context, + 'extended_context': extended_context, + 'suggestions': match.replacements, + 'offset': match.offset, + 'extended_offset': extended_offset, + 'length': match.errorLength, + 'rule': match.ruleId + } + grammar_errors.append(error) - # Fermer l'outil pour libérer les ressources - tool.close() + # Fermer l'outil pour libérer les ressources + tool.close() - return grammar_errors + return grammar_errors + except Exception as e: + print(f"Erreur lors de la vérification grammaticale: {str(e)}") + return [] def generate_error_report(chapters, output_path): """ @@ -183,9 +264,39 @@ def generate_error_report(chapters, output_path): # Écrire les erreurs d'orthographe report_file.write("### Erreurs d'orthographe\n\n") if spelling_errors: - for word, suggestions in spelling_errors.items(): - suggestions_str = ", ".join(suggestions) if suggestions else "Aucune suggestion" + # Regrouper les erreurs par mot + errors_by_word = {} + for error in spelling_errors: + word = error['word'] + if word not in errors_by_word: + errors_by_word[word] = { + 'suggestions': error['suggestions'], + 'occurrences': [] + } + errors_by_word[word]['occurrences'].append({ + 'context': error['context'], + 'extended_context': error['extended_context'], + 'context_offset': error['context_offset'], + 'extended_offset': error['extended_offset'] + }) + + # Écrire les erreurs regroupées par mot + for word, data in errors_by_word.items(): + suggestions_str = ", ".join(data['suggestions']) if data['suggestions'] else "Aucune suggestion" report_file.write(f"- **{word}**: {suggestions_str}\n") + + # Ajouter les contextes pour chaque occurrence + for i, occurrence in enumerate(data['occurrences']): + # Mettre en évidence le mot dans le contexte + context = occurrence['context'] + offset = occurrence['context_offset'] + highlighted_context = context[:offset] + f"**{word}**" + context[offset+len(word):] + + # Ajouter le contexte étendu + extended_context = occurrence['extended_context'] + report_file.write(f" - **Occurrence {i+1}**:\n") + report_file.write(f" - **Contexte**: {highlighted_context}\n") + report_file.write(f" - **Contexte étendu**: ```\n{extended_context}\n```\n") else: report_file.write("Aucune erreur d'orthographe détectée.\n") @@ -194,12 +305,20 @@ def generate_error_report(chapters, output_path): # Écrire les erreurs grammaticales report_file.write("### Erreurs grammaticales\n\n") if grammar_errors: - for error in grammar_errors: + for i, error in enumerate(grammar_errors): suggestions_str = ", ".join(error['suggestions'][:5]) if error['suggestions'] else "Aucune suggestion" + + # Mettre en évidence l'erreur dans le contexte context = error['context'].replace(error['context'][error['offset']:error['offset']+error['length']], f"**{error['context'][error['offset']:error['offset']+error['length']]}**") - report_file.write(f"- **Erreur**: {error['message']}\n") + + report_file.write(f"- **Erreur {i+1}**: {error['message']}\n") report_file.write(f" - **Contexte**: {context}\n") + + # Ajouter le contexte étendu + if 'extended_context' in error: + report_file.write(f" - **Contexte étendu**: ```\n{error['extended_context']}\n```\n") + report_file.write(f" - **Suggestions**: {suggestions_str}\n\n") else: report_file.write("Aucune erreur grammaticale détectée.\n") diff --git a/api_corrections.py b/api_corrections.py old mode 100644 new mode 100755 diff --git a/app.py b/app.py old mode 100644 new mode 100755 diff --git a/export_with_remove_tags.py b/export_with_remove_tags.py old mode 100644 new mode 100755 diff --git a/find_characters_in_book.py b/find_characters_in_book.py old mode 100644 new mode 100755 diff --git a/format_typo.py b/format_typo.py old mode 100644 new mode 100755 diff --git a/gantt_parser.py b/gantt_parser.py old mode 100644 new mode 100755 diff --git a/generate_corrections_page.py b/generate_corrections_page.py old mode 100644 new mode 100755 index ae00129..83741ae --- a/generate_corrections_page.py +++ b/generate_corrections_page.py @@ -55,19 +55,53 @@ def parse_error_report(report_path): 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) - error_pattern = r'- \*\*(.*?)\*\*: (.*?)(?=\n- \*\*|\Z)' - for error_match in re.finditer(error_pattern, spelling_text, re.DOTALL): - word = error_match.group(1) - suggestions_text = error_match.group(2).strip() + + # 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(',')] - spelling_errors.append({ - 'word': word, - 'suggestions': suggestions, - 'type': 'spelling' - }) + # 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)' @@ -76,11 +110,16 @@ def parse_error_report(report_path): grammar_errors = [] if grammar_match and "Aucune erreur grammaticale détectée" not in grammar_match.group(1): grammar_text = grammar_match.group(1) - error_pattern = r'- \*\*Erreur\*\*: (.*?)\n - \*\*Contexte\*\*: (.*?)\n - \*\*Suggestions\*\*: (.*?)(?=\n\n- \*\*Erreur|\Z)' + + # 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): - message = error_match.group(1).strip() - context = error_match.group(2).strip() - suggestions_text = error_match.group(3).strip() + 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) @@ -93,13 +132,19 @@ def parse_error_report(report_path): if suggestions_text != "Aucune suggestion": suggestions = [s.strip() for s in suggestions_text.split(',')] - grammar_errors.append({ + 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, @@ -133,7 +178,7 @@ def create_css(): margin-bottom: 30px; } - h1, h2, h3 { + h1, h2, h3, h4, h5 { margin-top: 0; } @@ -171,6 +216,29 @@ def create_css(): 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; @@ -201,6 +269,48 @@ def create_css(): 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; @@ -328,6 +438,12 @@ def create_js(errors_by_chapter): 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}}`); @@ -347,6 +463,41 @@ def create_js(errors_by_chapter): }}); }} + // 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 @@ -470,6 +621,9 @@ def create_js(errors_by_chapter): changeTab(tab.getAttribute('data-tab')); }}); }}); + + // Configurer les champs de saisie personnalisée + setupCustomInputs(); }}); """ @@ -539,7 +693,26 @@ def generate_spelling_html(errors_by_chapter): html += f"""
{error['extended_context']}+