"""Extrae los embeds ![[...]] (attachments incrustados) de una nota de Obsidian. Funcion pura: solo procesa texto. A diferencia de extract_obsidian_wikilinks, ignora los wikilinks normales [[...]] y devuelve unicamente los embeds ![[...]]. """ import re # Matchea SOLO embeds: el '!' inicial es obligatorio (? list: """Extrae SOLO los embeds ``![[...]]`` del cuerpo de una nota de Obsidian. Un embed `![[archivo.jpg]]` incrusta un attachment (imagen, pdf, otra nota) dentro de la nota. Esta funcion devuelve el target de cada embed, mientras que los wikilinks normales `[[...]]` (que NO empiezan por `!`) se ignoran. Para cada embed se normaliza el target: ![[imagen.jpg]] -> "imagen.jpg" ![[imagen.jpg|300]] -> "imagen.jpg" (se quita el alias |...) ![[nota#Seccion]] -> "nota" (se quita el ancla #...) ![[dni enmanuel (2).jpg]] -> "dni enmanuel (2).jpg" Los targets se deduplican preservando el orden de primera aparicion. Pura y deterministica: no hace I/O ni muta nada. Args: body: Cuerpo Markdown de una nota de Obsidian (idealmente sin frontmatter). Returns: Lista de strings con los nombres de los attachments embebidos, unicos y en orden de aparicion. Lista vacia si no hay embeds. """ if not body: return [] seen = set() targets = [] for match in _EMBED_RE.finditer(body): inner = match.group(1) # Quitar alias tras el primer '|'. target = inner.split("|", 1)[0] # Quitar ancla/heading tras el primer '#'. target = target.split("#", 1)[0] target = target.strip() if not target: continue if target in seen: continue seen.add(target) targets.append(target) return targets if __name__ == "__main__": body = ( "Texto con [[Nota normal]] y un embed ![[imagen.jpg]]. " "Otra cosa [[Otra Nota|alias]] y ![[doc.pdf]] y ![[imagen.jpg]]." ) assert extract_obsidian_embeds(body) == ["imagen.jpg", "doc.pdf"], body assert extract_obsidian_embeds("") == [] assert extract_obsidian_embeds("solo [[wikilink]] aqui") == [] assert extract_obsidian_embeds("![[dni enmanuel (2).jpg]]") == ["dni enmanuel (2).jpg"] print("extract_obsidian_embeds smoke OK")