
# Fichier: python_cheats/cheatsheets/regex.txt
# Cheatsheet Regex Python - Guide Complet


[OK] IMPORT
import re
from typing import Match, Pattern, List, Optional


[OK] FONCTIONS DE BASE


text = "Hello World, Hello Python"

# search() - Trouve la première occurrence
match = re.search(r'World', text)
if match:
    print(f"Trouvé à position {match.start()}")  # Position 6

# match() - Vérifie au DÉBUT de la chaîne uniquement
match = re.match(r'Hello', text)  # Succès
match = re.match(r'World', text)  # None (pas au début)

# fullmatch() - Vérifie que TOUTE la chaîne correspond
match = re.fullmatch(r'Hello World, Hello Python', text)  # Succès
match = re.fullmatch(r'Hello', text)  # None (pas toute la chaîne)

# findall() - Toutes les occurrences (liste de strings)
text = "cat bat rat"
matches = re.findall(r'.at', text)  # ['cat', 'bat', 'rat']

# finditer() - Itérateur de Match objects
for match in re.finditer(r'.at', text):
    print(f"{match.group()} à position {match.start()}-{match.end()}")

# sub() - Remplacement
result = re.sub(r'cat', 'dog', text)  # "dog bat rat"

# subn() - Remplacement avec compteur
result, count = re.subn(r'at', 'ot', text)  # ("cot bot rot", 3)

# split() - Découpage
text = "one,two;three:four"
parts = re.split(r'[,;:]', text)  # ['one', 'two', 'three', 'four']

# escape() - Échapper caractères spéciaux
special = "1+1=2"
escaped = re.escape(special)  # "1\+1\=2"


[OK] MATCH OBJECTS


text = "John: 30 years old"
match = re.search(r'(\w+): (\d+)', text)

if match:
    # Récupérer la correspondance complète
    match.group()      # "John: 30"
    match.group(0)     # "John: 30" (pareil)
    
    # Récupérer les groupes
    match.group(1)     # "John"
    match.group(2)     # "30"
    match.groups()     # ("John", "30")
    
    # Positions
    match.start()      # 0 (début)
    match.end()        # 8 (fin)
    match.span()       # (0, 8)
    
    # Position d'un groupe
    match.start(1)     # 0
    match.end(1)       # 4
    match.span(1)      # (0, 4)
    
    # String originale
    match.string       # "John: 30 years old"
    
    # Pattern utilisé
    match.re           # compiled pattern object
    
    # Dernier groupe capturé
    match.lastgroup    # None ou nom du groupe
    match.lastindex    # 2 (dernier index de groupe)


[OK] GROUPES


# Groupes numérotés
text = "John: 30, Jane: 25"
match = re.search(r'(\w+): (\d+)', text)
if match:
    name = match.group(1)   # 'John'
    age = match.group(2)    # '30'
    all_groups = match.groups()  # ('John', '30')

# Groupes nommés
pattern = r'(?P<name>\w+): (?P<age>\d+)'
match = re.search(pattern, text)
if match:
    print(match.group('name'))        # 'John'
    print(match.group('age'))         # '30'
    print(match.groupdict())          # {'name': 'John', 'age': '30'}

# Groupes non-capturants (?:...)
# Utile pour grouper sans créer de groupe de capture
text = "http://example.com"
match = re.search(r'(?:http|https)://(\w+)', text)
# match.group(1) = "example" (pas "http")

# Références arrière (backreferences)
# \1, \2, etc. référencent les groupes capturés
text = "hello hello world"
match = re.search(r'(\w+) \1', text)  # Trouve "hello hello"

# Références arrière nommées
pattern = r'(?P<word>\w+) (?P=word)'
match = re.search(pattern, text)

# Groupes avec assertions
# Capturer seulement si suivi de quelque chose
pattern = r'(\w+)(?=:)'
matches = re.findall(pattern, 'name: John, age: 30')  # ['name', 'age']


[OK] CARACTÈRES SPÉCIAUX & MÉTACARACTÈRES


# Quantificateurs
.       # N'importe quel caractère sauf \n
^       # Début de chaîne (ou ligne avec MULTILINE)
$       # Fin de chaîne (ou ligne avec MULTILINE)
*       # 0 ou plus (greedy)
+       # 1 ou plus (greedy)
?       # 0 ou 1 (greedy)
{n}     # Exactement n fois
{n,}    # n fois ou plus
{n,m}   # Entre n et m fois
{,m}    # Maximum m fois

# Quantificateurs non-greedy (lazy)
*?      # 0 ou plus (minimal)
+?      # 1 ou plus (minimal)
??      # 0 ou 1 (minimal)
{n,m}?  # Entre n et m (minimal)

# Exemples greedy vs non-greedy
text = "<div>content</div>"
re.search(r'<.*>', text).group()    # '<div>content</div>' (greedy)
re.search(r'<.*?>', text).group()   # '<div>' (non-greedy)

# Ensembles de caractères
[abc]       # a, b, ou c
[^abc]      # Tout sauf a, b, c
[a-z]       # Lettres minuscules
[A-Z]       # Lettres majuscules
[0-9]       # Chiffres
[a-zA-Z]    # Toutes les lettres
[a-zA-Z0-9] # Lettres et chiffres

# Opérateurs
|       # OU logique
()      # Groupe
\       # Échapper un caractère spécial


[OK] CLASSES DE CARACTÈRES


\d      # Chiffre [0-9]
\D      # Non-chiffre [^0-9]
\w      # Lettre, chiffre ou underscore [a-zA-Z0-9_]
\W      # Non-mot [^a-zA-Z0-9_]
\s      # Espace blanc [ \t\n\r\f\v]
\S      # Non-espace [^ \t\n\r\f\v]
\b      # Frontière de mot (word boundary)
\B      # Non-frontière de mot
\A      # Début de chaîne (comme ^)
\Z      # Fin de chaîne (comme $)

# Caractères spéciaux
\n      # Nouvelle ligne
\r      # Retour chariot
\t      # Tabulation
\f      # Form feed
\v      # Tabulation verticale
\\      # Backslash littéral
\.      # Point littéral
\*      # Astérisque littéral
\+      # Plus littéral
\?      # Point d'interrogation littéral
\[      # Crochet ouvrant littéral
\]      # Crochet fermant littéral
\(      # Parenthèse ouvrante littérale
\)      # Parenthèse fermante littérale
\{      # Accolade ouvrante littérale
\}      # Accolade fermante littérale
\|      # Pipe littéral
\^      # Circonflexe littéral
\$      # Dollar littéral

# Unicode
\u0041      # Caractère Unicode (A)
\U00000041  # Caractère Unicode long (A)
\N{name}    # Caractère Unicode nommé


[OK] FLAGS (MODIFICATEURS)


# re.IGNORECASE ou re.I - Insensible à la casse
match = re.search(r'hello', 'HELLO', re.IGNORECASE)

# re.MULTILINE ou re.M - ^ et $ pour chaque ligne
text = "line1\nline2\nline3"
matches = re.findall(r'^line\d', text, re.MULTILINE)  # ['line1', 'line2', 'line3']

# re.DOTALL ou re.S - . inclut \n
text = "first\nsecond"
match = re.search(r'first.second', text, re.DOTALL)  # Succès

# re.VERBOSE ou re.X - Permet espaces et commentaires
pattern = r"""
    \d{3}    # Area code
    -        # Separator
    \d{3}    # Prefix
    -        # Separator
    \d{4}    # Line number
"""
match = re.search(pattern, '123-456-7890', re.VERBOSE)

# re.ASCII ou re.A - \w, \d, etc. seulement ASCII
pattern = r'\w+'
text = "café"
re.findall(pattern, text)           # ['café']
re.findall(pattern, text, re.ASCII) # ['caf']

# re.LOCALE ou re.L - Dépend de la locale (obsolète)

# Combiner plusieurs flags
match = re.search(r'hello', text, re.IGNORECASE | re.MULTILINE)
# Ou avec inline flags
match = re.search(r'(?im)hello', text)

# Inline flags dans le pattern
(?i)    # IGNORECASE
(?m)    # MULTILINE
(?s)    # DOTALL
(?x)    # VERBOSE
(?a)    # ASCII


[OK] ASSERTIONS (LOOKAHEAD & LOOKBEHIND)


# Positive lookahead (?=...)
# Correspond si suivi par le pattern
text = "name: John, age: 30"
re.findall(r'\w+(?=:)', text)  # ['name', 'age']

# Negative lookahead (?!...)
# Correspond si PAS suivi par le pattern
text = "100px 200em 300pt"
re.findall(r'\d+(?!px)', text)  # ['200', '300']

# Positive lookbehind (?<=...)
# Correspond si précédé par le pattern
text = "Price: $100, Cost: $50"
re.findall(r'(?<=\$)\d+', text)  # ['100', '50']

# Negative lookbehind (?<!...)
# Correspond si PAS précédé par le pattern
text = "user@email.com"
re.findall(r'(?<!@)\w+', text)  # ['user', 'email', 'com']

# Assertions complexes
# Prix avec devise mais pas EUR
pattern = r'(?<!\bEUR)\b\d+(?:\.\d{2})?'
text = "USD100.50 EUR50.00 GBP75.25"
re.findall(pattern, text)  # ['100.50', '75.25']


[OK] COMPILATION (PERFORMANCE)


# Compiler un pattern pour réutilisation
pattern = re.compile(r'\d+')
matches = pattern.findall("10 20 30")

# Avec flags
pattern = re.compile(r'hello', re.IGNORECASE)

# Réutiliser le pattern (plus rapide)
match1 = pattern.search(text1)
match2 = pattern.search(text2)
matches = pattern.findall(text3)

# Méthodes disponibles sur pattern compilé
pattern.search(text)
pattern.match(text)
pattern.fullmatch(text)
pattern.findall(text)
pattern.finditer(text)
pattern.sub(repl, text)
pattern.subn(repl, text)
pattern.split(text)

# Attributs du pattern
pattern.pattern      # String du pattern
pattern.flags        # Flags utilisés
pattern.groups       # Nombre de groupes
pattern.groupindex   # Dict des groupes nommés


[OK] REMPLACEMENT AVANCÉ


text = "Price: 10, Quantity: 5"

# Remplacement simple
result = re.sub(r'\d+', '0', text)  # "Price: 0, Quantity: 0"

# Remplacement avec fonction
def double(match):
    return str(int(match.group()) * 2)

result = re.sub(r'\d+', double, text)  # "Price: 20, Quantity: 10"

# Avec lambda
result = re.sub(r'\d+', lambda m: str(int(m.group()) * 2), text)

# Utiliser groupes dans remplacement
text = "John Doe, Jane Smith"
result = re.sub(r'(\w+) (\w+)', r'\2, \1', text)
# "Doe, John, Smith, Jane"

# Groupes nommés dans remplacement
text = "2024-10-26"
result = re.sub(
    r'(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})',
    r'\g<day>/\g<month>/\g<year>',
    text
)  # "26/10/2024"

# Remplacement conditionnel
def smart_replace(match):
    value = int(match.group())
    if value > 10:
        return "HIGH"
    else:
        return "LOW"

result = re.sub(r'\d+', smart_replace, "5 15 3 20")  # "LOW HIGH LOW HIGH"

# Limiter le nombre de remplacements
result = re.sub(r'a', 'X', 'aaa bbb aaa', count=2)  # "XXa bbb aaa"

# subn() - Remplacement avec compteur
result, count = re.subn(r'a', 'X', 'aaa bbb aaa')
# result = "XXX bbb XXX", count = 6


[OK] SPLIT AVANCÉ


# Split simple
text = "one,two,three"
parts = re.split(r',', text)  # ['one', 'two', 'three']

# Split sur plusieurs délimiteurs
text = "one,two;three:four|five"
parts = re.split(r'[,;:|]', text)  # ['one', 'two', 'three', 'four', 'five']

# Garder les délimiteurs (avec groupe de capture)
text = "one,two;three"
parts = re.split(r'([,;])', text)  # ['one', ',', 'two', ';', 'three']

# Limiter le nombre de splits
text = "a,b,c,d,e"
parts = re.split(r',', text, maxsplit=2)  # ['a', 'b', 'c,d,e']

# Split sur whitespace variable
text = "one  two   three"
parts = re.split(r'\s+', text)  # ['one', 'two', 'three']

# Split en gardant les espaces de début/fin
text = "  one  two  "
parts = re.split(r'\s+', text)  # ['', 'one', 'two', '']
parts = [p for p in parts if p]  # ['one', 'two']


[OK] VALIDATION


# Validation simple
def is_valid_email(email: str) -> bool:
    pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
    return bool(re.match(pattern, email))

# Validation avec fullmatch (recommandé)
def validate_phone(phone: str) -> bool:
    pattern = r'\d{3}-\d{3}-\d{4}'
    return bool(re.fullmatch(pattern, phone))

# Validation complexe
def validate_password(password: str) -> tuple[bool, str]:
    """Valide: 8+ chars, 1 majuscule, 1 minuscule, 1 chiffre, 1 spécial"""
    if len(password) < 8:
        return False, "Trop court"
    if not re.search(r'[A-Z]', password):
        return False, "Pas de majuscule"
    if not re.search(r'[a-z]', password):
        return False, "Pas de minuscule"
    if not re.search(r'\d', password):
        return False, "Pas de chiffre"
    if not re.search(r'[!@#$%^&*]', password):
        return False, "Pas de caractère spécial"
    return True, "Valide"


[OK] EXEMPLES PRATIQUES - EMAIL


# Email simple
pattern = r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b'
emails = re.findall(pattern, text)

# Email plus strict
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
is_valid = bool(re.match(pattern, email))

# Email avec validation TLD
pattern = r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.(com|org|net|edu|gov)\b'

# Extraire parties de l'email
pattern = r'(?P<user>[^@]+)@(?P<domain>.+)'
match = re.match(pattern, 'john.doe@example.com')
# match.group('user') = 'john.doe'
# match.group('domain') = 'example.com'


[OK] EXEMPLES PRATIQUES - TÉLÉPHONE


# Format US: 123-456-7890
pattern = r'\d{3}-\d{3}-\d{4}'
phones = re.findall(pattern, text)

# Format US flexible: (123) 456-7890 ou 123-456-7890
pattern = r'\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}'

# International avec code pays
pattern = r'\+?\d{1,3}[-.\s]?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}'

# Extraire parties
pattern = r'\(?(\d{3})\)?[-.\s]?(\d{3})[-.\s]?(\d{4})'
match = re.search(pattern, '(123) 456-7890')
# area, prefix, line = match.groups()

# Normaliser format
def normalize_phone(phone: str) -> str:
    digits = re.sub(r'\D', '', phone)  # Garder chiffres seulement
    if len(digits) == 10:
        return f"{digits[:3]}-{digits[3:6]}-{digits[6:]}"
    return phone


[OK] EXEMPLES PRATIQUES - URL


# URL basique
pattern = r'https?://[\w\-\.]+\.\w+'
urls = re.findall(pattern, text)

# URL complète avec path et query
pattern = r'https?://(?:www\.)?[\w\-\.]+\.\w+(?:/[\w\-\./?%&=]*)?'

# URL avec validation stricte
pattern = r'^https?://(?:www\.)?[\w\-]+\.[\w\-]+(?:\.[\w\-]+)*(?:/[\w\-\./?%&=]*)?$'

# Extraire composants URL
pattern = r'(?P<protocol>https?)://(?P<domain>[\w\-\.]+)(?P<path>/[\w\-\./?%&=]*)?'
match = re.search(pattern, 'https://example.com/path/to/page')
# match.groupdict() = {'protocol': 'https', 'domain': 'example.com', 'path': '/path/to/page'}


[OK] EXEMPLES PRATIQUES - DATE


# Format DD/MM/YYYY
pattern = r'\b\d{2}/\d{2}/\d{4}\b'
dates = re.findall(pattern, text)

# Format flexible: DD/MM/YYYY, DD-MM-YYYY, DD.MM.YYYY
pattern = r'\b\d{2}[/-.]\d{2}[/-.]\d{4}\b'

# Format YYYY-MM-DD (ISO)
pattern = r'\b\d{4}-\d{2}-\d{2}\b'

# Validation de date avec valeurs réalistes
pattern = r'\b(?:0[1-9]|[12]\d|3[01])/(?:0[1-9]|1[0-2])/\d{4}\b'

# Extraire composants
pattern = r'(?P<day>\d{2})/(?P<month>\d{2})/(?P<year>\d{4})'
match = re.search(pattern, '26/10/2024')
# match.groupdict() = {'day': '26', 'month': '10', 'year': '2024'}

# Convertir format
def convert_date(date_str: str) -> str:
    """Convertit DD/MM/YYYY vers YYYY-MM-DD"""
    match = re.match(r'(\d{2})/(\d{2})/(\d{4})', date_str)
    if match:
        day, month, year = match.groups()
        return f"{year}-{month}-{day}"
    return date_str


[OK] EXEMPLES PRATIQUES - IP ADDRESS


# IPv4 simple
pattern = r'\b(?:\d{1,3}\.){3}\d{1,3}\b'
ips = re.findall(pattern, text)

# IPv4 avec validation (0-255)
pattern = r'\b(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\b'

# IPv6 (simplifié)
pattern = r'\b(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}\b'

# IPv4 ou IPv6
pattern = r'\b(?:(?:\d{1,3}\.){3}\d{1,3}|(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4})\b'


[OK] EXEMPLES PRATIQUES - PRIX & MONNAIE


# Prix avec devise
pattern = r'\$\d+(?:\.\d{2})?'
prices = re.findall(pattern, 'Items: $10.99, $5, $100.50')

# Prix avec devise flexible
pattern = r'[$€£]\s*\d+(?:[.,]\d{2,3})?'

# Extraire prix sans devise
pattern = r'(?:[$€£]\s*)(\d+(?:[.,]\d{2})?)'
amounts = re.findall(pattern, text)

# Normaliser prix
def extract_price(text: str) -> Optional[float]:
    match = re.search(r'(\d+(?:\.\d{2})?)', text)
    return float(match.group(1)) if match else None


[OK] EXEMPLES PRATIQUES - CODE POSTAL


# US ZIP Code
pattern = r'\b\d{5}(?:-\d{4})?\b'

# Canada
pattern = r'\b[A-Z]\d[A-Z]\s?\d[A-Z]\d\b'

# UK
pattern = r'\b[A-Z]{1,2}\d{1,2}\s?\d[A-Z]{2}\b'

# France
pattern = r'\b\d{5}\b'


[OK] EXEMPLES PRATIQUES - CARTE DE CRÉDIT


# Format générique (avec espaces/tirets optionnels)
pattern = r'\b\d{4}[- ]?\d{4}[- ]?\d{4}[- ]?\d{4}\b'

# Visa (commence par 4)
pattern = r'\b4\d{3}[- ]?\d{4}[- ]?\d{4}[- ]?\d{4}\b'

# Mastercard (commence par 5)
pattern = r'\b5\d{3}[- ]?\d{4}[- ]?\d{4}[- ]?\d{4}\b'

# Masquer numéro (garder 4 derniers chiffres)
def mask_credit_card(text: str) -> str:
    pattern = r'\b(\d{4})[- ]?(\d{4})[- ]?(\d{4})[- ]?(\d{4})\b'
    return re.sub(pattern, r'****-****-****-\4', text)


[OK] EXEMPLES PRATIQUES - HTML/XML


# Extraire contenu de balise
pattern = r'<title>(.*?)</title>'
match = re.search(pattern, html, re.IGNORECASE)

# Supprimer toutes les balises HTML
clean_text = re.sub(r'<[^>]+>', '', html)

# Extraire attributs
pattern = r'<a\s+href=["\'](.*?)["\']'
links = re.findall(pattern, html)

# Extraire images
pattern = r'<img\s+src=["\'](.*?)["\']'
images = re.findall(pattern, html)

# [ATTENTION] Pour parsing HTML/XML complexe, utiliser BeautifulSoup ou lxml


[OK] EXEMPLES PRATIQUES - MARKDOWN


# Headers
pattern = r'^#{1,6}\s+(.+)$'
headers = re.findall(pattern, text, re.MULTILINE)

# Liens
pattern = r'\[([^\]]+)\]\(([^\)]+)\)'
links = re.findall(pattern, text)  # [(text, url), ...]

# Images
pattern = r'!\[([^\]]*)\]\(([^\)]+)\)'
images = re.findall(pattern, text)

# Code inline
pattern = r'`([^`]+)`'
code = re.findall(pattern, text)

# Code blocks
pattern = r'```(\w+)?\n(.*?)```'
blocks = re.findall(pattern, text, re.DOTALL)


[OK] EXEMPLES PRATIQUES - LOGS


# Log line standard
pattern = r'(\d{4}-\d{2}-\d{2})\s+(\d{2}:\d{2}:\d{2})\s+(\w+)\s+(.+)'
log = "2024-10-26 10:30:45 ERROR Database connection failed"
match = re.match(pattern, log)
# date, time, level, message = match.groups()

# Extraire erreurs seulement
pattern = r'^\d{4}-\d{2}-\d{2}.*?(ERROR|CRITICAL).*$'
errors = re.findall(pattern, logs, re.MULTILINE)

# IP dans logs
pattern = r'\b(?:\d{1,3}\.){3}\d{1,3}\b'
ips = re.findall(pattern, logs)


[OK] EXEMPLES PRATIQUES - NETTOYAGE TEXTE


# Supprimer espaces multiples
text = re.sub(r'\s+', ' ', text)

# Supprimer espaces début/fin de lignes
text = re.sub(r'^\s+|\s+$', '', text, flags=re.MULTILINE)

# Supprimer lignes vides
text = re.sub(r'\n\s*\n', '\n', text)

# Supprimer ponctuation
text = re.sub(r'[^\w\s]', '', text)

# Garder seulement lettres et chiffres
text = re.sub(r'[^a-zA-Z0-9\s]', '', text)

# Normaliser sauts de ligne
text = re.sub(r'\r\n|\r', '\n', text)


[OK] EXEMPLES PRATIQUES - EXTRACTION DE DONNÉES


# Extraire nombres
numbers = re.findall(r'-?\d+(?:\.\d+)?', text)  # Inclut négatifs et décimaux

# Extraire mots
words = re.findall(r'\b\w+\b', text)

# Extraire hashtags
hashtags = re.findall(r'#\w+', text)

# Extraire mentions (@username)
mentions = re.findall(r'@\w+', text)

# Extraire variables Python
variables = re.findall(r'\b[a-z_][a-z0-9_]*\b', code)

# Extraire clés de dictionnaire
text = "{'name': 'John', 'age': 30}"
keys = re.findall(r"'(\w+)':", text)


[OK] PERFORMANCE & OPTIMISATION


# Compiler patterns réutilisés
pattern = re.compile(r'\d+')  # Compile une fois
for line in lines:
    matches = pattern.findall(line)  # Réutilise

# Éviter catastrophic backtracking
# [X] Mauvais: (a+)+b
[OK] Bon: a+b

# Utiliser quantificateurs non-greedy quand approprié
# [X] Lent: <.*>
[OK] Rapide: <.*?>

# Ancrer le pattern si possible
# [X] Lent: \d{3}
[OK] Rapide: ^\d{3}$ (si applicable)

# Utiliser groupes non-capturants quand pas besoin de capture
# [X] Moins optimal: (http|https)://
[OK] Optimal: (?:http|https)://

# Utiliser caractère littéral au lieu de classe quand possible
# [X] Moins optimal: [.]
[OK] Optimal: \.

# Benchmark de patterns
import time

def benchmark_pattern(pattern, text, iterations=10000):
    compiled = re.compile(pattern)
    start = time.time()
    for _ in range(iterations):
        compiled.search(text)
    return time.time() - start


[OK] UNICODE & ENCODAGE


# Supporter caractères Unicode
text = "café résumé naïve"
pattern = r'\w+'
matches = re.findall(pattern, text)  # ['café', 'résumé', 'naïve']

# ASCII seulement
matches = re.findall(pattern, text, re.ASCII)  # ['caf', 'r', 'sum', 'na', 've']

# Catégories Unicode
\p{L}   # Lettre (nécessite regex module)
\p{N}   # Nombre
\p{P}   # Ponctuation
\p{Z}   # Séparateur

# Avec module regex (pip install regex)
import regex
pattern = regex.compile(r'\p{L}+')
matches = pattern.findall(text)

# Normalisation Unicode
import unicodedata
text = unicodedata.normalize('NFKD', text)


[OK] PATTERNS COMPLEXES


# Palindrome
pattern = r'\b(\w)(\w?)(\w?)\3\2\1\b'  # 3-6 lettres
# Ex: "bob", "noon", "racecar"

# Mots en double consécutifs
pattern = r'\b(\w+)\s+\1\b'
text = "the the quick brown brown fox"
matches = re.findall(pattern, text, re.IGNORECASE)  # ['the', 'brown']

# Nombre avec séparateurs de milliers
pattern = r'\b\d{1,3}(?:,\d{3})*(?:\.\d+)?\b'
numbers = re.findall(pattern, "1,234,567.89 and 12,345")

# Expressions mathématiques simples
pattern = r'\d+\s*[+\-*/]\s*\d+'
expressions = re.findall(pattern, "10 + 5 - 3 * 2 / 4")

# Balanced parentheses (limité)
pattern = r'\([^()]*\)'
inner = re.findall(pattern, "text (inner) more (text)")


[OK] CONDITIONAL PATTERNS


# If-then-else dans regex (avancé)
# (?(condition)yes-pattern|no-pattern)

# Exemple: valider format conditionnel
# Si commence par +, doit avoir code pays
pattern = r'^(\+)?(?:(\1)\d{1,3}-?)?\d{3}-\d{3}-\d{4}


[OK] GESTION D'ERREURS


# Try-except pour patterns invalides
try:
    pattern = re.compile(r'[invalid(')
except re.error as e:
    print(f"Pattern invalide: {e}")

# Valider pattern avant utilisation
def is_valid_pattern(pattern_str: str) -> bool:
    try:
        re.compile(pattern_str)
        return True
    except re.error:
        return False


[OK] DÉBOGAGE DE REGEX


# Afficher matches avec détails
def debug_matches(pattern: str, text: str):
    for match in re.finditer(pattern, text):
        print(f"Match: '{match.group()}'")
        print(f"  Position: {match.span()}")
        print(f"  Groupes: {match.groups()}")
        print()

# Tester pattern en ligne
# https://regex101.com (recommandé)
# https://regexr.com
# https://pythex.org

# Mode verbose pour patterns complexes
pattern = r"""
    ^                   # Début
    (?P<protocol>https?)  # Protocole
    ://                 # Séparateur
    (?P<domain>[\w.-]+)   # Domaine
    (?P<path>/.*)?        # Path optionnel
    $                   # Fin
"""
compiled = re.compile(pattern, re.VERBOSE)


[OK] ALTERNATIVES AU MODULE re


# Module regex (plus puissant)
# pip install regex
import regex

# Supporte:
# - Lookbehind de longueur variable
# - Récursion
# - Propriétés Unicode (\p{...})
# - Fuzzy matching
# - Atomic grouping

# Exemple avec regex
pattern = regex.compile(r'\p{L}+')  # Toutes lettres Unicode
matches = pattern.findall("café résumé 日本語")

# Fuzzy matching (approximatif)
pattern = regex.compile(r'(hello){e<=1}')  # 1 erreur max
matches = pattern.findall("helo hello hallo")


[OK] CAS D'USAGE RÉELS


# 1. Parser CSV simple
def parse_csv_line(line: str) -> List[str]:
    """Parse ligne CSV (simple, sans quotes)"""
    return re.split(r',\s*', line.strip())

# 2. Extraire variables d'un template
def extract_template_vars(template: str) -> List[str]:
    """Extrait {{variable}} d'un template"""
    return re.findall(r'\{\{(\w+)\}\}', template)

# 3. Valider identifiant
def is_valid_identifier(name: str) -> bool:
    """Valide nom de variable Python"""
    return bool(re.fullmatch(r'[a-zA-Z_][a-zA-Z0-9_]*', name))

# 4. Slugify (URL-friendly)
def slugify(text: str) -> str:
    """Convertit en slug URL-friendly"""
    text = text.lower()
    text = re.sub(r'[^\w\s-]', '', text)
    text = re.sub(r'[-\s]+', '-', text)
    return text.strip('-')

# 5. Extraire domaine d'email
def extract_domain(email: str) -> Optional[str]:
    """Extrait domaine d'un email"""
    match = re.search(r'@([\w.-]+)', email)
    return match.group(1) if match else None

# 6. Formatter numéro de téléphone
def format_phone(phone: str) -> str:
    """Formate numéro US: (123) 456-7890"""
    digits = re.sub(r'\D', '', phone)
    if len(digits) == 10:
        return f"({digits[:3]}) {digits[3:6]}-{digits[6:]}"
    return phone

# 7. Extraire mentions et hashtags (Twitter-like)
def extract_social(text: str) -> dict:
    """Extrait mentions et hashtags"""
    return {
        'mentions': re.findall(r'@(\w+)', text),
        'hashtags': re.findall(r'#(\w+)', text)
    }

# 8. Censurer mots sensibles
def censor_words(text: str, words: List[str]) -> str:
    """Remplace mots par des astérisques"""
    pattern = r'\b(' + '|'.join(re.escape(w) for w in words) + r')\b'
    return re.sub(pattern, lambda m: '*' * len(m.group()), text, flags=re.IGNORECASE)

# 9. Extraire code couleur hex
def extract_colors(css: str) -> List[str]:
    """Extrait codes couleurs hex CSS"""
    return re.findall(r'#[0-9A-Fa-f]{3,6}\b', css)

# 10. Parser logs Apache/Nginx
def parse_log_line(line: str) -> Optional[dict]:
    """Parse ligne de log web"""
    pattern = r'(\S+) - - \[(.*?)\] "(\S+) (\S+) (\S+)" (\d+) (\d+)'
    match = re.match(pattern, line)
    if match:
        return {
            'ip': match.group(1),
            'timestamp': match.group(2),
            'method': match.group(3),
            'path': match.group(4),
            'protocol': match.group(5),
            'status': int(match.group(6)),
            'size': int(match.group(7))
        }
    return None

# 11. Extraire version (semver)
def extract_version(text: str) -> Optional[str]:
    """Extrait version sémantique"""
    match = re.search(r'\bv?(\d+\.\d+\.\d+(?:-[\w.]+)?)\b', text)
    return match.group(1) if match else None

# 12. Remplacer variables dans template
def render_template(template: str, context: dict) -> str:
    """Remplace {{var}} par valeurs"""
    def replace(match):
        var_name = match.group(1)
        return str(context.get(var_name, match.group(0)))
    return re.sub(r'\{\{(\w+)\}\}', replace, template)

# 13. Extraire balises XML/HTML simples
def extract_tag_content(html: str, tag: str) -> List[str]:
    """Extrait contenu d'une balise"""
    pattern = f'<{tag}>(.*?)</{tag}>'
    return re.findall(pattern, html, re.DOTALL)

# 14. Convertir camelCase en snake_case
def camel_to_snake(name: str) -> str:
    """Convertit camelCase en snake_case"""
    name = re.sub(r'([A-Z]+)([A-Z][a-z])', r'\1_\2', name)
    name = re.sub(r'([a-z\d])([A-Z])', r'\1_\2', name)
    return name.lower()

# 15. Convertir snake_case en camelCase
def snake_to_camel(name: str) -> str:
    """Convertit snake_case en camelCase"""
    components = name.split('_')
    return components[0] + ''.join(x.title() for x in components[1:])


[OK] VALIDATION AVANCÉE


# Validation mot de passe fort
def validate_strong_password(password: str) -> dict:
    """Valide force du mot de passe"""
    checks = {
        'length': len(password) >= 8,
        'uppercase': bool(re.search(r'[A-Z]', password)),
        'lowercase': bool(re.search(r'[a-z]', password)),
        'digit': bool(re.search(r'\d', password)),
        'special': bool(re.search(r'[!@#$%^&*(),.?":{}|<>]', password)),
        'no_spaces': not bool(re.search(r'\s', password))
    }
    checks['valid'] = all(checks.values())
    return checks

# Validation numéro de sécurité sociale (US)
def validate_ssn(ssn: str) -> bool:
    """Valide SSN format XXX-XX-XXXX"""
    pattern = r'^(?!000|666|9\d{2})\d{3}-(?!00)\d{2}-(?!0000)\d{4}
    return bool(re.match(pattern, ssn))

# Validation IBAN (simplifié)
def validate_iban(iban: str) -> bool:
    """Valide format IBAN"""
    pattern = r'^[A-Z]{2}\d{2}[A-Z0-9]{1,30}
    iban = iban.replace(' ', '')
    return bool(re.match(pattern, iban))

# Validation plaque d'immatriculation (France)
def validate_license_plate_fr(plate: str) -> bool:
    """Valide format AA-123-AA"""
    pattern = r'^[A-Z]{2}-\d{3}-[A-Z]{2}
    return bool(re.match(pattern, plate.upper()))


[OK] PATTERNS PAR PAYS/LANGUE


# France
PATTERNS_FR = {
    'phone': r'0[1-9](?:\s?\d{2}){4}',  # 01 23 45 67 89
    'postal_code': r'\d{5}',
    'siret': r'\d{14}',
    'siren': r'\d{9}',
}

# USA
PATTERNS_US = {
    'phone': r'\d{3}-\d{3}-\d{4}',
    'ssn': r'\d{3}-\d{2}-\d{4}',
    'zip': r'\d{5}(?:-\d{4})?',
}

# UK
PATTERNS_UK = {
    'phone': r'0\d{10}',
    'postal_code': r'[A-Z]{1,2}\d{1,2}\s?\d[A-Z]{2}',
    'nhs': r'\d{3}\s?\d{3}\s?\d{4}',
}


[OK] REGEX POUR SQL INJECTION (DÉTECTION)


# Patterns suspects (à bloquer)
SQL_INJECTION_PATTERNS = [
    r"('|(\\'))+(\s)*(or|and)(\s)*(\\')?(=|<|>|')+",
    r"(\d)+((\s)*(or|and)(\s)*(\d)+)+((\s)*(=|>|<)+(\s)*(\d)+)",
    r"(\w)*(\s)*(or|and)(\s)*(\w)*\s*(=|>|<)(\s)*(\w)*",
    r"union(\s)+select",
    r"select.*from",
    r"insert(\s)+into",
    r"delete(\s)+from",
    r"drop(\s)+table",
    r"update.*set",
]

def is_sql_injection_attempt(input_str: str) -> bool:
    """Détecte tentative d'injection SQL"""
    input_lower = input_str.lower()
    return any(re.search(pattern, input_lower, re.IGNORECASE) 
               for pattern in SQL_INJECTION_PATTERNS)


[OK] REGEX POUR XSS (DÉTECTION)


# Patterns suspects XSS
XSS_PATTERNS = [
    r'<script[^>]*>.*?</script>',
    r'javascript:',
    r'on\w+\s*=',  # onclick, onload, etc.
    r'<iframe[^>]*>',
    r'<object[^>]*>',
    r'<embed[^>]*>',
]

def is_xss_attempt(input_str: str) -> bool:
    """Détecte tentative XSS"""
    return any(re.search(pattern, input_str, re.IGNORECASE) 
               for pattern in XSS_PATTERNS)


[OK] HELPERS & UTILITIES


# Wrapper pour recherches multiples
def multi_search(patterns: List[str], text: str) -> dict:
    """Recherche plusieurs patterns"""
    results = {}
    for pattern in patterns:
        results[pattern] = re.findall(pattern, text)
    return results

# Recherche insensible aux accents
import unicodedata

def remove_accents(text: str) -> str:
    """Supprime les accents"""
    nfd = unicodedata.normalize('NFD', text)
    return ''.join(c for c in nfd if unicodedata.category(c) != 'Mn')

def search_ignore_accents(pattern: str, text: str):
    """Recherche sans tenir compte des accents"""
    clean_text = remove_accents(text)
    clean_pattern = remove_accents(pattern)
    return re.search(clean_pattern, clean_text, re.IGNORECASE)

# Highlighter de matches
def highlight_matches(pattern: str, text: str, wrapper: str = '**') -> str:
    """Entoure les matches"""
    return re.sub(pattern, rf'{wrapper}\g<0>{wrapper}', text)

# Statistiques sur matches
def match_stats(pattern: str, text: str) -> dict:
    """Statistiques des matches"""
    matches = list(re.finditer(pattern, text))
    return {
        'count': len(matches),
        'positions': [m.span() for m in matches],
        'values': [m.group() for m in matches],
        'unique': len(set(m.group() for m in matches))
    }


[OK] BONNES PRATIQUES


# [OK] Utiliser raw strings (r'...') pour patterns
# [OK] Compiler patterns réutilisés avec re.compile()
# [OK] Utiliser groupes nommés pour clarté
# [OK] Préférer méthodes spécifiques (fullmatch vs match)
# [OK] Tester patterns sur regex101.com
# [OK] Commenter patterns complexes (re.VERBOSE)
# [OK] Utiliser assertions (lookahead/lookbehind) quand approprié
# [OK] Valider/sanitize input utilisateur
# [OK] Gérer les exceptions re.error
# [OK] Utiliser str.startswith/endswith quand suffisant

# [X] Ne pas parser HTML/XML avec regex (utiliser BeautifulSoup)
# [X] Éviter patterns trop gourmands (catastrophic backtracking)
# [X] Ne pas réinventer la roue (utiliser librairies validation)
# [X] Ne pas faire confiance aveuglément aux données utilisateur
# [X] Éviter captures inutiles (utiliser (?:...))


[OK] ERREURS COURANTES & SOLUTIONS


# [X] Oublier raw string
pattern = '\d+'  # Interprété comme chaîne échappée
[OK] Utiliser raw string
pattern = r'\d+'

# [X] Greedy quand on veut lazy
pattern = r'<.*>'  # Matche trop
[OK] Utiliser lazy
pattern = r'<.*?>'

# [X] Pas d'ancrage
pattern = r'\d{3}'  # Matche "123" dans "abc123def"
[OK] Ancrer si nécessaire
pattern = r'^\d{3}

# [X] Oublier d'échapper caractères spéciaux
pattern = r'price: $10'  # $ = fin de ligne!
[OK] Échapper
pattern = r'price: \$10'

# [X] Utiliser regex pour parsing complexe
[OK] Utiliser parsers appropriés (HTML: BeautifulSoup, JSON: json)


[OK] CHEATSHEET RAPIDE


"""
MÉTACARACTÈRES:
    .       Tout caractère        ^       Début            $       Fin
    *       0+                    +       1+               ?       0 ou 1
    {n}     n fois                {n,m}   n à m fois       |       OU
    []      Classe                ()      Groupe           \       Échapper

CLASSES:
    \d      Chiffre               \D      Non-chiffre
    \w      Mot                   \W      Non-mot
    \s      Espace                \S      Non-espace
    \b      Frontière             \B      Non-frontière

GROUPES:
    (...)       Groupe capturant
    (?:...)     Groupe non-capturant
    (?P<name>...)   Groupe nommé
    \1, \2      Backreference

ASSERTIONS:
    (?=...)     Positive lookahead
    (?!...)     Negative lookahead
    (?<=...)    Positive lookbehind
    (?<!...)    Negative lookbehind

FLAGS:
    re.I        IGNORECASE
    re.M        MULTILINE
    re.S        DOTALL
    re.X        VERBOSE

FONCTIONS:
    re.search()     Première occurrence
    re.match()      Début de chaîne
    re.fullmatch()  Chaîne complète
    re.findall()    Toutes occurrences
    re.finditer()   Itérateur
    re.sub()        Remplacement
    re.split()      Découpage
"""


[OK] RESSOURCES


# Documentation officielle Python:
# https://docs.python.org/3/library/re.html

# Testeurs en ligne:
# - regex101.com (meilleur, avec explication)
# - regexr.com
# - pythex.org (spécifique Python)

# Modules alternatifs:
# - regex: pip install regex (plus puissant)
# - parse: pip install parse (parsing simple)

# Guides et tutoriels:
# - https://www.regular-expressions.info/
# - https://regexone.com/ (interactif)

# Librairies de validation:
# - validators: pip install validators
# - phonenumbers: pip install phonenumbers
# - email-validator: pip install email-validator