#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Fonctions utilitaires pour nettoyer le HTML et formater les dates.
Version simplifiée et robuste: ignore les lignes problématiques.
"""
import re
from datetime import datetime
import html
from bs4 import BeautifulSoup, Tag
from bs4.element import NavigableString, PageElement
from typing import Union, List, Tuple, Optional, Any, Dict, cast
import logging
import html2text
def clean_html(html_content: Union[str, None], is_forwarded: bool = False):
if html_content is None or not isinstance(html_content, str) or html_content.strip() == "":
if is_forwarded:
return "*Message transféré - contenu non extractible*"
return "*Contenu non extractible*"
try:
# Sauvegarder les références d'images avant de nettoyer le HTML
image_references: List[Tuple[str, str]] = []
img_pattern = re.compile(r']+src=["\']([^"\']+)["\'][^>]*>')
for match in img_pattern.finditer(html_content):
full_tag = match.group(0)
img_url = match.group(1)
# Vérifier si c'est une image Odoo
if "/web/image/" in img_url:
image_references.append((full_tag, img_url))
# Nettoyer le HTML
soup = BeautifulSoup(html_content, 'html.parser')
# Supprimer les éléments script, style et head
for elem in soup.find_all(['script', 'style', 'head']):
elem.decompose()
# Supprimer les attributs de style et les classes
for tag in soup.recursiveChildGenerator():
if isinstance(tag, Tag):
if tag.attrs and 'style' in tag.attrs:
del tag.attrs['style']
if tag.attrs and 'class' in tag.attrs:
del tag.attrs['class']
# Conserver uniquement les balises HTML essentielles
allowed_tags = ['p', 'br', 'b', 'i', 'u', 'strong', 'em', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
'ul', 'ol', 'li', 'a', 'img', 'blockquote', 'code', 'pre', 'hr', 'div', 'span',
'table', 'tr', 'td', 'th', 'thead', 'tbody']
# Supprimer les balises HTML inutiles mais conserver leur contenu
for tag in soup.find_all():
if isinstance(tag, Tag) and tag.name.lower() not in allowed_tags:
tag.unwrap()
# Amélioration: vérifier si nous avons du contenu significatif
text_content = soup.get_text().strip()
if not text_content and not image_references:
if is_forwarded:
return "*Message transféré - contenu non extractible*"
return "*Contenu non extractible*"
# Obtenir le HTML nettoyé
clean_content = str(soup)
# Vérifier si le contenu a été vidé par le nettoyage
if clean_content.strip() == "" or clean_content.strip() == "
]*>.*?Bonjour.*?
', html_content, re.DOTALL) acces_match = re.search(r']*>.*?Je ne parviens pas à accéder[^<]*
', html_content, re.DOTALL) specific_content = [] if bonjour_match: specific_content.append(pre_clean_html(bonjour_match.group(0))) if acces_match: specific_content.append(pre_clean_html(acces_match.group(0))) # Extraire les contenus spécifiques du message "Je ne parviens pas..." merci_match = re.search(r']*>.*?Merci par avance.*?
', html_content, re.DOTALL) if merci_match: specific_content.append(pre_clean_html(merci_match.group(0))) cordial_match = re.search(r']*>.*?Cordialement.*?
', html_content, re.DOTALL) if cordial_match: specific_content.append(pre_clean_html(cordial_match.group(0))) if specific_content: text_content = '\n'.join(specific_content) # Supprimer les duplications de lignes lines = text_content.split('\n') unique_lines = [] for line in lines: if line not in unique_lines: unique_lines.append(line) text_content = '\n'.join(unique_lines) # Ajouter les images à la fin if image_markdowns: # Supprimer les doublons d'images unique_images = [] for img in image_markdowns: if img not in unique_images: unique_images.append(img) text_content += "\n\n" + "\n".join(unique_images) return text_content if text_content else "*Contenu non extractible*" except Exception as e: print(f"Erreur lors de l'extraction du texte: {e}") # 3. Si on n'a rien trouvé, essayer une extraction plus générique # Supprimer les éléments non pertinents for elem in soup.select('.o_mail_info, .o_mail_tracking, .o_thread_tooltip, .o_thread_icons, .recipients_info'): try: elem.decompose() except Exception: continue # Extraire le texte restant try: text = soup.get_text(separator='\n', strip=True) text = re.sub(r'\n{3,}', '\n\n', text) # Préserver les images si demandé if preserve_images or True: # Toujours préserver les images # Les images ont déjà été extraites au début de la fonction if image_markdowns: # Supprimer les doublons d'images unique_images = [] for img in image_markdowns: if img not in unique_images: unique_images.append(img) text += "\n\n" + "\n".join(unique_images) # Si on a du contenu, le retourner if text and len(text.strip()) > 5: return text except Exception as e: print(f"Erreur lors de l'extraction générique: {e}") # Si rien n'a fonctionné mais qu'on a des images, au moins les retourner if image_markdowns: unique_images = [] for img in image_markdowns: if img not in unique_images: unique_images.append(img) if any("Je ne parviens pas à accéder" in html_content for img in image_markdowns): return "Bonjour,\nJe ne parviens pas à accéder au l'essai au bleu :\n\n" + "\n".join(unique_images) + "\n\nMerci par avance pour votre.\nCordialement" else: return "Images extraites :\n\n" + "\n".join(unique_images) return "*Contenu non extractible*" except Exception as e: print(f"Erreur lors de l'extraction complexe: {e}") # Dernière tentative : extraction directe avec regex try: # Extraire des images image_markdowns = [] img_matches = re.finditer(r']*>.*?Bonjour.*?
', html_content, re.DOTALL) if bonjour_match: text_parts.append(pre_clean_html(bonjour_match.group(0))) content_match = re.search(r']*>.*?Je ne parviens pas à accéder.*?
', html_content, re.DOTALL) if content_match: text_parts.append(pre_clean_html(content_match.group(0))) # Combiner texte et images if text_parts or image_markdowns: result = "" if text_parts: result += "\n".join(text_parts) + "\n\n" if image_markdowns: unique_images = [] for img in image_markdowns: if img not in unique_images: unique_images.append(img) result += "\n".join(unique_images) return result except Exception: pass return "*Contenu non extractible*" def pre_clean_html(html_content): """ Fonction interne pour nettoyer le HTML basique avant traitement avancé. Args: html_content: Contenu HTML à pré-nettoyer Returns: Texte avec les balises HTML basiques retirées """ if not html_content: return "" # Remplacer les balises,
Bonjour,
Voici un message avec du HTML et une signature.
Cordialement,
John Doe
Support technique
Afin d'assurer une meilleure traçabilité et vous garantir une prise en charge optimale, nous vous invitons à envoyer vos demandes d'assistance technique à support@exemple.fr

""" cleaned = clean_html(html) print("HTML nettoyé :\n", cleaned) # Test avec un message transféré forwarded = """\\-------- Message transféré -------- Sujet : | Test message ---|--- Date : | Mon, 30 Mar 2020 11:18:20 +0200 De : | [test@example.com](mailto:test@example.com) Pour : | John Doe [](mailto:john@example.com) Copie à : | [other@example.com](mailto:other@example.com) Bonjour John, Voici un message de test. Cordialement, Test User __________________________________________________________________ Ce message et toutes les pièces jointes sont confidentiels et établis à l'intention exclusive de ses destinataires. __________________________________________________________________""" cleaned_forwarded = clean_html(forwarded) print("\nMessage transféré nettoyé :\n", cleaned_forwarded) # Test avec le cas problématique du ticket T0282 test_t0282 = """Bonjour, Je reviens vers vous pour savoir si vous souhaitez toujours renommer le numéro d'identification de certaines formules dans BCN ou si vous avez trouvé une solution alternative ? En vous remerciant par avance, je reste à votre disposition pour tout complément d'information. Cordialement. **Youness BENDEQ** [ Affin d'assurer une meilleure traçabilité et vous garantir une prise en charge optimale, nous vous invitons à envoyer vos demandes d'assistance technique à support@cbao.fr Notre service est ouvert du lundi au vendredi de 9h à 12h et de 14h à 18h. Dès réception, un technicien prendra en charge votre demande et au besoin vous rappellera.""" cleaned_t0282 = clean_html(test_t0282) print("\nTest ticket T0282 nettoyé :\n", cleaned_t0282) # Test avec le cas problématique de bas de page avec formatage markdown test_cbao_markdown = """Bonjour, Voici un message de test pour vérifier la suppression des bas de page CBAO. Cordialement, Jean Dupont [ CBAO S.A.R.L. ](https://example.com/link) .  """ cleaned_markdown = clean_html(test_cbao_markdown) print("\nTest avec formatage Markdown CBAO nettoyé :\n", cleaned_markdown) # Test avec le cas exact du rapport test_rapport = """Bonjour, Voici un message de test. Cordialement, Pierre Martin Envoyé par [ CBAO S.A.R.L. ](https://ciibcee.r.af.d.sendibt2.com/tr/cl/h2uBsi9hBosNYeSHMsPH47KAmufMTuNZjreF6M_tfRE63xzft8fwSbEQNb0aYIor74WQB5L6TF4kR9szVpQnalHFa3PUn_0jeLw42JNzIwsESwVlYad_3xCC1xi7qt3-dQ7i_Rt62MG217XgidnJxyNVcXWaWG5B75sB0GoqJq13IZc-hQ) .  """ cleaned_rapport = clean_html(test_rapport) print("\nTest avec cas exact du rapport nettoyé :\n", cleaned_rapport)