Iterando múltiples nodos (padre, hijo) usando Python ElementTree

La implementación estándar de ElementTree for Python (2.6) no proporciona indicadores a los padres desde nodos secundarios. Por lo tanto, si se necesitan padres, se sugiere que los padres se repartan en lugar de los niños.

Considere mi xml es de la forma:

 first second
third

Lo siguiente encuentra todos los nodos “Para” sin considerar a los padres:

 (1) paras = [p for p in page.getiterator("Para")] 

Esto (adaptado de effbot) almacena el padre haciendo un bucle sobre ellos en lugar de los nodos hijos:

 (2) paras = [(c,p) for p in page.getiterator() for c in p] 

Esto tiene mucho sentido, y puede extenderse con un condicional para lograr el (supuestamente) mismo resultado que (1), pero con la información del padre agregada:

 (3) paras = [(c,p) for p in page.getiterator() for c in p if c.tag == "Para"] 

La documentación de ElementTree sugiere que el método getiterator () realiza una búsqueda en profundidad. Al ejecutarlo sin buscar el padre (1) se obtiene:

 first second third 

Sin embargo, al extraer el texto de los párrafos en (3), se obtiene:

 first, Content>Para third, Content>Para second, Table>Para 

Esto parece ser lo primero en amplitud.

Por lo tanto, esto plantea dos preguntas.

  1. ¿Es este el comportamiento correcto y esperado?
  2. ¿Cómo se extraen las tuplas (padre, hijo) cuando el hijo debe ser de un tipo determinado pero el padre puede ser cualquier cosa, si se debe mantener el orden de los documentos ? No creo que ejecutar dos bucles y mapear los (padres, hijos) generados por (3) a las órdenes generadas por (1) sea lo ideal.

Considera esto:

 >>> xml = """ ... first ... second
... third ...
""" >>> import xml.etree.cElementTree as et >>> page = et.fromstring(xml) >>> for p in page.getiterator(): ... print "ppp", p.tag, repr(p.text) ... for c in p: ... print "ccc", c.tag, repr(c.text), p.tag ... ppp Content '\n ' ccc Para 'first' Content ccc Table None Content ccc Para 'third' Content ppp Para 'first' ppp Table None ccc Para 'second' Table ppp Para 'second' ppp Para 'third' >>>

Aparte: las listas de comprensión son magníficas hasta que desee ver exactamente qué se está repitiendo 🙂

getiterator está produciendo los elementos “ppp” en el orden anunciado. Sin embargo, está extrayendo sus elementos de interés de los elementos subsidiarios “ccc”, que no están en el orden deseado.

Una solución es hacer tu propia iteración:

 >>> def process(elem, parent): ... print elem.tag, repr(elem.text), parent.tag if parent is not None else None ... for child in elem: ... process(child, elem) ... >>> process(page, None) Content '\n ' None Para 'first' Content Table None Content Para 'second' Table Para 'third' Content >>> 

Ahora puede capturar los elementos de “Para” cada uno con una referencia a su padre (si existe) a medida que pasan.

Esto se puede envolver muy bien en un gadget generador:

 >>> def iterate_with_parent(elem): ... stack = [] ... while 1: ... for child in reversed(elem): ... stack.append((child, elem)) ... if not stack: return ... elem, parent = stack.pop() ... yield elem, parent ... >>> >>> showtag = lambda e: e.tag if e is not None else None >>> showtext = lambda e: repr((e.text or '').rstrip()) >>> for e, p in iterate_with_parent(page): ... print e.tag, showtext(e), showtag(p) ... Para 'first' Content Table '' Content Para 'second' Table Para 'third' Content >>>