Spaces:
Sleeping
Sleeping
Download utils/md_parser.py from DidierHernandez/p14: direct link, hf CLI and curl.
- Browser
- Download file 6.17 kB
-
https://huggingface.co/spaces/DidierHernandez/p14/resolve/main/utils/md_parser.py
- Command line
-
hf download hf://spaces/DidierHernandez/p14/utils/md_parser.py
-
curl -L -o md_parser.py https://huggingface.co/spaces/DidierHernandez/p14/resolve/main/utils/md_parser.py
6.17 kB
| # Ce fichier poc/utils/md_parser.py | |
| import os | |
| import re | |
| import yaml | |
| import logging | |
| from langchain_core.documents import Document | |
| # Récupération du logger configuré centralement | |
| logger = logging.getLogger(__name__) | |
| def parse_md_to_documents(corpus_dir: str) -> list[Document]: | |
| """ | |
| Parcourt le dossier du corpus, lit chaque fichier Markdown (.md), | |
| extrait le Front Matter YAML (métadonnées globales) et découpe le corps | |
| du texte en chunks sémantiques basés sur les Titres 2 (##). | |
| Chaque action ou bloc de hors-périmètre devient un unique Document LangChain. | |
| :param corpus_dir: Chemin vers le dossier contenant les fichiers .md | |
| :return: Liste d'objets Document utilisables directement par FAISS | |
| """ | |
| documents = [] | |
| if not os.path.exists(corpus_dir): | |
| logger.error(f"Le dossier du corpus est introuvable : {corpus_dir}") | |
| return documents | |
| # 1. Parcourir tous les fichiers du dossier corpus | |
| for filename in os.listdir(corpus_dir): | |
| if not filename.endswith('.md'): | |
| continue # On ignore les fichiers qui ne sont pas du Markdown | |
| file_path = os.path.join(corpus_dir, filename) | |
| logger.info(f"Début du traitement du fichier : {filename}") | |
| try: | |
| with open(file_path, 'r', encoding='utf-8') as f: | |
| content = f.read() | |
| # 2. Extraction du Front Matter YAML (Métadonnées globales) | |
| # Les métadonnées sont délimitées par des triples tirets --- en haut du fichier | |
| front_matter = {} | |
| body_text = content | |
| if content.startswith('---'): | |
| # Expression régulière pour isoler le bloc YAML et le reste du texte | |
| match = re.match(r'^---\s*\n(.*?)\n---\s*\n(.*)', content, re.DOTALL) | |
| if match: | |
| yaml_content = match.group(1) | |
| body_text = match.group(2) | |
| # Chargement sécurisé du dictionnaire de métadonnées YAML | |
| front_matter = yaml.safe_load(yaml_content) or {} | |
| logger.debug(f"Front Matter extrait avec succès pour {filename}") | |
| # 3. Découpage du texte par Action (Titre 2 : ##) | |
| # On utilise un regex avec capture pour conserver les titres '## Nom de l'action' | |
| # re.split va générer une liste alternant le texte précédent (souvent vide), le titre, puis le contenu | |
| sections = re.split(r'^(##\s+.*)$', body_text, flags=re.MULTILINE) | |
| # La première section contient le texte avant le premier ## (Ex: Titre 1 du Process) | |
| # On peut l'extraire pour enrichir le contexte global si nécessaire | |
| process_intro = sections[0].strip() | |
| # 4. Itération par paires (Titre H2, Contenu du H2) | |
| # On commence à l'index 1 et on avance de 2 en 2 | |
| for i in range(1, len(sections), 2): | |
| h2_title = sections[i].strip() | |
| h2_content = sections[i+1].strip() if (i+1) < len(sections) else "" | |
| # Nettoyage cosmétique du titre de l'action (on enlève les '## ') | |
| action_name = h2_title.lstrip('#').strip() | |
| # Reconstitution du texte complet du chunk (Titre + Corps) | |
| # On préserve scrupuleusement le formatage Markdown d'origine (comme le **Gras**) | |
| full_chunk_text = f"{h2_title}\n\n{h2_content}" | |
| # 5. Extraction des métadonnées locales spécifiques à l'action | |
| # On cherche les lignes clés "Écran de départ : ..." et "Mots-clés : ..." dans le texte du bloc | |
| ecran_depart_match = re.search(r'(?:Écran de départ|Ecran de depart)\s*:\s*(.*)', h2_content, re.IGNORECASE) | |
| mots_cles_match = re.search(r'(?:Mots-clés|Mots-cles|Mots clés)\s*:\s*(.*)', h2_content, re.IGNORECASE) | |
| ecran_depart = ecran_depart_match.group(1).strip() if ecran_depart_match else "N/A" | |
| mots_cles = mots_cles_match.group(1).strip() if mots_cles_match else "N/A" | |
| # 6. Fusion des métadonnées (Globales + Locales) | |
| # C'est l'équivalent de votre mapping standardisé pour format_docs() | |
| metadata = { | |
| "source_file": filename, | |
| "process": front_matter.get("titre", filename.replace(".md", "")), | |
| "auteur": front_matter.get("auteur", "Inconnu"), | |
| "version": front_matter.get("version", "1.0"), | |
| "date_maj": front_matter.get("date_maj", "N/A"), | |
| "action": action_name, | |
| "ecran_depart": ecran_depart, | |
| "mots_cles": mots_cles | |
| } | |
| # 7. Instanciation du Document LangChain | |
| doc = Document( | |
| page_content=full_chunk_text, | |
| metadata=metadata | |
| ) | |
| documents.append(doc) | |
| logger.info(f"Fichier {filename} traité : {len(sections) // 2} chunks créés.") | |
| except Exception as e: | |
| logger.error(f"Erreur lors du parsing du fichier {filename} : {e}") | |
| continue | |
| logger.info(f"Fin du parsing global. Total de documents LangChain créés : {len(documents)}") | |
| return documents | |
| # Bloc de test unitaire rapide pour valider le comportement en isolation | |
| if __name__ == "__main__": | |
| # Configuration d'un logging basique pour le test | |
| logging.basicConfig(level=logging.INFO) | |
| # Simulation d'un appel vers un dossier de test local | |
| test_dir = "./corpus" | |
| print(f"--- Test du parser sur le dossier {test_dir} ---") | |
| docs = parse_md_to_documents(test_dir) | |
| if docs: | |
| print(f"\nExemple de structure du premier Document LangChain généré :") | |
| print(f"METADATA : {docs[0].metadata}") | |
| print(f"CONTENT PREVIEW (200 chars) :\n{docs[0].page_content[:200]}...") |