diff --git a/README.md b/README.md index b1e125c..6922198 100644 --- a/README.md +++ b/README.md @@ -77,8 +77,34 @@ Analyse les fautes d'orthographe et de grammaire dans votre livre et génère: - Un rapport détaillé au format Markdown des erreurs trouvées dans chaque chapitre - Un fichier CSV résumant le nombre d'erreurs par chapitre - Des suggestions de correction pour chaque erreur identifiée +- Utilise un dictionnaire personnalisé pour exclure certains mots de la vérification -Cet outil vous aide à améliorer la qualité linguistique de votre texte en identifiant les problèmes potentiels chapitre par chapitre. +#### Interface de correction interactive +`python generate_corrections_page.py` +Génère une page web interactive dans le dossier build qui permet de: +- Visualiser les erreurs orthographiques et grammaticales dans leur contexte +- Appliquer directement les corrections suggérées au fichier livre.org +- Ignorer certaines erreurs en les marquant comme "à ne pas traiter" +- Ajouter des mots au dictionnaire personnalisé + +Pour utiliser l'interface de correction: +1. Exécutez `python generate_corrections_page.py` pour générer la page +2. Lancez le serveur Flask avec `python app.py` +3. Accédez à http://localhost:5000/build/corrections.html dans votre navigateur + +Cet outil vous aide à améliorer la qualité linguistique de votre texte en identifiant les problèmes potentiels chapitre par chapitre et en facilitant leur correction. + +### Analyse des enchaînements d'intrigue +`python analyse_intrigues.py` +Analyse les enchaînements d'intrigue dans votre livre et génère: +- Un graphique avec une ligne unique composée de segments colorés représentant chaque intrigue +- Un diagramme de Gantt coloré des intrigues +- Une page web interactive dans le dossier build qui présente: + - Un graphique interactif des intrigues avec informations au survol + - Une liste hiérarchique des intrigues et sous-intrigues + - Les visualisations PNG et SVG générées + +Cet outil vous aide à visualiser comment vos intrigues s'enchaînent et s'imbriquent tout au long de votre livre. Les visualisations sont également intégrées au tableau de bord web existant. ### Statistiques `bash up_infos.sh` @@ -134,7 +160,11 @@ Un tableau de bord web interactif est disponible pour visualiser les données de - Graphique de progression de l'écriture - Graphique de réseau des personnages (déplaçable à la souris) - Graphique de réseau reliant les personnages aux intrigues (déplaçable à la souris, les labels des personnages sont toujours visibles, les labels des intrigues ne sont visibles que lors du survol) -- Chronologie des intrigues +- Chronologie des intrigues avec: + - Visualisation interactive des intrigues + - Enchaînement des intrigues avec segments colorés + - Diagramme de Gantt coloré des intrigues + - Lien vers une page d'analyse détaillée des intrigues Pour générer le tableau de bord, exécutez: ```bash diff --git a/analyse_intrigues.py b/analyse_intrigues.py new file mode 100644 index 0000000..96240b1 --- /dev/null +++ b/analyse_intrigues.py @@ -0,0 +1,470 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +""" +Script d'analyse des enchaînements d'intrigue dans le livre. +Ce script: +1. Analyse le CSV des intrigues +2. Attribue une couleur à chaque ligne d'intrigue selon un gradient rouge-vert (rouge pour le début, vert pour la fin) +3. Réalise un graphique avec une seule ligne faite de segments colorés +4. Sauvegarde le graphique en PNG et SVG +5. Crée une page web interactive dans le dossier build +""" + +import pandas as pd +import matplotlib.pyplot as plt +import matplotlib.colors as mcolors +import numpy as np +import os +import json +from matplotlib.colors import to_hex +import shutil + +# Créer le dossier build s'il n'existe pas +if not os.path.exists('build'): + os.makedirs('build') + +# Fonction pour générer un gradient de couleurs du rouge au vert +def generate_red_to_green_gradient(n): + """Génère n couleurs dans un gradient allant du rouge au vert""" + # Utiliser un gradient de rouge (début) à vert (fin) + colors = [] + for i in range(n): + # Calculer la position dans le gradient (0 = rouge, 1 = vert) + t = i / max(1, n - 1) + + # Rouge: diminue de 1 à 0 + r = 1.0 - t + # Vert: augmente de 0 à 1 + g = t + # Bleu: toujours à 0 + b = 0.0 + + colors.append(np.array([r, g, b, 1.0])) + + return colors + +# Lire le fichier CSV des intrigues +df = pd.read_csv('intrigues.csv') + +# Trier les intrigues par début +df = df.sort_values(by=['Début', 'Fin']) + +# Identifier les intrigues principales et les sous-intrigues +# Une sous-intrigue est contenue dans une autre intrigue si son début et sa fin +# sont compris dans l'intervalle de l'intrigue parente +def identify_subplots(df): + """Identifie les relations parent-enfant entre les intrigues""" + # Initialiser une colonne pour le parent + df['Parent'] = None + + # Pour chaque intrigue, trouver son parent (s'il existe) + for i, row in df.iterrows(): + # Chercher parmi toutes les autres intrigues + for j, potential_parent in df.iterrows(): + if i != j: # Ne pas comparer avec soi-même + # Si l'intrigue est contenue dans une autre + if (row['Début'] >= potential_parent['Début'] and + row['Fin'] <= potential_parent['Fin'] and + # S'assurer que ce n'est pas exactement le même intervalle + not (row['Début'] == potential_parent['Début'] and + row['Fin'] == potential_parent['Fin'])): + # Si on a déjà trouvé un parent, prendre le plus spécifique + if df.at[i, 'Parent'] is not None: + current_parent_idx = df.index[df['Intrigue'] == df.at[i, 'Parent']].tolist()[0] + current_parent = df.iloc[current_parent_idx] + # Le parent le plus spécifique est celui avec l'intervalle le plus court + if ((potential_parent['Fin'] - potential_parent['Début']) < + (current_parent['Fin'] - current_parent['Début'])): + df.at[i, 'Parent'] = potential_parent['Intrigue'] + else: + df.at[i, 'Parent'] = potential_parent['Intrigue'] + + return df + +# Appliquer la fonction pour identifier les sous-intrigues +df = identify_subplots(df) + +# Organiser les intrigues en groupes basés sur les relations parent-enfant +def organize_plots_by_hierarchy(df): + # Créer un dictionnaire pour stocker les groupes d'intrigues + plot_groups = {} + group_id = 0 + + # D'abord, identifier toutes les intrigues principales (sans parent) + main_plots = df[df['Parent'].isna()]['Intrigue'].tolist() + + # Pour chaque intrigue principale, créer un groupe avec ses sous-intrigues + for main_plot in main_plots: + group = [main_plot] + # Trouver toutes les sous-intrigues directes + subplots = df[df['Parent'] == main_plot]['Intrigue'].tolist() + group.extend(subplots) + + # Trouver les sous-intrigues de niveau 2 (sous-intrigues des sous-intrigues) + for subplot in subplots: + sub_subplots = df[df['Parent'] == subplot]['Intrigue'].tolist() + group.extend(sub_subplots) + + plot_groups[group_id] = group + group_id += 1 + + # Créer un dictionnaire qui associe chaque intrigue à son groupe + plot_to_group = {} + for group_id, plots in plot_groups.items(): + for plot in plots: + plot_to_group[plot] = group_id + + return plot_groups, plot_to_group + +# Déterminer les valeurs min et max pour les chapitres +min_chapter = df['Début'].min() +max_chapter = df['Fin'].max() +chapter_range = max_chapter - min_chapter + +# Créer un dictionnaire de couleurs pour chaque intrigue +color_dict = {} + +# Trier les intrigues par position dans l'histoire (début du chapitre) +sorted_intrigues = df.sort_values(by='Début') + +# Générer un gradient de couleurs du rouge au vert +gradient_colors = generate_red_to_green_gradient(len(df)) + +# Assigner les couleurs aux intrigues en fonction de leur position dans l'histoire +for i, (_, row) in enumerate(sorted_intrigues.iterrows()): + intrigue = row['Intrigue'] + # Utiliser la position normalisée dans l'histoire pour déterminer la couleur + position = (row['Début'] - min_chapter) / chapter_range if chapter_range > 0 else 0 + # Assigner la couleur du gradient correspondant à la position + color_index = min(int(position * (len(gradient_colors) - 1)), len(gradient_colors) - 1) + color_dict[intrigue] = to_hex(gradient_colors[i]) + +# Créer un graphique avec une ligne unique composée de segments colorés +plt.figure(figsize=(12, 4)) + +# Tracer chaque segment d'intrigue +for i, row in df.iterrows(): + plt.plot([row['Début'], row['Fin']], [1, 1], linewidth=10, + color=color_dict[row['Intrigue']], + solid_capstyle='butt', + label=row['Intrigue']) + +# Ajouter des marqueurs pour les points de début et fin +for i, row in df.iterrows(): + plt.plot(row['Début'], 1, 'o', color='black', markersize=5) + plt.plot(row['Fin'], 1, 'o', color='black', markersize=5) + +# Configurer les axes +plt.yticks([]) # Cacher l'axe y +plt.xlabel('Chapitres') +plt.title('Enchaînement des intrigues') + +# Ajouter une légende unique pour chaque intrigue +handles, labels = plt.gca().get_legend_handles_labels() +by_label = dict(zip(labels, handles)) +plt.legend(by_label.values(), by_label.keys(), loc='upper center', + bbox_to_anchor=(0.5, -0.15), ncol=3) + +# Ajuster les marges +plt.tight_layout() + +# Sauvegarder en PNG et SVG +plt.savefig('build/enchaînement_intrigues.png', dpi=300, bbox_inches='tight') +plt.savefig('build/enchaînement_intrigues.svg', bbox_inches='tight') + +# Créer un diagramme de Gantt (comme dans gantt_parser.py mais avec des couleurs) +fig, ax = plt.subplots(figsize=(12, 8)) + +# Tracer chaque intrigue avec sa couleur +for i, row in df.iterrows(): + ax.plot([row['Début'], row['Fin']], [i, i], '-', + color=color_dict[row['Intrigue']], + linewidth=10) + ax.plot([row['Début'], row['Début']], [i-0.1, i+0.1], 'ko') + ax.plot([row['Fin'], row['Fin']], [i-0.1, i+0.1], 'ko') + +# Configurer les axes +ax.set_yticks(range(len(df))) +ax.set_yticklabels(df['Intrigue']) +ax.set_xticks(range(int(df['Début'].min()), int(df['Fin'].max())+1)) +ax.set_xticklabels(ax.get_xticks()) +ax.set_xlabel('Chapitres') +ax.set_title('Diagramme de Gantt des intrigues') + +# Ajuster les marges +plt.tight_layout() + +# Sauvegarder en PNG et SVG +plt.savefig('build/gantt_intrigues.png', dpi=300, bbox_inches='tight') +plt.savefig('build/gantt_intrigues.svg', bbox_inches='tight') + +# Préparer les données pour la page web interactive +web_data = [] +for i, row in df.iterrows(): + web_data.append({ + 'intrigue': row['Intrigue'], + 'debut': int(row['Début']), + 'fin': int(row['Fin']), + 'color': color_dict[row['Intrigue']], + 'parent': row['Parent'] + }) + +# Sauvegarder les données en JSON +with open('build/intrigues_data.json', 'w', encoding='utf-8') as f: + json.dump(web_data, f, ensure_ascii=False, indent=2) + +# Créer la page HTML interactive pour les intrigues +html_content = """ + + + + + Analyse des Intrigues + + + +
+

Analyse des Intrigues

+ +

Enchaînement des intrigues

+
+
+ +

Liste des intrigues

+
+ +
+

Visualisations

+
+

Enchaînement des intrigues

+ Enchaînement des intrigues +
+
+

Diagramme de Gantt des intrigues

+ Diagramme de Gantt des intrigues +
+
+
+ + + + +""" + +# Écrire le fichier HTML pour les intrigues +with open('build/intrigues.html', 'w', encoding='utf-8') as f: + f.write(html_content) + +# Supprimer l'ancien index.html s'il existe +if os.path.exists('build/index.html'): + os.remove('build/index.html') + +# Mettre à jour le dashboard.html pour inclure un lien vers la page des intrigues +# et ajouter les visualisations d'intrigues +if os.path.exists('build/dashboard.html'): + with open('build/dashboard.html', 'r', encoding='utf-8') as f: + dashboard_content = f.read() + + # Ajouter un lien vers la page des intrigues dans la section des intrigues + if '
\n

Chronologie des Intrigues

' in dashboard_content: + dashboard_content = dashboard_content.replace( + '
\n

Chronologie des Intrigues

', + '
\n

Chronologie des Intrigues

\n

Voir l\'analyse détaillée des intrigues

' + ) + + # Ajouter les nouvelles visualisations à la section des intrigues + if '
' in dashboard_content: + dashboard_content = dashboard_content.replace( + '
', + '
\n

Enchaînement des intrigues

\n
\n Enchaînement des intrigues\n
\n

Diagramme de Gantt des intrigues

\n
\n Diagramme de Gantt des intrigues\n
' + ) + + # Écrire le fichier dashboard mis à jour + with open('build/dashboard.html', 'w', encoding='utf-8') as f: + f.write(dashboard_content) + +print("Analyse des intrigues terminée.") +print("Fichiers générés dans le dossier 'build':") +print("- enchaînement_intrigues.png") +print("- enchaînement_intrigues.svg") +print("- gantt_intrigues.png") +print("- gantt_intrigues.svg") +print("- intrigues_data.json") +print("- intrigues.html") \ No newline at end of file diff --git a/analyse_orthographe_grammaire.py b/analyse_orthographe_grammaire.py index 908e86c..2d6cf34 100644 --- a/analyse_orthographe_grammaire.py +++ b/analyse_orthographe_grammaire.py @@ -15,7 +15,7 @@ import re import os import csv import argparse -from pyspellchecker import SpellChecker +from spellchecker import SpellChecker import language_tool_python # Définir les arguments en ligne de commande @@ -71,12 +71,38 @@ def clean_chapter_content(content): return content.strip() -def check_spelling(text, lang='fr'): +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. """ 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()) @@ -86,10 +112,15 @@ def check_spelling(text, lang='fr'): # 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)[:5] + suggestions_list = list(suggestions) if suggestions is not None else [] + suggestions_list = suggestions_list[:5] spelling_errors[word] = suggestions_list return spelling_errors diff --git a/api_corrections.py b/api_corrections.py new file mode 100644 index 0000000..76a42f6 --- /dev/null +++ b/api_corrections.py @@ -0,0 +1,242 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +API pour traiter les requêtes de correction depuis la page web. +Ce script: +1. Fournit des endpoints pour appliquer des corrections au fichier livre.org +2. Permet d'ajouter des mots au dictionnaire personnalisé +3. Permet de marquer des erreurs comme "à ne pas traiter" dans le CSV +""" + +import os +import re +import json +import csv +from flask import Flask, request, jsonify, Blueprint + +# Créer un Blueprint pour l'API +corrections_api = Blueprint('corrections_api', __name__) + +# Chemin vers les fichiers +LIVRE_PATH = 'livre.org' +DICT_PATH = 'dictionnaire_personnalise.txt' +CSV_PATH = 'resume_erreurs.csv' + +@corrections_api.route('/api/corrections', methods=['POST']) +def handle_corrections(): + """Endpoint pour traiter les requêtes de correction.""" + data = request.json + + if not data or 'action' not in data: + return jsonify({'success': False, 'message': 'Action non spécifiée'}), 400 + + action = data['action'] + + if action == 'apply_correction': + return apply_correction(data) + elif action == 'ignore_error': + return ignore_error(data) + elif action == 'add_to_dictionary': + return add_to_dictionary(data) + else: + return jsonify({'success': False, 'message': 'Action non reconnue'}), 400 + +def apply_correction(data): + """Applique une correction au fichier livre.org.""" + # Vérifier les paramètres requis + required_params = ['error_type', 'chapter', 'error_index', 'correction'] + if not all(param in data for param in required_params): + return jsonify({'success': False, 'message': 'Paramètres manquants'}), 400 + + error_type = data['error_type'] + chapter = data['chapter'] + error_index = data['error_index'] + correction = data['correction'] + + try: + # Lire le contenu du fichier + with open(LIVRE_PATH, 'r', encoding='utf-8') as file: + content = file.read() + + # Trouver la section du chapitre + chapter_pattern = r'^\*\* ' + re.escape(chapter) + r'$(.*?)(?=^\*\* |\Z)' + chapter_match = re.search(chapter_pattern, content, re.MULTILINE | re.DOTALL) + + if not chapter_match: + return jsonify({'success': False, 'message': f'Chapitre "{chapter}" non trouvé'}), 404 + + chapter_content = chapter_match.group(1) + chapter_start = chapter_match.start() + chapter_end = chapter_match.end() + + # Appliquer la correction en fonction du type d'erreur + if error_type == 'spelling': + # Charger les erreurs d'orthographe pour ce chapitre + errors = load_spelling_errors(chapter) + if error_index >= len(errors): + return jsonify({'success': False, 'message': 'Index d\'erreur invalide'}), 400 + + error = errors[error_index] + word = error['word'] + + # Remplacer le mot dans le contenu du chapitre + modified_chapter = re.sub(r'\b' + re.escape(word) + r'\b', correction, chapter_content) + + # Mettre à jour le contenu complet + new_content = content[:chapter_start] + '** ' + chapter + modified_chapter + content[chapter_end:] + + elif error_type == 'grammar': + # Charger les erreurs grammaticales pour ce chapitre + errors = load_grammar_errors(chapter) + if error_index >= len(errors): + return jsonify({'success': False, 'message': 'Index d\'erreur invalide'}), 400 + + error = errors[error_index] + error_text = error['error_text'] + context = error['context'] + + # Trouver le contexte dans le chapitre + context_pattern = re.escape(context.replace(error_text, error_text)) + context_match = re.search(context_pattern, chapter_content) + + if not context_match: + return jsonify({'success': False, 'message': 'Contexte non trouvé dans le chapitre'}), 404 + + # Remplacer l'erreur dans le contexte + corrected_context = context.replace(error_text, correction) + + # Remplacer le contexte dans le chapitre + modified_chapter = chapter_content.replace(context, corrected_context) + + # Mettre à jour le contenu complet + new_content = content[:chapter_start] + '** ' + chapter + modified_chapter + content[chapter_end:] + + else: + return jsonify({'success': False, 'message': 'Type d\'erreur non reconnu'}), 400 + + # Écrire le contenu modifié dans le fichier + with open(LIVRE_PATH, 'w', encoding='utf-8') as file: + file.write(new_content) + + return jsonify({'success': True, 'message': 'Correction appliquée avec succès'}) + + except Exception as e: + return jsonify({'success': False, 'message': f'Erreur lors de l\'application de la correction: {str(e)}'}), 500 + +def ignore_error(data): + """Marque une erreur comme 'à ne pas traiter' dans le CSV.""" + # Vérifier les paramètres requis + required_params = ['error_type', 'chapter', 'error_index'] + if not all(param in data for param in required_params): + return jsonify({'success': False, 'message': 'Paramètres manquants'}), 400 + + error_type = data['error_type'] + chapter = data['chapter'] + error_index = data['error_index'] + + try: + # Lire le CSV des erreurs + rows = [] + with open(CSV_PATH, 'r', newline='', encoding='utf-8') as csvfile: + reader = csv.reader(csvfile) + headers = next(reader) # Lire les en-têtes + + # Ajouter les en-têtes à la liste des lignes + rows.append(headers) + + # Parcourir les lignes et mettre à jour celle du chapitre concerné + for row in reader: + if len(row) > 0 and row[0] == chapter: + # Mettre à jour la ligne en fonction du type d'erreur + if error_type == 'spelling' and len(row) > 1: + # Décrémenter le nombre d'erreurs d'orthographe + spelling_errors = int(row[1]) - 1 + row[1] = str(max(0, spelling_errors)) + # Mettre à jour le total + if len(row) > 3: + row[3] = str(int(row[3]) - 1) + elif error_type == 'grammar' and len(row) > 2: + # Décrémenter le nombre d'erreurs grammaticales + grammar_errors = int(row[2]) - 1 + row[2] = str(max(0, grammar_errors)) + # Mettre à jour le total + if len(row) > 3: + row[3] = str(int(row[3]) - 1) + + rows.append(row) + + # Écrire les modifications dans le CSV + with open(CSV_PATH, 'w', newline='', encoding='utf-8') as csvfile: + writer = csv.writer(csvfile) + writer.writerows(rows) + + return jsonify({'success': True, 'message': 'Erreur ignorée avec succès'}) + + except Exception as e: + return jsonify({'success': False, 'message': f'Erreur lors de l\'ignorance de l\'erreur: {str(e)}'}), 500 + +def add_to_dictionary(data): + """Ajoute un mot au dictionnaire personnalisé.""" + # Vérifier les paramètres requis + if 'word' not in data: + return jsonify({'success': False, 'message': 'Mot non spécifié'}), 400 + + word = data['word'] + + try: + # Vérifier si le mot est déjà dans le dictionnaire + existing_words = set() + if os.path.exists(DICT_PATH): + with open(DICT_PATH, 'r', encoding='utf-8') as file: + for line in file: + line = line.strip() + if line and not line.startswith('#'): + existing_words.add(line.lower()) + + # Si le mot n'est pas déjà dans le dictionnaire, l'ajouter + if word.lower() not in existing_words: + with open(DICT_PATH, 'a', encoding='utf-8') as file: + file.write(f"\n{word}") + + return jsonify({'success': True, 'message': 'Mot ajouté au dictionnaire avec succès'}) + + except Exception as e: + return jsonify({'success': False, 'message': f'Erreur lors de l\'ajout du mot au dictionnaire: {str(e)}'}), 500 + +def load_spelling_errors(chapter): + """Charge les erreurs d'orthographe pour un chapitre donné depuis le rapport.""" + # Cette fonction devrait charger les erreurs d'orthographe depuis le rapport + # Pour simplifier, nous utilisons une implémentation fictive + # Dans une implémentation réelle, il faudrait parser le rapport d'erreurs + + # Exemple d'implémentation fictive + from generate_corrections_page import parse_error_report + + errors_by_chapter = parse_error_report('rapport_orthographe_grammaire.md') + + if chapter in errors_by_chapter: + return errors_by_chapter[chapter]['spelling'] + + return [] + +def load_grammar_errors(chapter): + """Charge les erreurs grammaticales pour un chapitre donné depuis le rapport.""" + # Cette fonction devrait charger les erreurs grammaticales depuis le rapport + # Pour simplifier, nous utilisons une implémentation fictive + # Dans une implémentation réelle, il faudrait parser le rapport d'erreurs + + # Exemple d'implémentation fictive + from generate_corrections_page import parse_error_report + + errors_by_chapter = parse_error_report('rapport_orthographe_grammaire.md') + + if chapter in errors_by_chapter: + return errors_by_chapter[chapter]['grammar'] + + return [] + +# Pour tester l'API indépendamment +if __name__ == '__main__': + app = Flask(__name__) + app.register_blueprint(corrections_api) + app.run(debug=True, port=5001) \ No newline at end of file diff --git a/app.py b/app.py index 884644c..419597b 100644 --- a/app.py +++ b/app.py @@ -1,11 +1,21 @@ -from flask import Flask, render_template, request, jsonify +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Application Flask pour le générateur de livre. +""" + +from flask import Flask, render_template, request, jsonify, send_from_directory import os import json from datetime import datetime, timedelta import re +from api_corrections import corrections_api app = Flask(__name__, static_folder='static') +# Enregistrer le blueprint de l'API de corrections +app.register_blueprint(corrections_api) + def load_org_file(filename): if os.path.exists(filename): with open(filename, 'r', encoding='utf-8') as f: @@ -355,5 +365,10 @@ def get_characters_file(): except Exception as e: return jsonify({'error': str(e)}), 500 +@app.route('/build/') +def serve_build(path): + """Sert les fichiers statiques du dossier build.""" + return send_from_directory('build', path) + if __name__ == '__main__': - app.run(debug=True) \ No newline at end of file + app.run(debug=True, host='0.0.0.0', port=5000) \ No newline at end of file diff --git a/data.json b/data.json index 5c77458..865e9c2 100644 --- a/data.json +++ b/data.json @@ -84,5 +84,9 @@ } ] } - ] + ], + "2025-08-30": { + "words": 0, + "last_update": "2025-08-30 22:34:19" + } } \ No newline at end of file diff --git a/dictionnaire_personnalise.txt b/dictionnaire_personnalise.txt new file mode 100644 index 0000000..21b2c4f --- /dev/null +++ b/dictionnaire_personnalise.txt @@ -0,0 +1,8 @@ +# Dictionnaire personnalisé +# Ce fichier contient les mots à considérer comme corrects lors de l'analyse orthographique +# Un mot par ligne + +# Noms propres +eryndor + +# Termes spécifiques \ No newline at end of file diff --git a/fix_corrupted_csv.py b/fix_corrupted_csv.py deleted file mode 100755 index c5cad05..0000000 --- a/fix_corrupted_csv.py +++ /dev/null @@ -1,92 +0,0 @@ -#!/usr/bin/env python3 -""" -Script pour réparer les fichiers CSV contenant des dates corrompues avec des caractères nuls. -Ce script lit un fichier CSV, nettoie les dates corrompues, et écrit un nouveau fichier CSV corrigé. -""" - -import csv -import re -import os -import sys -from datetime import datetime - -def fix_corrupted_csv(input_file, output_file=None, delimiter=';'): - """ - Répare un fichier CSV contenant des dates corrompues avec des caractères nuls. - - Args: - input_file: Chemin vers le fichier CSV à réparer - output_file: Chemin vers le fichier CSV de sortie (si None, ajoute '_fixed' au nom du fichier d'entrée) - delimiter: Délimiteur utilisé dans le fichier CSV - - Returns: - Le chemin vers le fichier CSV réparé - """ - if output_file is None: - base, ext = os.path.splitext(input_file) - output_file = f"{base}_fixed{ext}" - - fixed_rows = [] - corrupted_count = 0 - total_count = 0 - - # Lire le fichier CSV d'entrée - with open(input_file, 'r') as csvfile: - reader = csv.reader(csvfile, delimiter=delimiter) - for row in reader: - total_count += 1 - if not row: - continue - - try: - # Vérifier si la date est corrompue - date_str = row[0] - try: - # Essayer de parser la date directement - datetime.fromisoformat(date_str) - except ValueError: - # Si la date est corrompue, essayer de l'extraire avec regex - match = re.search(r'\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?', date_str) - if match: - # Remplacer la date corrompue par la date extraite - row[0] = match.group(0) - corrupted_count += 1 - print(f"Date corrompue réparée: {date_str} -> {row[0]}") - else: - print(f"AVERTISSEMENT: Impossible de réparer la date: {date_str}") - - fixed_rows.append(row) - except Exception as e: - print(f"ERREUR lors du traitement de la ligne: {e}") - - # Écrire le fichier CSV réparé - with open(output_file, 'w', newline='') as csvfile: - writer = csv.writer(csvfile, delimiter=delimiter) - writer.writerows(fixed_rows) - - print(f"\nRésumé:") - print(f"Total des lignes traitées: {total_count}") - print(f"Lignes corrompues réparées: {corrupted_count}") - print(f"Fichier réparé sauvegardé sous: {output_file}") - - return output_file - -def main(): - if len(sys.argv) < 2: - print("Usage: python fix_corrupted_csv.py [output_csv_file] [delimiter]") - sys.exit(1) - - input_file = sys.argv[1] - output_file = sys.argv[2] if len(sys.argv) > 2 else None - delimiter = sys.argv[3] if len(sys.argv) > 3 else ';' - - if not os.path.exists(input_file): - print(f"ERREUR: Le fichier {input_file} n'existe pas.") - sys.exit(1) - - fixed_file = fix_corrupted_csv(input_file, output_file, delimiter) - print(f"\nPour utiliser le fichier réparé avec follow_progress.py, exécutez:") - print(f"python follow_progress.py {fixed_file}") - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/fix_csv_execution.py b/fix_csv_execution.py deleted file mode 100644 index 53af166..0000000 --- a/fix_csv_execution.py +++ /dev/null @@ -1,48 +0,0 @@ -#!/usr/bin/env python3 -""" -Script pour ajouter un commentaire en début de fichier CSV afin d'éviter -qu'il soit exécuté directement comme un script Python. -""" - -import os -import sys - -def fix_csv_file(csv_file_path): - """ - Ajoute un commentaire en début de fichier CSV pour éviter l'exécution directe. - """ - if not os.path.exists(csv_file_path): - print(f"Erreur: Le fichier {csv_file_path} n'existe pas.") - return False - - # Lire le contenu actuel du fichier - with open(csv_file_path, 'r') as f: - content = f.read() - - # Vérifier si le fichier commence déjà par un commentaire - if content.startswith('#'): - print(f"Le fichier {csv_file_path} est déjà protégé.") - return True - - # Ajouter le commentaire au début du fichier - with open(csv_file_path, 'w') as f: - f.write('# Ce fichier est un CSV et ne doit pas être exécuté directement avec Python.\n') - f.write(content) - - print(f"Le fichier {csv_file_path} a été protégé contre l'exécution directe.") - return True - -def main(): - """ - Fonction principale. - """ - # Utiliser le fichier spécifié en argument ou par défaut 'suivi_livre.csv' - if len(sys.argv) > 1: - csv_file = sys.argv[1] - else: - csv_file = 'suivi_livre.csv' - - fix_csv_file(csv_file) - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/fix_csv_execution_improved.py b/fix_csv_execution_improved.py deleted file mode 100644 index b433e77..0000000 --- a/fix_csv_execution_improved.py +++ /dev/null @@ -1,76 +0,0 @@ -#!/usr/bin/env python3 -""" -Script pour ajouter un en-tête Python au début d'un fichier CSV afin qu'il -puisse être exécuté directement sans erreur et affiche un message d'aide. -""" - -import os -import sys - -def fix_csv_file(csv_file_path): - """ - Ajoute un en-tête Python au début du fichier CSV pour gérer l'exécution directe. - """ - if not os.path.exists(csv_file_path): - print(f"Erreur: Le fichier {csv_file_path} n'existe pas.") - return False - - # Lire le contenu actuel du fichier - with open(csv_file_path, 'r') as f: - content = f.read() - - # Vérifier si le fichier commence déjà par le script Python - if content.startswith('#!/usr/bin/env python3'): - print(f"Le fichier {csv_file_path} est déjà protégé.") - return True - - # En-tête Python à ajouter au début du fichier - python_header = '''#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -Ce fichier est un CSV contenant des données de suivi de livre. -Il n'est pas destiné à être exécuté directement comme un script Python. - -Pour analyser ces données, utilisez plutôt: -- follow_progress.py: pour mettre à jour et analyser les statistiques -- generate_dashboard.py: pour générer un tableau de bord visuel -""" - -import sys - -def main(): - print("Ce fichier est un CSV contenant des données de suivi de livre.") - print("Il n'est pas destiné à être exécuté directement comme un script Python.") - print("\\nPour analyser ces données, utilisez plutôt:") - print("- follow_progress.py: pour mettre à jour et analyser les statistiques") - print("- generate_dashboard.py: pour générer un tableau de bord visuel") - return 0 - -if __name__ == "__main__": - sys.exit(main()) - -# Les données CSV commencent ci-dessous: -''' - - # Ajouter l'en-tête Python au début du fichier - with open(csv_file_path, 'w') as f: - f.write(python_header) - f.write(content) - - print(f"Le fichier {csv_file_path} a été protégé contre l'exécution directe.") - return True - -def main(): - """ - Fonction principale. - """ - # Utiliser le fichier spécifié en argument ou par défaut 'suivi_livre.csv' - if len(sys.argv) > 1: - csv_file = sys.argv[1] - else: - csv_file = 'suivi_livre.csv' - - fix_csv_file(csv_file) - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/fix_csv_simple.py b/fix_csv_simple.py deleted file mode 100644 index c0d1133..0000000 --- a/fix_csv_simple.py +++ /dev/null @@ -1,52 +0,0 @@ -#!/usr/bin/env python3 -""" -Script pour restaurer le fichier CSV à un format simple avec juste un commentaire en tête. -""" - -import os -import sys - -def fix_csv_file(csv_file_path): - """ - Restaure le fichier CSV à un format simple avec juste un commentaire en tête. - """ - if not os.path.exists(csv_file_path): - print(f"Erreur: Le fichier {csv_file_path} n'existe pas.") - return False - - # Lire le contenu actuel du fichier - with open(csv_file_path, 'r') as f: - content = f.read() - - # Extraire les lignes de données (ignorer les lignes de commentaire et le script Python) - lines = content.split('\n') - data_lines = [] - for line in lines: - # Conserver uniquement les lignes qui contiennent des données CSV (celles avec des points-virgules) - if ';' in line and not line.startswith('#'): - data_lines.append(line) - - # Réécrire le fichier avec juste un commentaire simple en tête - with open(csv_file_path, 'w') as f: - f.write('# Ce fichier est un CSV et ne doit pas être exécuté directement avec Python.\n') - f.write('# Utilisez view_suivi_livre.py pour visualiser ces données.\n') - for line in data_lines: - f.write(line + '\n') - - print(f"Le fichier {csv_file_path} a été restauré à un format simple.") - return True - -def main(): - """ - Fonction principale. - """ - # Utiliser le fichier spécifié en argument ou par défaut 'suivi_livre.csv' - if len(sys.argv) > 1: - csv_file = sys.argv[1] - else: - csv_file = 'suivi_livre.csv' - - fix_csv_file(csv_file) - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/generate_corrections_page.py b/generate_corrections_page.py new file mode 100644 index 0000000..ae00129 --- /dev/null +++ b/generate_corrections_page.py @@ -0,0 +1,661 @@ +#!/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) + 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() + 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 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) + error_pattern = r'- \*\*Erreur\*\*: (.*?)\n - \*\*Contexte\*\*: (.*?)\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() + + # 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_errors.append({ + 'message': message, + 'context': clean_context, + 'error_text': error_text, + 'suggestions': suggestions, + 'type': 'grammar' + }) + + 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 { + 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; + } + + .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; + } + + .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'; + }}); + }} 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 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')); + }}); + }}); + }}); + """ + + 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}

+ +
+ """ + + if suggestions: + for suggestion in suggestions: + html += f""" + + """ + else: + html += """ +

Aucune suggestion disponible

+ """ + + html += f""" +
+ +
+ + +
+ +
+
+
+ """ + + 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} +
+ +
+ """ + + if suggestions: + for suggestion in suggestions: + html += f""" + + """ + else: + html += """ +

Aucune suggestion disponible

+ """ + + html += f""" +
+ +
+ +
+ +
+
+
+ """ + + 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() \ No newline at end of file diff --git a/rapport_orthographe_grammaire.md b/rapport_orthographe_grammaire.md new file mode 100644 index 0000000..f0cf710 --- /dev/null +++ b/rapport_orthographe_grammaire.md @@ -0,0 +1,119 @@ +# Rapport d'analyse orthographique et grammaticale + +## Chapitre: préambule du cul + +### Erreurs d'orthographe + +- **eeeeeeeeeeeeeeeeeeeee**: Aucune suggestion +- **eeeeeeeeeeeeeeeeee**: Aucune suggestion +- **zou**: cou, mou, fou, zoo, ou + +### Erreurs grammaticales + +- **Erreur**: Faute de frappe possible trouvée. + - **Contexte**: **Eeeeeeeeeeeeeeeeeeeee** préambule du cul eeeeeeeeeeeeeeeeee Cet... + - **Suggestions**: Aucune suggestion + +- **Erreur**: Faute de frappe possible trouvée. + - **Contexte**: E**eeeeeeeeeeeeeeeeee**ee préambule du cul **eeeeeeeeeeeeeeeeee** Cette partie ne devrait pas avoir de ti... + - **Suggestions**: Aucune suggestion + +- **Erreur**: Un mot est répété. + - **Contexte**: ****.****.****.**** ****d****e****v****r****a****i****t**** ****p****a****s**** ****a****v****o****i****r**** ****d****e**** ****t****i****t****r****e**** **** ****A****l****l****e****z**** ****h****e****i****n**** ****z****o****u**** ****z****o****u**** + - **Suggestions**: zou + + +--- + +## Chapitre: Chapitre 0 + +### Erreurs d'orthographe + +Aucune erreur d'orthographe détectée. + +### Erreurs grammaticales + +Aucune erreur grammaticale détectée. + +--- + +## Chapitre: Chapitre 1 + +### Erreurs d'orthographe + +- **blah**: flash, ah, boa, lac, blet +- **bleh**: bleu, blet +- **eryndor**: Aucune suggestion + +### Erreurs grammaticales + +- **Erreur**: Il manque probablement un trait d’union. + - **Contexte**: **Celui là** on doit le voir: chapitre 1 au dessus i... + - **Suggestions**: Celui-là + +- **Erreur**: Une virgule est requise. + - **Contexte**: Celui là** on** doit le voir: chapitre 1 au dessus ici.... + - **Suggestions**: , on + +- **Erreur**: Les deux-points sont précédés d’une espace insécable. + - **Contexte**: Celui là on doit le **voir:** chapitre 1 au dessus ici. Dans un mond... + - **Suggestions**: voir : + +- **Erreur**: « au dessus » est une faute de typographie. + - **Contexte**: Celui là on doit le voir: chapitre 1 **au dessus** ici. Dans un monde lointain, il y avai... + - **Suggestions**: au-dessus + +- **Erreur**: Faute de frappe possible trouvée. + - **Contexte**: ****.****.****.**** ****d****e****s**** ****é****t****o****i****l****e****s****.**** ****U****n**** ****j****e****u****n****e**** ****a****v****e****n****t****u****r****i****e****r**** ****n****o****m****m****é**** ****E****r****y****n****d****o****r**** ****y**** ****a****r****r****i****v****a**** ****u****n**** ****j****o****u****r****,**** ****a****t****t****i****r****é**** ****p****a****r**** ****l****e****s**** ****l****é****g****e****n****d****.****.****.**** + - **Suggestions**: Grandeur, Grandir, Brando, Condor, Rondo + +- **Erreur**: Faute de frappe possible trouvée. + - **Contexte**: ****.****.****.****d****e****s**** ****s****e****c****r****e****t****s**** ****e****t**** ****d****e****s**** ****p****o****u****v****o****i****r****s**** ****m****a****g****i****q****u****e****s****.**** **** **** ****B****l****a****h**** ****b****l****a****h**** ****B****l****e****h**** ****b****o****b**** ****t****r****o****u****v****a**** ****u****n**** ****c****r****i****s****t****a****l**** ****q****u****i**** ****l****u****i**** ****p****e****r****m****.****.****.**** + - **Suggestions**: Bla-bla + +- **Erreur**: Faute de frappe possible trouvée. + - **Contexte**: ****.****.****.****s**** ****e****t**** ****d****e****s**** ****p****o****u****v****o****i****r****s**** ****m****a****g****i****q****u****e****s****.**** **** **** ****B****l****a****h**** ****b****l****a****h**** ****B****l****e****h**** ****b****o****b**** ****t****r****o****u****v****a**** ****u****n**** ****c****r****i****s****t****a****l**** ****q****u****i**** ****l****u****i**** ****p****e****r****m****i****t**** ****d****e****.****.****.**** + - **Suggestions**: Bleu, Blé, Blés, Bled, Blet + +- **Erreur**: Faute de frappe possible trouvée. + - **Contexte**: ****.****.****.**** ****l****u****m****i****è****r****e**** ****é****t****e****r****n****e****l****l****e****.**** ****L****'****î****l****e**** ****f****u****t**** ****s****a****u****v****é****e**** ****e****t**** ****E****r****y****n****d****o****r**** ****d****e****v****i****n****t**** ****u****n**** ****h****é****r****o****s**** ****l****é****g****e****n****d****a****i****r****e****.**** **** **** ****1****1****1****1****1****1****1****1****1****.****.****.**** + - **Suggestions**: Grandeur, Grandir, Brando, Condor, Rondo + + +--- + +## Chapitre: Chapitre 2 + +### Erreurs d'orthographe + +- **chuck**: check + +### Erreurs grammaticales + +- **Erreur**: Faute de frappe possible : une espace est répétée + - **Contexte**: 2222222222222** ** Chuck fait des trucs + - **Suggestions**: + + +--- + +## Chapitre: Chapitre 3 + +### Erreurs d'orthographe + +- **bobette**: burette, tomette, omette, belette, molette + +### Erreurs grammaticales + +- **Erreur**: Faute de frappe possible trouvée. + - **Contexte**: 33333333333333333 **Bobette** et bob sont sur un bateau + - **Suggestions**: Boette, Bébête, Bobettes, Boetté, Bouette + + +--- + +## Résumé + +- **Nombre total de chapitres analysés**: 5 +- **Nombre total d'erreurs d'orthographe**: 8 +- **Nombre total d'erreurs grammaticales**: 13 diff --git a/up_infos.sh b/up_infos.sh index 2b3b395..8fbc1e1 100755 --- a/up_infos.sh +++ b/up_infos.sh @@ -8,4 +8,9 @@ python3 gantt_parser.py python3 follow_progress.py python3 format_typo.py python3 network_graph.py +python3 analyse_frequence_mots.py +python3 analyse_orthographe_grammaire.py +python3 generate_dashboard.py +python3 generate_corrections_page.py + bash git_save.sh