XPATH

Cuando se escribe un programa para procesar un documento XML, generalmente hay que seleccionar ciertas partes del documento, para procesarlas de un cierto modo. Por ejemplo, podemos querer seleccionar el contenido de ciertos elementos que se ajustan a una cierta condición, como una fecha o un número. Pongamos un ejemplo: tenemos un documento XML que almacena información sobre recursos humanos de una empresa y queremos presentar los nombres de los empleados en una lista. En tal caso, debemos poder referirnos al contenido de los elementos de forma que podamos recuperar nombres y apellidos. El lenguaje XPath está diseñado para permitir al desarrollador seleccionar partes específicas de un documentos XML.

XPath fue diseñado para trabajar con XSLT. Mediante XSLT podemos transformar un documento XML en otro, tomando partes del documento XML original mediante XPath, y componiendo el nuevo documento como si se tratase de un puzzle. De este modo, por ejemplo, podemos extraer partes del documento XML y componer una página XHTML con los datos extraidos.

MODELANDO DOCUEMENTOS XML

En un documento XML, escribimos etiquetas de apertura y de cierre, siguiendo una estructura anidada. El modelo de datos XPath representa la mayoría de las partes de un docuemnto XML como un árbol de nodos. La mayoría de las partes del documento XML están representadas como nodos en el modelo XPath, de modo que el nodo raíz representa la raíz del documento.

Una forma de imaginar XPath es como un árbol de direcciones de calles. Cuando damos direcciones de calles, tenemos cuatro direcciones principales: norte, sur, este y oeste. En XPath, hay 13 direcciones, llamadas ejes. Al igual que para dar ayudar a alguien a llegar a un sitio diríamos cosas como "Desde la rotonda, toma la primera a la derecha y sigue recto hasta la fachada verde", en XPath, podríamos escribir algo como:

/Libro/Capítulo[@número=2]

Estaríamos diciendo: desde el nodo raíz, toma el eje hijo y busca nodos elemento llamados "Libro"; entonces, para cada uno de los nodos encontrados, buscar elementos nodo llamados "Capítulo", usando también el eje hijo; Entonces, elegir solo los elementos "Capítulo" que tengan un atributo "número" cuyo valor sea 2.

Los ejes llamados "hijo" son los más comunes, así que podemos decir eje sin más. La parte entre corchetes, es llamada "predicado", que actúa como filtro de los nodos seleccionados en la primera parte de la expresión.

EJEMPLO DE ARBOL XPATH

<?xml version="1.0"?> <?Ejemplo. No procesar.?> <!DOCTYPE Gente SYSTEM "Gente.dtd"> <Gente xmlns="http://www.gente.org/gente1234"> <!-- Lista de gente --> <Persona idPersona="123456"> Quién: <Nombre>Ana Pérez</Nombre> Qué: <Posición>Sales Manager</Posición> </Persona> <Persona idPersona="987654"> Quién: <Nombre>Juan Gris</Nombre> Qué: <Posición>Programador XML</Posición> </Persona> </Gente>

Haz clic en la imagen para verla más grande.

NODOS

Un nodo es una representación en el modelo de datos XPath de una parte lógica de un documento XML. En XPath 1.0 hay 7 tipos de nodos:

  • Nodo raíz
  • Nodo elemento
  • Nodo atributo
  • Nodo texto
  • Nodo espacio de nombres
  • Nodo comentario
  • Nodo instrucción de procesamiento

Es posible extraer mediante XPATH los diferentes nodos de un elemento.

Por ejemplo, vamos a probar a evaluar las siguientes rutas XPath en XML Copy Editor, sobre el siguiente documento XML. Representa gráficamente el árbol XPath y los nodos que selecciona cada ruta.

<?xml version="1.0" encoding="UTF-8"?> <Libros almacén="Guadalpín"> <Libro id="1234"> <Nombre>MySQL</Nombre> <Editorial año="2005">Sybex</Editorial> <Capítulos> <Capítulo número="1">Guía rápida de MySQL</Capítulo> <Capítulo número="2">Tipos de datos y tipos de tabla</Capítulo> <Capítulo número="3">SQL Avanzado</Capítulo> <Capítulo número="4">Programación con MySQL</Capítulo> <Capítulo número="5">Índices y optimización de consultas</Capítulo> </Capítulos> </Libro> <Libro id="4321"> <Nombre>Windows Server 2003</Nombre> <Editorial año="2004">Anaya</Editorial> <Capítulos> <Capítulo número="1">Fundamentos previos</Capítulo> <Capítulo número="2">Características de Windows Server 2003</Capítulo> <Capítulo número="3">Preinstalación</Capítulo> <Capítulo número="4">Instalación</Capítulo> <Capítulo número="5">Asistente de configuración</Capítulo> <Capítulo número="6">Administración</Capítulo> </Capítulos> </Libro> </Libros>

/ /Libros /Libros/Libro /Libros/Libro/Nombre /Libros/Libro[1]/Editorial/@año /Libros/Libro/Capítulos /Libros/Libro/Capítulos/Capítulo /Libros/Libro/Capítulos/Capítulo/@número /Libros/Libro[2]/Capítulos/Capítulo[@número="1"]/text()

LOS EJES DE XPATH

XPath tiene un total de 13 ejes, que son usados para navegar a través del árbol de nodos del modelo de datos XPath. XSLT soporta todos estos ejes. Los ejes son "child","attribute", "ancestor", "ancestor-or-self", "descendant", "descendant-or-self", "following", "following-sibling", "namespace", "parent", "preceding", "preceding-sibling" y "self".

Los siguientes gráficos muestra los nodos pertenecientes a cada eje:

NOTA: Los nombres de los ejes están en minúscula, y así se deben escribir siempre. XML es casesensitive.

Vamos uno por uno.

EJE CHILD

El eje por defecto en XPath es "child". Selecciona los nodos que son hijos inmediatos del nodo contexto. Por ejemplo:

<Factura> <Fecha> 02-01-2011 </Fecha> <Item cantidad="4">AS11</Item> <Item cantidad="5">AS13</Item> </Factura>

Si el nodo de contexto es el nodo elemento "Factura", la ruta "child::Item" (o en su modo abreviado "Item") devolverá un "node-set" (conjunto de nodos) con los nodos "Item", hijos del elemento "Factura".

Para seleccionar tanto elementos "Fecha" como "Item", usaremos la ruta "child::*" (o en su modo abreviado "*").

Para seleccionar todos los nodos hijo, incluyendo nodos comentario y nodos texto, podemos escribir la siguiente ruta: "child::node()" (o en su modo abreviado "node()").

Si lo que queremos es seleccionar específicamente los nodos texto hijos del nodo de contexto, podemos escribir "child::text()" (o en su modo abreviado "text()").

Como "child" es el eje por defecto, las siguientes rutas significan lo mismo:

/child::Libros/child::Libro/child::Capítulos /Libros/Libro/Capítulos

NOTA: Prueba a evaluar la siguiente ruta en el documento anterior sobre Libros: /Libros/Libro/Capítulos/Capítulo/text(). Con esta ruta estás recorriendo todos los nodos Capítulo del árbol y recuperando su nodo texto.

EJE ATRIBUTO

Suponiendo que el nodo de contexto es un nodo elemento, la rutas siguientes significan lo mismo:

attribute::* @*

Ambas rutas devolverán todos los nodos atributo asociados con el nodo elemento. De modo que si existe más de un atributo, se mostrarán ambos.

NOTA: Si el nodo de contexto no es un nodo elemento, el eje atributo devolverá un node-set vacío. Por ejemplo prueva a evaluar la siguiente ruta en el documento anterior sobre Libros: /Libros/Libro/Editorial/@*

Actividad 1.Utilizando el documento sobre los libros anteriormente presentados:

  • Crea una ruta y evalúala para obtener el valor de los atributos año de los elementos Editorial.
  • Crea una ruta y evalúala para obtener el valor de los atributos número de los elementos Capítulo.

Entregar en un documento de texto con el nombre Act1-xpath.txt

EJE ANCESTOR

El eje ancestor selecciona el nodo padre del nodo de contexto, el nodo "abuelo", el "bisabuelo" y así sucesivamente. Si el nodo de contexto es el nodo raíz, el eje ancestor devuelve un node-set vacío.

Por ejemplo:

<Libro> <Capítulo número="1"> <Sección>Primera sección. </Sección> <Sección>Segunda sección. </Sección> </Capítulo> </Libro>

Entonces la ruta "/Libro/Capítulo/Sección/ancestor::*" devolvería el nodo elemento "Capítulo", que tiene un atributo "número" que vale 1. También devolverá el nodo elemento "Libro". Y por último devolverá el nodo raíz.

NOTA: La sintaxis reducida para el eje "ancestor" es "..".

Prueba a evaluar la ruta "/Libros/Libro/Capítulos/ancestor::*" y la ruta "/Libros/Libro/Capítulos/ancestor::Libro" sobre el documento sobre libros.

Actividad 2. Trata de explicar el resultado de evaluar la siguiente ruta XPath, y después evalúala para comprobar si has acertado:

/Libros/Libro/Nombre[text()="MySQL"]/../Capítulos/Capítulo[position()=2]

Reescribe la ruta anterior sin utilizar sintaxis reducida del nodo parent.

Entrega el archivo Act2-xpath.txt

EJE DESCENDANT

Dado el nodo de contexto, el eje descendant selecciona su nodo hijo, su nodo "nieto", su "bisnieto" y así sucesivamente.

En el ejemplo anterior, si el nodo de contexto es "Libro", la ruta "descendant::*" seleccionaría tanto el nodo elemento "Capítulo", así como los dos nodos "Sección".

Si escribimos la ruta "/descendant::Sección", estaríamos describiendo todos los nodos elemento "Sección" que contiene el nodo de contexto.

NOTA: Aquí no hay sintaxis abreviada.

¿Qué nodos crees que devolvería la ruta "/Libros/Libro/descendant::Capítulos"? Prueba a evaluarla y comprueba si estabas en lo cierto. ¿Y la ruta "/Libros/Libro/descendant::*"

EJE DESCENTANT-OR-SELF

Incluye todos los nodos en el eje "descendant" y también el nodo de contexto. La forma abreviada es "//".

Este eje nos permite encontrar nodos, independientemente de su posición.Es decir, si queremos todos los elementos "Capítulo" pero no estamos seguros de la jerarquía XML (o quizá el elemento "Capítulo" puede estar anidado), la ruta "//Capítulo" nos los devolverá todos. Esta ruta requiere un trabajo intensivo de búsqueda, lo que tiene un coste computacional.

¿Qué crees que devolvería la ruta "/Libros/Libro/Capítulos/descendant-or-self::*"? Prueba a evaluarla y comprueba si estabas en los cierto.

Actividad 3. Definir la ruta necesaria para recuperar el nodo "Capitulos" del segundo libro así como todos sus descendientes.

Entregar un ejercicio llamado Act3-xpath.txt

EJES FOLLOWING

El eje "following" contiene todos los nodos que vienen tras el nodo de contexto según el orden del documento, pero excluye todos los nodos descendientes así como cualquier nodo atributo y nodo namespace asociado con el nodo de contexto.

Un ejemplo:

<Empleados> <Persona> <Nombre>Pedro</Nombre> <Apellido1>Pérez</Apellido1> <FechaDeNacimiento>12-12-1970</FechaDeNacimiento> </Persona> <Persona> <Nombre>José</Nombre> <Apellido1>García</Apellido1> <FechaDeNacimiento>11-10-1984</FechaDeNacimiento> </Persona> <Persona> <Nombre>Ángel</Nombre> <Apellido1>Merino</Apellido1> <FechaDeNacimiento>01-02-1980</FechaDeNacimiento> </Persona> </Empleados>

¿Cuáles son los nodos del eje following siguiente: /Empleados/Persona[1]/Nombre/following::*

Piensa en el ejemplo de los libros ¿Qué crees que devolverá la ruta "/Libros/Libro/Capítulos/following::*"? Prueba a evaluarla y comprobar si estabas en lo cierto.

EJE FOLLOWING-SIBLING

El eje "following-sibling" incluye cualquier nodo en el eje "following" que comparte su nodo padre con el nodo de contexto. Vamos a ver un ejemplo aclaratorio. Partiendo del documento XML anterior, si en este caso elegimos el nodo "Nombre" como nodo de contexto, entonces el eje "following-sibling" incluirá a los nodos siguientes que comparten al mismo nodo elemento padre, es decir, "Apellido1" y "FechaDeNacimiento". Prueba a pasar esta hoja de estilos XLST al archivo XML anterior:

¿Qué crees que devolverá la ruta "/Libros/Libro/Capítulos/Capítulo/following-sibling::*? Prueba a evaluarla y comprobar si estabas en lo cierto.

EJES PARENT

Los ejes "parent" se usan para seleccionar los nodos padre del nodo contacto. Por ejemplo:

<Partes> <Parte número="ABC123"/> <Parte número="DEF456"/> </Partes>

Si el nodo de contexto está en uno de los nodos elemento "Parte", entonces la ruta siguiente selecciona su nodo padre, es decir, el nodo elemento "Partes":

parent::node()

En cambio, si el nodo de contexto fuese el nodo elemento "Partes", la ruta anterior seleccionaría el nodo raíz.

NOTA: Una forma de comprobar si estamos en el nodo raíz es comprobar si el nodo padre es "null". El nodo raíz es el único sin un padre.

La sintaxis abreviada para el nodo padre es "..". Se parece a la forma de indicar el directorio padre en un sistema de archivos.

¿Qué crees que devolverá la ruta "/Libros/Libro/Capítulos/.."? Prueba a evaluarla para ver si estabas en lo cierto.

EJE PRECEDING

El eje "preceding" contiene todos los nodos anteriores al nodo de contexto, según el orden del documento, excluyendo los nodos del eje "ancestor" y los nodos atributo y namespace.

Si tomamos el ejemplo anterior de los empleados, y le pasamos la siguiente hoja de estilos:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" > <xsl:template match="/"> <html> <head> <title>Demostración del eje preceding.</title> </head> <body> <h3>Demostración del eje preceding.</h3> <xsl:apply-templates select="/Empleados/Persona[3]/FechaDeNacimiento" /> </body> </html> </xsl:template> <xsl:template match="FechaDeNacimiento"> <xsl:for-each select="preceding::*"> <p><xsl:value-of select="name(.)" /> --< Contiene el texto "<xsl:value-of select="." />".</p> </xsl:for-each> </xsl:template> </xsl:stylesheet>

Si probamos el ejemplo, veremos que se muestran todos los elementos anteriores al elemento "FechaDeNacimiento" del último elemento "Persona".

¿Qué crees que devolverá la ruta "/Libros/Libro/Nombre/preceding::*"? Prueba a evaluarla para ver si estabas en lo cierto. ¿Y "/Libros/Libro[2]/Capítulos/Capítulo[3]/following-sibling::*"?

EJE PRECEDING-SIBLING

Este eje incluye todos los nodos que están dentro del eje "preceding" y que comparten padre con el nodo de contexto. O sea, que si en el ejemplo anterior, probamos a cambiar "prededing::*" por "preceding-sibling::*", obtendríamos solo los elementos "nombre" y "apellido1" del tercer elemento "persona".

¡Ala! A probarlo.

EJE SELF

El eje "self" selecciona el nodo de contexto. La forma no abreviada del eje self es "self::node()". Su forma abreviada es ".". Es decir, que las dos rutas siguientes serían equivalentes:

<xsl:value-of select="." /> <xsl:value-of select="self::node()" />

Predicados

Un predicado permite restringir el conjunto de nodos seleccionados por un hacha a aquellos que cumplen cierta condición. Dicha condición es una expresión XPath y se expecifíca entre corchetes. Vamos a partir del siguiente documento XML:

<libro> <titulo>Dos por tres calles</titulo> <autor>Josefa Santos</autor> <capitulo num="1"> La primera calle <parrafo> Era una sombría noche del mes de agosto... </parrafo> <parrafo destacar="si"> Ella, inocente cual <enlace href="http://www.enlace.es">mariposa</enlace> que surca el cielo en busca de libaciones... </parrafo> </capitulo> <capitulo num="2" public="si"> La segunda calle <parrafo>Era una obscura noche del mes de septiembre...</parrafo> <parrafo> Ella, inocente cual <enlace href="http://www.abejilla.es">abejilla</enlace> que surca el viento en busca del néctar de las flores... </parrafo> </capitulo> <apendice num="a" public="si"> La tercera calle <parrafo> Era una densa noche del mes de diciembre... </parrafo> <parrafo> Ella, cándida cual <enlace href="http://www.pajarillo.es">abejilla</enlace> que surca el espacio en busca de bichejos para comer... </parrafo> </apendice> </libro>
Vamos a analizar algunas rutas

  • //*[@num]
  • //capitulo[parrafo/*[@href]]
  • //capitulo[parrafo/*[@href]][@public='si']
  • //capitulo[ (parrafo/*[@href]) and (@public='si')]
  • //capitulo[parrafo/*[@href]]|//apendice
  • //capitulo[not(@public)]
  • //capitulo[position()=2]
  • //capitulo[2]
  • //capitulo[last()]
  • //capitulo[not(position()=last())]
  • //capitulo[last()-1]
  • id( "capitulo_1" )/parrafo

Actividad 4. Averigua la ruta XPath necesaria para obtener la respuesta a la siguiente consulta:

Encontrar los primeros capítulos de todos los libros cuya editorial sea "Sybex"

Entrega la actividad en un archivo llamado Act4-xpath.txt