Experimentos con XPATH y PHP5 DOM

Hola a todos; estoy en medio de un experimento (como los llama walter, medio criptico) de construcción de un XPATH parser para el DOM de tomates (basado en DOM de php5); en este caso, para resumir mucho más las largas estructuras DOM:XPATH (como lo hace jquery).

Por lo general yo nunca posteo preguntas (mas bien respuestas) en mi blog; pero estoy algo atrasado con esta idea y queria ver si alguien al leer este artículo se anima a participar.

Para los que aun no conocen XPATH, este es un lenguaje de localización y recepción de nodos dentro de un arbol DOM XML (o sea un archivo XML cualquiera); estas recepciones se crean mediante expresiones, tanto sencillas como complejas, para ubicar nodos dentro de un documento.
el ejemplo más sencillo es un documento XHTML, de la forma:

<html>
  <head>
    <title>Titulo de la pagina web</title>
  </head>
  <body><div id="capa1" style="font-family: verdana; color: #000cab; ">Prueba de DIV<span class="estilo1" style="font-family: arial; ">span anidado dentro de un div de id capa1</span><div>
</html>

Llegarle a BODY significa un path (eje o axis) es: /html/body … es util porque en muchos casos getElementById o getElementsByTagName no funcionan ya que requieren un DTD (Doctype Document Definition) y a veces el XML es propio (y no un XHTML).
sin embargo, llegarle al SPAN que está dentro del DIV es más dificil; para ello deberiamos llegarle al DIV y luego de todos sus hijos, seleccionar el SPAN que cumpla las reglas; algo como:

body -> div:id=capa1 -> span:class=estilo1

eso, en XPATH se escribe de la siguiente manera:

id(“capa1″)/child::span[@class=”estilo1”]

/child representa todos los hijos de id(); (::) representa el scope (alcance) actual (en este caso, todos los childs), span representa evidentemente el nombre de la etiqueta SPAN y dentro de los corchetes se coloca [] lo que se conoce como predicado, o expresión filtro; en este caso @ representa los atributos que la etiqueta span pudiera tener; @class se refiere al atributo class que es igual a estilo1.
Bueno, al caso; voy a mostrar algunos ejemplos de para que puede servir XPATH en experimentos realmente particulares con XHTML; fijense:

Ejemplos:
id(“capa1”)/parent::node()
//trae el padre del id capa1

id(“capa1″)/child::span[@class=”estilo1”]
//trae de id:capa1 todos los span que tengan como className:estilo1

/html/body/ul[1]/li[position()=last()]
//del primer UL, trae el li que tiene la ultima posicion

Tambien se puede escribir como //ul[1]/li[last()]

id(“tb1”)/child::tbody/child::tr[position() mod 2 = 1]
//del objeto id:tb1, revisa todos los childs tbody::tr que su posicion sea impar.

//ul/li[position()> 2 and position()< 8]
//trae todos los li, que su posicion ordinal sea mayor que dos y menor que 8

id(“lista_estados”)/li[starts-with(., “M”)]
//para la lista de id:lista_estados, traer todos los li que empiezan con M

//*[.=”item 1″]
//para todos los elementos que tienen como texto la palabra “item 1”

id(“tb1”)/child::tbody/child::tr/child::td[@id[starts-with(., “fecha_nacimiento”)]]
//de la tabla id:tb1; traer todas las celdas que su id comience con fecha_nacimiento.

id(“tb1”)/child::tbody/child::tr/child::td[@id[starts-with(., “fecha_nacimiento”)] and substring(., 1, 4)=”1970″]
//El mismo de arriba, pero ademas, que el valor de esa celda, pasado por la funcion substring, sea igual a 1970

Lo que ven empezando es una expresion XPATH, lo segundo (luego del //) es la explicacion de lo que hace la expresion XPATH; alguna vez se han preguntado, sobre todo trabajando con DOM; como hago para contar (sin tener que pegarme a la DB) desde una tabla cuantas personas femeninas nacieron posterior a 1970 y su nombre empieza con A?.
XPATH, en muchos casos es la respuesta.

Bueno, en el caso actual, deseo simplificar este proceso; haciendolo tal cual como lo hace jquery, pero en PHP; algo como:

$(‘ul li:first’)
es igual a:
/html/body/ul/li[position()=1]

ven a lo que me refiero?, ambas dicen: traeme todos los LI que su posicion sea 1, en todos los UL del documento HTML actual.

para hacer esto en PHP requiero dos cosas:

separar la expresion de arriba (el ‘ul li:first’) en “tokens” (ej: ul, li:first) y esos tokens analizarlos segun expresiones regulares (es lo más optimo que veo ahi) y hacer las transformaciones segun el siguiente mapa:

Algunas reglas robadas de jquery:

Reglas:                se traduce a:

#identificadores:
#name <- es id                 id(‘name’)
.class <- posee clase x     [@class=”class”]
div <- es un elemento        /div

#indicadores de posicion:
:first                                [position()=1]
:last                                [position()=last()]
:n (number)                      [position()=n]
:odd <- impares                [position() mod 2 = 1]
:even                               [position() mod 2 = 0]
:>2                                  [position() > 2]

#descendencia y ascendencia:
:child                child::*
:child::span            child::span
:parent                parent::node()
:self                self::node()

Lo que ven de primero, es como se “escribiria” y lo de la derecha, como es la expresion “traducida” en XPATH; entienden a lo que me refiero?, un parser de expresiones a XPATH; el problema por el que atravieso ahora es el poco tiempo disponible para obtener las expresiones regulares que separen en “tokens” y las que interpretan esos tokens; quien se anima a ayudar?.

Si se logra esto (crear el parser XPATH), esta expresion:
id(“tb1”)/child::tbody/child::tr/child::td[@id[starts-with(., “fecha_nacimiento”)] and substring(., 1, 4)=”1970″]
quedaria parecida como en jquery:
(“#tb1 tbody tr td:id=(starts-with(., ‘fecha_nacimiento’) and substring(., 1, 4)=1970)”)

Y para que podría ser util XPATH en PHP?, bueno, fijense este codigo:

$filas = $dom->query(‘id(“tb1”)/child::tbody/child::tr[position() mod 2 = 1]’);
foreach($filas as $fila) {
$fila->className(‘odd’);
}

Dice, “toma todas las filas impares del tbody de la tabla llamada tb1”; de esas filas (que es un DOMNodelist) itero sobre ellas y les aplico un estilo “odd” lo que hace que nuestra tabla sea una “zebra”; claro, esto ya lo hace jquery; pero es un ejemplo interesante de iteración sobre tablas estáticas ya existentes.

Alguno se ha preguntado como hacer un Total de la columna sub-total de una tabla? …
$subtotal = 0;
$celdas = $dom->query(‘id(“tb1″)/child::tbody/child::tr/child::td[@class=”subtotal”]’);
foreach($celdas as $celda) {
$subtotal+= $celda->nodeValue;
}

en la variable $subtotal tendremos el total general de todas las columnas que tengan como class “subtotal” (existen otras formas de identificarlas, pero es para plantear la idea).

y esperen a que llegue XPATH 2.0; podremos hacer cosas como:

sum(for $x in id(‘tb1’)/child::tbody/child::tr/child::td return $x/@cantidad * $x/@precio)

La expresion id(‘tb1’)/child::tbody/child::tr/child::td return $x/@cantidad * $x/@precio) retorna el subtotal de multiplicar las TD:class=cantidad * TD:class=precio; si ese subtotal, lo iteramos entre TODAS las filas (eso hace el for) y sumamos eso (eso hace el sum) obtendremos el total general.

Mientras, seguiré haciendo busquedas con XPATH normalito y básico; pero es un proyecto interesante que tengo por ahi …

El que quiera colaborar, un reply a este post…

Acerca de phenobarbital

http://about.me/phenobarbital

Publicado el 21 noviembre 2007 en Cultura Libre, PHP, PlanetaLinux, Programacion. Añade a favoritos el enlace permanente. Deja un comentario.

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s

A %d blogueros les gusta esto: