first commit
This commit is contained in:
10
.claude/settings.local.json
Normal file
10
.claude/settings.local.json
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"permissions": {
|
||||||
|
"allow": [
|
||||||
|
"Bash(pip install:*)",
|
||||||
|
"Bash(pip3 install:*)",
|
||||||
|
"Bash(python3 -m pip install python-docx)",
|
||||||
|
"Bash(python3:*)"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
54
.claude/skills/formation-docx-replacer/SKILL.md
Normal file
54
.claude/skills/formation-docx-replacer/SKILL.md
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
---
|
||||||
|
name: formation-docx-replacer
|
||||||
|
description: Use when the user asks to create a new formation dossier, replace text in docx files, or apply a YAML config to modify formation documents. Triggers on keywords like formation, dossier, remplacements, docx, yaml config, nouvelle formation.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Formation DOCX Replacer
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Prepare un dossier de formation a partir du template et guide l'utilisateur pour configurer et lancer les remplacements dans les .docx.
|
||||||
|
|
||||||
|
## When to Use
|
||||||
|
|
||||||
|
- L'utilisateur veut creer une nouvelle formation
|
||||||
|
- L'utilisateur mentionne un nouveau dossier de formation
|
||||||
|
- L'utilisateur veut dupliquer le template pour un nouveau client/formation
|
||||||
|
|
||||||
|
## Workflow
|
||||||
|
|
||||||
|
1. **Demander le nom de la formation** a l'utilisateur (ex: "Excel Avance Dupont Avril 2026")
|
||||||
|
2. **Creer le dossier** dans `formations/` en slugifiant le nom (ex: `formations/excel_avance_dupont_avril_2026/`)
|
||||||
|
3. **Copier le template YAML** : `template/formation_template.yaml` -> `formations/<nom>/formation.yaml`
|
||||||
|
4. **Indiquer a l'utilisateur** :
|
||||||
|
- Le chemin du fichier YAML a editer
|
||||||
|
- Qu'il doit remplir les paires ancien/nouveau avec ses infos
|
||||||
|
- La commande exacte a lancer une fois le YAML pret :
|
||||||
|
```
|
||||||
|
python3 replace_docx.py formations/<nom>/formation.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
Ne PAS essayer de remplir le YAML a la place de l'utilisateur. Il connait ses infos.
|
||||||
|
|
||||||
|
## Format YAML
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
dossier_source: template
|
||||||
|
remplacements:
|
||||||
|
- ancien: "texte a chercher"
|
||||||
|
nouveau: "texte de remplacement"
|
||||||
|
- ancien: "autre texte"
|
||||||
|
nouveau: "nouveau texte"
|
||||||
|
fichiers: ["seul_ce_fichier.docx"] # optionnel, par defaut tous
|
||||||
|
```
|
||||||
|
|
||||||
|
## Ce que le script gere
|
||||||
|
|
||||||
|
- Paragraphes, tableaux (imbriques), en-tetes, pieds de page
|
||||||
|
- Texte decoupe sur plusieurs runs Word
|
||||||
|
- Preservation du formatage (police, taille, couleur, gras)
|
||||||
|
|
||||||
|
## Common Mistakes
|
||||||
|
|
||||||
|
- **Texte introuvable** : Word fragmente parfois le texte en plusieurs runs. Verifier avec python-docx
|
||||||
|
- **Accents** : YAML en UTF-8
|
||||||
10
.idea/.gitignore
generated
vendored
Normal file
10
.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Ignored default folder with query files
|
||||||
|
/queries/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
||||||
10
.idea/material_theme_project_new.xml
generated
Normal file
10
.idea/material_theme_project_new.xml
generated
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="MaterialThemeProjectNewConfig">
|
||||||
|
<option name="metadata">
|
||||||
|
<MTProjectMetadataState>
|
||||||
|
<option name="userId" value="2bbcbe4d:19d1b20fd6b:-65c4" />
|
||||||
|
</MTProjectMetadataState>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/qualiopi.iml" filepath="$PROJECT_DIR$/.idea/qualiopi.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
19
.idea/php.xml
generated
Normal file
19
.idea/php.xml
generated
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="MessDetectorOptionsConfiguration">
|
||||||
|
<option name="transferred" value="true" />
|
||||||
|
</component>
|
||||||
|
<component name="PHPCSFixerOptionsConfiguration">
|
||||||
|
<option name="transferred" value="true" />
|
||||||
|
</component>
|
||||||
|
<component name="PHPCodeSnifferOptionsConfiguration">
|
||||||
|
<option name="highlightLevel" value="WARNING" />
|
||||||
|
<option name="transferred" value="true" />
|
||||||
|
</component>
|
||||||
|
<component name="PhpStanOptionsConfiguration">
|
||||||
|
<option name="transferred" value="true" />
|
||||||
|
</component>
|
||||||
|
<component name="PsalmOptionsConfiguration">
|
||||||
|
<option name="transferred" value="true" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
8
.idea/qualiopi.iml
generated
Normal file
8
.idea/qualiopi.iml
generated
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="WEB_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$" />
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
||||||
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
53
CLAUDE.md
Normal file
53
CLAUDE.md
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
# CLAUDE.md - Qualiopi
|
||||||
|
|
||||||
|
## Contexte projet
|
||||||
|
|
||||||
|
Ce depot contient les documents de formation de la SAS MALIO (organisme de formation certifie Qualiopi). L'objectif principal est d'automatiser la creation de nouveaux dossiers de formation a partir de templates existants.
|
||||||
|
|
||||||
|
## Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
qualiopi/
|
||||||
|
├── replace_docx.py # Script principal
|
||||||
|
├── template/ # Documents modeles (ne jamais modifier directement)
|
||||||
|
│ ├── *.docx # Documents template
|
||||||
|
│ └── formation_template.yaml # Modele de config
|
||||||
|
└── formations/ # Un sous-dossier par formation generee
|
||||||
|
└── <nom_formation>/
|
||||||
|
├── formation.yaml # Config avec les remplacements
|
||||||
|
├── rapport.md # Rapport des modifications
|
||||||
|
└── *.docx # Documents generes
|
||||||
|
```
|
||||||
|
|
||||||
|
## Outils disponibles
|
||||||
|
|
||||||
|
### replace_docx.py
|
||||||
|
|
||||||
|
Script Python de remplacement de texte dans des fichiers `.docx`.
|
||||||
|
|
||||||
|
- **Commande** : `python3 replace_docx.py <chemin/vers/formation.yaml>`
|
||||||
|
- **Dependances** : python-docx, pyyaml
|
||||||
|
- **Comportement** : copie les .docx du template dans le dossier du YAML, applique les remplacements et genere un `rapport.md`
|
||||||
|
|
||||||
|
## Workflow pour creer une formation
|
||||||
|
|
||||||
|
Via le skill : `/formation-docx-replacer <nom_formation>` (cree le dossier et copie le YAML)
|
||||||
|
|
||||||
|
Ou manuellement :
|
||||||
|
1. `mkdir formations/<nom_formation>`
|
||||||
|
2. `cp template/formation_template.yaml formations/<nom_formation>/formation.yaml`
|
||||||
|
3. Editer le YAML avec les paires ancien/nouveau
|
||||||
|
4. `python3 replace_docx.py formations/<nom_formation>/formation.yaml`
|
||||||
|
5. Verifier `rapport.md` dans le dossier (signaler les remplacements non trouves)
|
||||||
|
|
||||||
|
## Regles importantes
|
||||||
|
|
||||||
|
- **Ne jamais modifier le dossier template/** : le script copie les .docx dans le dossier de la formation
|
||||||
|
- **Chemins relatifs** : dans le YAML, les chemins sont relatifs a la racine du projet
|
||||||
|
- **Encodage** : fichiers YAML en UTF-8
|
||||||
|
- **Verifier le rapport** : apres chaque execution, signaler les remplacements non trouves a l'utilisateur
|
||||||
|
- **Formatage** : le script preserve les polices, tailles, couleurs et styles
|
||||||
|
|
||||||
|
## Skill associe
|
||||||
|
|
||||||
|
Le skill `formation-docx-replacer` dans `.claude/skills/` gere l'utilisation automatique du script.
|
||||||
113
README.md
Normal file
113
README.md
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
# Qualiopi - Generateur de dossiers de formation
|
||||||
|
|
||||||
|
Outil d'automatisation pour creer des dossiers de formation a partir d'un template existant. Remplace en masse les informations (client, dates, formateur, etc.) dans tous les fichiers `.docx` d'un dossier, en preservant le formatage d'origine.
|
||||||
|
|
||||||
|
## Prerequis
|
||||||
|
|
||||||
|
- Python 3.10+
|
||||||
|
- python-docx (`pip3 install python-docx`)
|
||||||
|
- PyYAML (`pip3 install pyyaml`)
|
||||||
|
|
||||||
|
## Structure du projet
|
||||||
|
|
||||||
|
```
|
||||||
|
qualiopi/
|
||||||
|
├── replace_docx.py # Script de remplacement
|
||||||
|
├── template/ # Dossier template (ne pas modifier)
|
||||||
|
│ ├── CONDITIONS_GENERALES_DE_VENTE.docx
|
||||||
|
│ ├── formation_template.yaml # Modele de config a copier
|
||||||
|
│ └── ... # Autres documents template
|
||||||
|
└── formations/ # Dossiers generes par formation
|
||||||
|
└── excel_dupont_avril2026/
|
||||||
|
├── formation.yaml # Config de la formation
|
||||||
|
├── rapport.md # Rapport des modifications
|
||||||
|
├── CONDITIONS_GENERALES_DE_VENTE.docx # Copie modifiee
|
||||||
|
└── ...
|
||||||
|
```
|
||||||
|
|
||||||
|
## Utilisation
|
||||||
|
|
||||||
|
### 1. Creer le dossier de la formation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir -p formations/excel_dupont_avril2026
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Copier et configurer le YAML
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp template/formation_template.yaml formations/excel_dupont_avril2026/formation.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
Editer `formation.yaml` avec les infos de la formation :
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
dossier_source: template
|
||||||
|
|
||||||
|
remplacements:
|
||||||
|
- ancien: "Word - Initiation bureautique"
|
||||||
|
nouveau: "Excel Avance - TCD"
|
||||||
|
|
||||||
|
- ancien: "Societe Martin SARL"
|
||||||
|
nouveau: "Societe Dupont SAS"
|
||||||
|
|
||||||
|
- ancien: "10 et 11 mars 2026"
|
||||||
|
nouveau: "15 et 16 avril 2026"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Lancer le script
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 replace_docx.py formations/excel_dupont_avril2026/formation.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
Le script :
|
||||||
|
1. Copie les `.docx` du template dans le dossier de la formation
|
||||||
|
2. Applique tous les remplacements
|
||||||
|
3. Genere un fichier `rapport.md` dans le dossier de la formation
|
||||||
|
|
||||||
|
### 4. Verifier le rapport
|
||||||
|
|
||||||
|
Ouvrir `formations/excel_dupont_avril2026/rapport.md`. Il contient :
|
||||||
|
- La date d'execution
|
||||||
|
- Un tableau par fichier avec les remplacements effectues et le nombre d'occurrences
|
||||||
|
- La liste des remplacements non trouves (s'il y en a)
|
||||||
|
|
||||||
|
## Options
|
||||||
|
|
||||||
|
### Cibler un fichier specifique
|
||||||
|
|
||||||
|
Par defaut, chaque remplacement s'applique a tous les `.docx`. Pour cibler un fichier :
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
remplacements:
|
||||||
|
- ancien: "Texte specifique"
|
||||||
|
nouveau: "Nouveau texte"
|
||||||
|
fichiers: ["convention.docx"]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Chemins
|
||||||
|
|
||||||
|
Tous les chemins dans le YAML sont relatifs a la racine du projet (dossier contenant `replace_docx.py`).
|
||||||
|
|
||||||
|
## Ce qui est pris en charge
|
||||||
|
|
||||||
|
| Zone du document | Supporte |
|
||||||
|
|---|---|
|
||||||
|
| Paragraphes du corps | Oui |
|
||||||
|
| Cellules de tableaux | Oui |
|
||||||
|
| Tableaux imbriques | Oui |
|
||||||
|
| En-tetes de page | Oui |
|
||||||
|
| Pieds de page | Oui |
|
||||||
|
| Formatage (police, taille, couleur, gras) | Preserve |
|
||||||
|
| Texte decoupe sur plusieurs runs | Gere automatiquement |
|
||||||
|
|
||||||
|
## Utilisation avec Claude
|
||||||
|
|
||||||
|
Le skill `/formation-docx-replacer` permet de preparer un dossier automatiquement :
|
||||||
|
|
||||||
|
```
|
||||||
|
/formation-docx-replacer ma_nouvelle_formation
|
||||||
|
```
|
||||||
|
|
||||||
|
Claude cree le dossier dans `formations/`, copie le template YAML et indique la marche a suivre.
|
||||||
272
replace_docx.py
Normal file
272
replace_docx.py
Normal file
@@ -0,0 +1,272 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Script de remplacement de texte dans des fichiers .docx
|
||||||
|
Lit un fichier YAML de configuration et applique les remplacements
|
||||||
|
dans tous les .docx d'un dossier en préservant le formatage.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import yaml
|
||||||
|
from docx import Document
|
||||||
|
from copy import deepcopy
|
||||||
|
|
||||||
|
|
||||||
|
def get_full_text_from_runs(runs):
|
||||||
|
"""Reconstitue le texte complet à partir des runs."""
|
||||||
|
return "".join(r.text for r in runs)
|
||||||
|
|
||||||
|
|
||||||
|
def replace_in_runs(runs, ancien, nouveau):
|
||||||
|
"""
|
||||||
|
Remplace du texte dans une liste de runs en préservant le formatage.
|
||||||
|
Gère le cas où le texte est découpé sur plusieurs runs.
|
||||||
|
Retourne le nombre de remplacements effectués.
|
||||||
|
"""
|
||||||
|
count = 0
|
||||||
|
full_text = get_full_text_from_runs(runs)
|
||||||
|
|
||||||
|
if ancien not in full_text:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
while ancien in full_text:
|
||||||
|
count += 1
|
||||||
|
start_idx = full_text.index(ancien)
|
||||||
|
end_idx = start_idx + len(ancien)
|
||||||
|
|
||||||
|
# Trouver quels runs sont concernés
|
||||||
|
char_pos = 0
|
||||||
|
start_run = None
|
||||||
|
end_run = None
|
||||||
|
|
||||||
|
for i, run in enumerate(runs):
|
||||||
|
run_start = char_pos
|
||||||
|
run_end = char_pos + len(run.text)
|
||||||
|
|
||||||
|
if start_run is None and run_end > start_idx:
|
||||||
|
start_run = i
|
||||||
|
start_offset = start_idx - run_start
|
||||||
|
|
||||||
|
if run_end >= end_idx:
|
||||||
|
end_run = i
|
||||||
|
end_offset = end_idx - run_start
|
||||||
|
break
|
||||||
|
|
||||||
|
char_pos = run_end
|
||||||
|
|
||||||
|
if start_run is None or end_run is None:
|
||||||
|
break
|
||||||
|
|
||||||
|
# Cas simple : tout dans un seul run
|
||||||
|
if start_run == end_run:
|
||||||
|
run = runs[start_run]
|
||||||
|
run.text = run.text[:start_offset] + nouveau + run.text[end_offset:]
|
||||||
|
else:
|
||||||
|
# Multi-run : mettre le nouveau texte dans le premier run,
|
||||||
|
# vider les runs intermédiaires, ajuster le dernier
|
||||||
|
runs[start_run].text = runs[start_run].text[:start_offset] + nouveau
|
||||||
|
|
||||||
|
for i in range(start_run + 1, end_run):
|
||||||
|
runs[i].text = ""
|
||||||
|
|
||||||
|
runs[end_run].text = runs[end_run].text[end_offset:]
|
||||||
|
|
||||||
|
full_text = get_full_text_from_runs(runs)
|
||||||
|
|
||||||
|
return count
|
||||||
|
|
||||||
|
|
||||||
|
def replace_in_paragraph(paragraph, ancien, nouveau):
|
||||||
|
"""Remplace du texte dans un paragraphe."""
|
||||||
|
if not paragraph.runs:
|
||||||
|
return 0
|
||||||
|
return replace_in_runs(paragraph.runs, ancien, nouveau)
|
||||||
|
|
||||||
|
|
||||||
|
def replace_in_table(table, ancien, nouveau):
|
||||||
|
"""Remplace du texte dans toutes les cellules d'un tableau."""
|
||||||
|
count = 0
|
||||||
|
for row in table.rows:
|
||||||
|
for cell in row.cells:
|
||||||
|
for paragraph in cell.paragraphs:
|
||||||
|
count += replace_in_paragraph(paragraph, ancien, nouveau)
|
||||||
|
# Tables imbriquées
|
||||||
|
for nested_table in cell.tables:
|
||||||
|
count += replace_in_table(nested_table, ancien, nouveau)
|
||||||
|
return count
|
||||||
|
|
||||||
|
|
||||||
|
def replace_in_headers_footers(doc, ancien, nouveau):
|
||||||
|
"""Remplace du texte dans les en-têtes et pieds de page."""
|
||||||
|
count = 0
|
||||||
|
for section in doc.sections:
|
||||||
|
for header in [section.header, section.first_page_header, section.even_page_header]:
|
||||||
|
if header and header.is_linked_to_previous is False:
|
||||||
|
for paragraph in header.paragraphs:
|
||||||
|
count += replace_in_paragraph(paragraph, ancien, nouveau)
|
||||||
|
for table in header.tables:
|
||||||
|
count += replace_in_table(table, ancien, nouveau)
|
||||||
|
|
||||||
|
for footer in [section.footer, section.first_page_footer, section.even_page_footer]:
|
||||||
|
if footer and footer.is_linked_to_previous is False:
|
||||||
|
for paragraph in footer.paragraphs:
|
||||||
|
count += replace_in_paragraph(paragraph, ancien, nouveau)
|
||||||
|
for table in footer.tables:
|
||||||
|
count += replace_in_table(table, ancien, nouveau)
|
||||||
|
return count
|
||||||
|
|
||||||
|
|
||||||
|
def process_docx(filepath, remplacements):
|
||||||
|
"""Applique tous les remplacements sur un fichier .docx."""
|
||||||
|
doc = Document(filepath)
|
||||||
|
rapport = {}
|
||||||
|
|
||||||
|
for remp in remplacements:
|
||||||
|
ancien = remp["ancien"]
|
||||||
|
nouveau = remp["nouveau"]
|
||||||
|
fichiers_cibles = remp.get("fichiers", None)
|
||||||
|
|
||||||
|
# Si des fichiers cibles sont spécifiés, vérifier que ce fichier en fait partie
|
||||||
|
if fichiers_cibles:
|
||||||
|
basename = os.path.basename(filepath)
|
||||||
|
if basename not in fichiers_cibles:
|
||||||
|
continue
|
||||||
|
|
||||||
|
count = 0
|
||||||
|
|
||||||
|
# Paragraphes du corps
|
||||||
|
for paragraph in doc.paragraphs:
|
||||||
|
count += replace_in_paragraph(paragraph, ancien, nouveau)
|
||||||
|
|
||||||
|
# Tableaux
|
||||||
|
for table in doc.tables:
|
||||||
|
count += replace_in_table(table, ancien, nouveau)
|
||||||
|
|
||||||
|
# En-têtes et pieds de page
|
||||||
|
count += replace_in_headers_footers(doc, ancien, nouveau)
|
||||||
|
|
||||||
|
if count > 0:
|
||||||
|
rapport[f"'{ancien}' -> '{nouveau}'"] = count
|
||||||
|
|
||||||
|
doc.save(filepath)
|
||||||
|
return rapport
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
if len(sys.argv) < 2:
|
||||||
|
print("Usage: python3 replace_docx.py <fichier_config.yaml>")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
config_path = os.path.abspath(sys.argv[1])
|
||||||
|
config_dir = os.path.dirname(config_path)
|
||||||
|
|
||||||
|
with open(config_path, "r", encoding="utf-8") as f:
|
||||||
|
config = yaml.safe_load(f)
|
||||||
|
|
||||||
|
# Les chemins sont relatifs à la racine du projet (dossier contenant replace_docx.py)
|
||||||
|
project_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
|
||||||
|
dossier_source = config["dossier_source"]
|
||||||
|
if not os.path.isabs(dossier_source):
|
||||||
|
dossier_source = os.path.join(project_dir, dossier_source)
|
||||||
|
|
||||||
|
remplacements = config["remplacements"]
|
||||||
|
|
||||||
|
# Le dossier de travail est celui qui contient le fichier YAML
|
||||||
|
dossier_travail = config_dir
|
||||||
|
|
||||||
|
# Copier les .docx du template dans le dossier de travail
|
||||||
|
docx_copies = 0
|
||||||
|
for f in os.listdir(dossier_source):
|
||||||
|
if f.endswith(".docx") and not f.startswith("~$"):
|
||||||
|
src = os.path.join(dossier_source, f)
|
||||||
|
dst = os.path.join(dossier_travail, f)
|
||||||
|
if not os.path.exists(dst):
|
||||||
|
shutil.copy2(src, dst)
|
||||||
|
docx_copies += 1
|
||||||
|
|
||||||
|
if docx_copies > 0:
|
||||||
|
print(f"{docx_copies} fichier(s) .docx copie(s) depuis {dossier_source}")
|
||||||
|
else:
|
||||||
|
print(f"Fichiers .docx deja presents dans {dossier_travail}")
|
||||||
|
|
||||||
|
# Trouver tous les .docx du dossier de travail
|
||||||
|
docx_files = []
|
||||||
|
for f in os.listdir(dossier_travail):
|
||||||
|
if f.endswith(".docx") and not f.startswith("~$"):
|
||||||
|
docx_files.append(os.path.join(dossier_travail, f))
|
||||||
|
|
||||||
|
if not docx_files:
|
||||||
|
print("Aucun fichier .docx trouve.")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
print(f"\n{len(docx_files)} fichier(s) .docx trouve(s)\n")
|
||||||
|
|
||||||
|
# Appliquer les remplacements
|
||||||
|
rapport_global = {}
|
||||||
|
for filepath in sorted(docx_files):
|
||||||
|
rapport = process_docx(filepath, remplacements)
|
||||||
|
rel_path = os.path.relpath(filepath, dossier_travail)
|
||||||
|
rapport_global[rel_path] = rapport
|
||||||
|
|
||||||
|
# Identifier les remplacements non trouves
|
||||||
|
remplacements_non_trouves = set()
|
||||||
|
for remp in remplacements:
|
||||||
|
cle = f"'{remp['ancien']}' -> '{remp['nouveau']}'"
|
||||||
|
trouve = False
|
||||||
|
for fichier, rapport in rapport_global.items():
|
||||||
|
if cle in rapport:
|
||||||
|
trouve = True
|
||||||
|
break
|
||||||
|
if not trouve:
|
||||||
|
remplacements_non_trouves.add(cle)
|
||||||
|
|
||||||
|
# Generer le rapport markdown
|
||||||
|
from datetime import datetime
|
||||||
|
lignes = []
|
||||||
|
lignes.append(f"# Rapport de modifications")
|
||||||
|
lignes.append(f"")
|
||||||
|
lignes.append(f"**Date** : {datetime.now().strftime('%d/%m/%Y %H:%M')}")
|
||||||
|
lignes.append(f"**Template** : `{os.path.relpath(dossier_source, project_dir)}`")
|
||||||
|
lignes.append(f"**Fichiers traites** : {len(docx_files)}")
|
||||||
|
lignes.append(f"")
|
||||||
|
|
||||||
|
lignes.append(f"## Modifications par fichier")
|
||||||
|
lignes.append(f"")
|
||||||
|
for fichier, rapport in sorted(rapport_global.items()):
|
||||||
|
lignes.append(f"### {fichier}")
|
||||||
|
lignes.append(f"")
|
||||||
|
if rapport:
|
||||||
|
lignes.append(f"| Ancien | Nouveau | Occurrences |")
|
||||||
|
lignes.append(f"|---|---|---|")
|
||||||
|
for remp, count in rapport.items():
|
||||||
|
# remp est au format "'ancien' -> 'nouveau'"
|
||||||
|
parties = remp.split("' -> '")
|
||||||
|
ancien_txt = parties[0][1:] # enlever le ' du debut
|
||||||
|
nouveau_txt = parties[1][:-1] # enlever le ' de la fin
|
||||||
|
lignes.append(f"| {ancien_txt} | {nouveau_txt} | {count} |")
|
||||||
|
else:
|
||||||
|
lignes.append(f"Aucun remplacement effectue.")
|
||||||
|
lignes.append(f"")
|
||||||
|
|
||||||
|
if remplacements_non_trouves:
|
||||||
|
lignes.append(f"## Remplacements non trouves")
|
||||||
|
lignes.append(f"")
|
||||||
|
lignes.append(f"Les remplacements suivants n'ont ete trouves dans aucun fichier :")
|
||||||
|
lignes.append(f"")
|
||||||
|
for r in remplacements_non_trouves:
|
||||||
|
lignes.append(f"- {r}")
|
||||||
|
lignes.append(f"")
|
||||||
|
|
||||||
|
# Ecrire le rapport
|
||||||
|
rapport_path = os.path.join(dossier_travail, "rapport.md")
|
||||||
|
with open(rapport_path, "w", encoding="utf-8") as f:
|
||||||
|
f.write("\n".join(lignes))
|
||||||
|
|
||||||
|
print(f"Rapport genere : {rapport_path}")
|
||||||
|
print("Termine!")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
BIN
template/CONDITIONS_GENERALES_DE_VENTE.docx
Normal file
BIN
template/CONDITIONS_GENERALES_DE_VENTE.docx
Normal file
Binary file not shown.
43
template/formation_template.yaml
Normal file
43
template/formation_template.yaml
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
# Configuration de remplacement pour dossier de formation
|
||||||
|
# Usage:
|
||||||
|
# 1. Creer un dossier pour la formation : mkdir formations/ma_formation
|
||||||
|
# 2. Copier ce fichier dedans : cp template/formation_template.yaml formations/ma_formation/formation.yaml
|
||||||
|
# 3. Editer le fichier copie avec les bonnes infos
|
||||||
|
# 4. Lancer : python3 replace_docx.py formations/ma_formation/formation.yaml
|
||||||
|
#
|
||||||
|
# Le script copie automatiquement les .docx du template dans le dossier de la formation.
|
||||||
|
# Les chemins sont relatifs a la racine du projet.
|
||||||
|
|
||||||
|
# Dossier contenant les .docx template
|
||||||
|
dossier_source: template
|
||||||
|
|
||||||
|
# Liste des remplacements (appliques a TOUS les .docx copies)
|
||||||
|
remplacements:
|
||||||
|
# Infos formation
|
||||||
|
- ancien: "Word - Initiation bureautique"
|
||||||
|
nouveau: "Excel Avance - Tableaux croises dynamiques"
|
||||||
|
|
||||||
|
# Infos client
|
||||||
|
- ancien: "Societe Martin SARL"
|
||||||
|
nouveau: "Societe Dupont SAS"
|
||||||
|
|
||||||
|
# Dates
|
||||||
|
- ancien: "10 et 11 mars 2026"
|
||||||
|
nouveau: "15 et 16 avril 2026"
|
||||||
|
|
||||||
|
# Formateur
|
||||||
|
- ancien: "Marie Martin"
|
||||||
|
nouveau: "Jean Dupont"
|
||||||
|
|
||||||
|
# Lieu
|
||||||
|
- ancien: "14 allee d'Argenson, Chatellerault"
|
||||||
|
nouveau: "12 rue des Lilas, Poitiers"
|
||||||
|
|
||||||
|
# Duree
|
||||||
|
- ancien: "7 heures (1 jour)"
|
||||||
|
nouveau: "14 heures (2 jours)"
|
||||||
|
|
||||||
|
# Remplacement cible sur un seul fichier (optionnel)
|
||||||
|
# - ancien: "Texte specifique a la convention"
|
||||||
|
# nouveau: "Nouveau texte convention"
|
||||||
|
# fichiers: ["convention.docx"]
|
||||||
Reference in New Issue
Block a user