373 lines
No EOL
16 KiB
Python
Executable file
373 lines
No EOL
16 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
"""
|
|
Script pour analyser les fautes d'orthographe et de grammaire dans un fichier livre.org
|
|
et générer un rapport par chapitre.
|
|
|
|
Ce script:
|
|
1. Lit le fichier livre.org
|
|
2. Extrait le texte par chapitre
|
|
3. Analyse les fautes d'orthographe et de grammaire dans chaque chapitre
|
|
4. Génère un rapport détaillé des erreurs trouvées
|
|
"""
|
|
|
|
import re
|
|
import os
|
|
import csv
|
|
import argparse
|
|
|
|
# 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.')
|
|
parser.add_argument('dossier', nargs='?', help='Le chemin du dossier contenant le fichier livre.org. Si aucun dossier n\'est spécifié, le dossier courant sera utilisé.', default=os.getcwd())
|
|
args = parser.parse_args()
|
|
|
|
# Chemin vers le fichier livre.org
|
|
fichier_livre = f"{args.dossier}/livre.org"
|
|
|
|
def extract_chapters(file_path):
|
|
"""
|
|
Extrait les chapitres d'un fichier org-mode.
|
|
Retourne un dictionnaire avec les titres des chapitres comme clés et leur contenu comme valeurs.
|
|
"""
|
|
with open(file_path, 'r', encoding='utf-8') as file:
|
|
content = file.read()
|
|
|
|
# Diviser le contenu par chapitres (lignes commençant par **)
|
|
chapter_pattern = r'^\*\* (.*?)$(.*?)(?=^\*\* |\Z)'
|
|
chapters = re.findall(chapter_pattern, content, re.MULTILINE | re.DOTALL)
|
|
|
|
chapter_dict = {}
|
|
for title, content in chapters:
|
|
# Nettoyer le titre (supprimer ": title:" s'il existe)
|
|
clean_title = re.sub(r'\s*:\s*title\s*:', '', title).strip()
|
|
|
|
# Nettoyer le contenu
|
|
clean_content = clean_chapter_content(content)
|
|
|
|
chapter_dict[clean_title] = clean_content
|
|
|
|
return chapter_dict
|
|
|
|
def clean_chapter_content(content):
|
|
"""
|
|
Nettoie le contenu d'un chapitre en supprimant les commentaires et les balises org-mode.
|
|
"""
|
|
# Supprimer les blocs de commentaires
|
|
content = re.sub(r'#\+begin_comment.*?#\+end_comment', '', content, flags=re.DOTALL | re.IGNORECASE)
|
|
|
|
# Supprimer les lignes de métadonnées (commençant par #+)
|
|
content = re.sub(r'^\s*#\+.*$', '', content, flags=re.MULTILINE)
|
|
|
|
# Supprimer les sous-titres (lignes commençant par ***)
|
|
content = re.sub(r'^\s*\*\*\*.*$', '', content, flags=re.MULTILINE)
|
|
|
|
# Supprimer les liens org-mode [[...][...]] et [[...]]
|
|
content = re.sub(r'\[\[.*?\]\](?:\[.*?\])?', '', content)
|
|
|
|
# Supprimer les lignes vides multiples
|
|
content = re.sub(r'\n\s*\n', '\n\n', content)
|
|
|
|
return content.strip()
|
|
|
|
def load_custom_dictionary(file_path):
|
|
"""
|
|
Charge le dictionnaire personnalisé à partir d'un fichier texte.
|
|
Retourne un ensemble de mots à considérer comme corrects.
|
|
"""
|
|
custom_words = set()
|
|
|
|
# Vérifier si le fichier existe
|
|
if os.path.exists(file_path):
|
|
with open(file_path, 'r', encoding='utf-8') as file:
|
|
for line in file:
|
|
# Ignorer les lignes vides et les commentaires
|
|
line = line.strip()
|
|
if line and not line.startswith('#'):
|
|
custom_words.add(line.lower())
|
|
|
|
return custom_words
|
|
|
|
def check_spelling(text, lang='fr', custom_dict_path='dictionnaire_personnalise.txt'):
|
|
"""
|
|
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.
|
|
"""
|
|
# 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)
|
|
|
|
# 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())
|
|
|
|
# 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
|
|
|
|
# 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
|
|
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.
|
|
"""
|
|
# 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)
|
|
|
|
# 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
|
|
|
|
# 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()
|
|
|
|
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):
|
|
"""
|
|
Génère un rapport des erreurs d'orthographe et de grammaire par chapitre.
|
|
"""
|
|
with open(output_path, 'w', encoding='utf-8') as report_file:
|
|
report_file.write("# Rapport d'analyse orthographique et grammaticale\n\n")
|
|
|
|
total_spelling_errors = 0
|
|
total_grammar_errors = 0
|
|
|
|
for chapter_title, chapter_content in chapters.items():
|
|
report_file.write(f"## Chapitre: {chapter_title}\n\n")
|
|
|
|
# Vérifier l'orthographe
|
|
spelling_errors = check_spelling(chapter_content)
|
|
|
|
# Vérifier la grammaire
|
|
grammar_errors = check_grammar(chapter_content)
|
|
|
|
# Mettre à jour les totaux
|
|
total_spelling_errors += len(spelling_errors)
|
|
total_grammar_errors += len(grammar_errors)
|
|
|
|
# Écrire les erreurs d'orthographe
|
|
report_file.write("### Erreurs d'orthographe\n\n")
|
|
if spelling_errors:
|
|
# 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")
|
|
|
|
report_file.write("\n")
|
|
|
|
# Écrire les erreurs grammaticales
|
|
report_file.write("### Erreurs grammaticales\n\n")
|
|
if 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 {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")
|
|
|
|
report_file.write("\n---\n\n")
|
|
|
|
# Écrire le résumé
|
|
report_file.write("## Résumé\n\n")
|
|
report_file.write(f"- **Nombre total de chapitres analysés**: {len(chapters)}\n")
|
|
report_file.write(f"- **Nombre total d'erreurs d'orthographe**: {total_spelling_errors}\n")
|
|
report_file.write(f"- **Nombre total d'erreurs grammaticales**: {total_grammar_errors}\n")
|
|
|
|
print(f"Rapport d'erreurs généré: {output_path}")
|
|
|
|
def save_to_csv(chapters, output_path):
|
|
"""
|
|
Sauvegarde un résumé des erreurs dans un fichier CSV.
|
|
"""
|
|
with open(output_path, 'w', newline='', encoding='utf-8') as csvfile:
|
|
writer = csv.writer(csvfile)
|
|
writer.writerow(['Chapitre', 'Erreurs d\'orthographe', 'Erreurs grammaticales', 'Total'])
|
|
|
|
for chapter_title, chapter_content in chapters.items():
|
|
spelling_errors = check_spelling(chapter_content)
|
|
grammar_errors = check_grammar(chapter_content)
|
|
|
|
total_errors = len(spelling_errors) + len(grammar_errors)
|
|
writer.writerow([chapter_title, len(spelling_errors), len(grammar_errors), total_errors])
|
|
|
|
print(f"Résumé des erreurs sauvegardé dans {output_path}")
|
|
|
|
def main():
|
|
print(f"Analyse du fichier: {fichier_livre}")
|
|
|
|
# Extraire les chapitres
|
|
chapters = extract_chapters(fichier_livre)
|
|
print(f"Nombre de chapitres trouvés: {len(chapters)}")
|
|
|
|
# Définir les chemins de sortie
|
|
report_output = f"{args.dossier}/rapport_orthographe_grammaire.md"
|
|
csv_output = f"{args.dossier}/resume_erreurs.csv"
|
|
|
|
# Générer le rapport d'erreurs
|
|
generate_error_report(chapters, report_output)
|
|
|
|
# Sauvegarder le résumé en CSV
|
|
save_to_csv(chapters, csv_output)
|
|
|
|
print("Analyse orthographique et grammaticale terminée avec succès!")
|
|
|
|
if __name__ == "__main__":
|
|
main() |