Walker y utilidades
El paquete markast.ast expone primitivas de recorrido para que inspecciones o reescribas el árbol sin escribir recursión contra cada forma de contenedor.
walk(node) — generador
from markast import parse, walk
doc = parse("# Hola\n\nUna palabra **negrita**.")
for node in walk(doc.root):
print(node["type"])
# document
# heading
# text
# paragraph
# text
# bold
# text
El walker visita los nodos en orden de documento (depth-first, pre-order). Sigue cada clave contenedora (children, slots, head, body, rows, cells), así que widgets y tablas no se saltan.
walk(node, include_root=False) salta el nodo inicial y solo emite descendientes.
find(node, type_) y find_all(node, type_)
from markast import find, find_all, parse
doc = parse("# A\n\n## B\n\n## C")
print(find(doc.root, "heading")["children"][0]["value"]) # A
print([h["children"][0]["value"] for h in find_all(doc.root, "heading")])
# ["A", "B", "C"]
Ambas aceptan un tipo string o una lista:
find_all(doc.root, ["link", "inline_image"])
Visitor — despacho por clase
from markast import Visitor, parse
class HeadingCollector(Visitor):
def __init__(self):
self.headings = []
def visit_heading(self, node):
self.headings.append(node)
v = HeadingCollector()
v.run(parse("# A\n\n## B"))
print(len(v.headings)) # 2
Sobrescribe visit_<node_type> para cualquier tipo de nodo al que quieras reaccionar. Los métodos pueden devolver un valor, que run() colecta en una lista.
replace(node, fn) — reescritura funcional
replace recorre el árbol y aplica fn a cada nodo, sustituyendo cada uno por lo que fn retorne. Devolver None elimina el nodo:
from markast import NodeType, parse, replace
doc = parse("# Título\n\n---\n\nCuerpo.")
trimmed = replace(doc.root, lambda n: None if n.get("type") == NodeType.DIVIDER else n)
Devolver un nodo nuevo está bien — el walker continúa por los hijos del reemplazo, no del original. Esto hace que reescrituras de una pasada sean triviales:
def lowercase_text(node):
if node.get("type") == "text":
return {"type": "text", "value": node["value"].lower()}
return node
lowered = replace(doc.root, lowercase_text)
Pasa in_place=True para mutar el árbol existente si necesitas preservar identidad de objetos. Por defecto devuelve un árbol nuevo.
extract_text(node)
Atajo para "dame la proyección de texto plano de este subárbol":
from markast import extract_text, parse
doc = parse("# Hola **mundo**")
print(extract_text(doc.children[0]))
# "Hola mundo"
Recorre children, slots, y rows/cells de tablas, así que widgets y tablas también aportan texto.
count_nodes(node)
from markast import count_nodes, parse
doc = parse("# A\n\n## B\n\n- x\n- y")
print(count_nodes(doc.root))
# {'document': 1, 'heading': 2, 'text': 4, 'list': 1, 'list_item': 2, ...}
Cheat-sheet
| Objetivo | Usa |
|---|---|
| Iterar cada nodo, hacer algo | walk |
| Encontrar el primero / todos de un tipo | find / find_all |
| Extraer datos en una lista | Visitor |
| Mutar una copia del árbol | replace |
| Mutar en sitio | replace(..., in_place=True) |
| Obtener texto plano | extract_text |
| Contar tipos de nodos | count_nodes |
Modelo mental: el AST es solo dicts. El walker es la única parte no trivial; todo lo demás es manipulación de diccionarios.