Todo el procesamiento realizado con un fichero XML está basado en la posibilidad de direccionar o acceder a cada una de las partes que lo componen, de modo que podamos tratar cada uno de los elementos de forma diferenciada.
El tratamiento del fichero XML comienza por la localización del mismo a lo largo del conjunto de documentos existentes en el mundo. Para llevar a cabo esta localización de forma unívoca, se utilizan los URI (Unifom Resource Identifiers), de los cuales los URL (Unifom Resource Locators) son sin duda los más conocidos.
Una vez localizado el documento XML, la forma de seleccionar información dentro de él es mediante el uso de XPath, que es la abreviación de lo que se conoce como XML Path Language. Con XPath podremos seleccionar y hacer referencia a texto, elementos, atributos y cualquier otra información contenida dentro de un fichero XML.
XPath en sí es un lenguaje sofisticado y complejo, pero distinto de los lenguajes procedurales que solemos usar (C, C++, Basic, Java...). Además, como casi todo en el mundo de XML, aún está en estado de desarrollo, por lo que no es fácil encontrar herramientas que incorporen todas sus funcionalidades.
XPath es a su vez la base sobre la que se han especificado nuevas herramientas que aprovehcar para el tratamiento de documentos XML. Herramientas tales como XPointer, XLink y XQL (el lenguaje que maneja los documentos XML como si de una base de datos se tratase), que también están en estado de desarrollo, pero que sin duda cambiarán el modo en que actualmente concebimos la navegación por la Web. Así, XPath sirve para decir cómo debe procesar una hoja de estilo el contenido de una página XML, pero también para poder poner enlaces o cargar en un navegador zonas determinadas de una página XML, en vez de toda la página.
Un documento XML es procesado por un analizador (o parser) construyendo un árbol de nodos. Este árbol comienza con un elemento raíz, que se diversifica a lo largo de los elementos que cuelgan de él y acaba en nodos hoja, que contienen solo texto, comentarios, intrucciones de proceso o incluso que están vacíos y solo tienen atributos
La forma en que XPath selecciona partes del documento XML se basa precisamente en la representación arbórea que se genera del documento. De hecho, los "operadores" de que consta este lenguaje nos recordarán la terminología que se utiliza a la hora de hablar de árboles en informática: raíz, hijo, ancestro, descendiente, etc...
Un caso especial de nodo son los nodos atributo. Un nodo puede tener tantos atributos como desee, y para cada uno se le creará un nodo atributo. No obstante, dichos nodos atributo NO se consideran como hijos suyos, sino más bien como etiquetas añadidas al nodo elemento.
A continuación se muestra un ejemplo de cómo se convierte en árbol un documento XML. Este mismo ejemplo será usado a lo largo de todo el tutorial. En primer lugar se muestra el documento XML y a continuación el árbol que genera.
Página 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>
Árbol generado :
/
|
+---libro
|
+---titulo
| |
| +---(texto)Dos por tres calles
|
+---autor
| |
| +---(texto)Josefa Santos
|
+---capitulo [num=1]
| |
| +---(texto)La primera calle
| |
| +---parrafo
| | |
| | +---(texto)Era una sombría noche ...
| +---parrafo
| |
| +---(texto)Ella, cual inocente mariposa...
|
+---capitulo [num=2]
|
+---(texto)La segunda calle
|
+---parrafo
| |
| +---(texto)Era una obscura noche ...
+---parrafo
|
+---(texto)Ella, cual inocente abeja...
Existen distintos tipos de nodos en un árbol generado a partir de un documento XML, a saber: raíz, elemento, atributo, texto, comentario e instrucción de procesamiento (respectivamente;root, elements, attribute, text, comment y processing instruction).
Se identifica por /. No se debe confundir el nodo raíz con el elemento raíz del documento. Así, si el documento XML de nuestro ejemplo tiene por elemento raíz a libro, éste será el primer nodo que cuelgue del nodo raíz del árbol, el cual es: /.
Insisto: / hace referencia al nodo raíz del árbol, pero no al elemento raíz del documento XML, por más que un documento XML solo pueda tener un elemento raíz. De hecho, podemos afirmar que el nodo raíz del árbol contiene al elemento raíz del documento.
Cualquier elemento de un documento XML se convierte en un nodo elemento dentro del árbol. Cada elemento tiene su nodo padre. El nodo padre de cualquier elemento es, a su vez, un elemento, excepto el elemento raíz, cuyo padre es el nodo raíz. Los nodos elemento tienen a su vez hijos, que son: nodos elemento, nodos texto, nodos comentario y nodos de intrucciones de proceso. Los nodos elemento también tienen propiedades tales como su nombre, sus atributos e información sobre los "espacios de nombre" que tiene activos.
Cualquier elemento de un documento XML se convierte en un nodo elemento dentro del árbol. Cada elemento tiene su nodo padre. El nodo padre de cualquier elemento es, a su vez, un elemento, excepto el elemento raíz, cuyo padre es el nodo raíz. Los nodos elemento tienen a su vez hijos, que son: nodos elemento, nodos texto, nodos comentario y nodos de intrucciones de proceso. Los nodos elemento también tienen propiedades tales como su nombre, sus atributos e información sobre los "espacios de nombre" que tiene activos.
Una propiedad interesante de los nodos elemento es que pueden tener identificadores únicos (para ello deben ir acompañados de un DTD que especifique dicho atributotoma valores únicos), esto permite referenciar a dichos elementos de una forma mucho más directa.
Por texto vamos a hacer refrenecia a todos los caracteres del documento que no está marcados con alguna etiqueta. Un nodo texto no tiene hijos, es decir, los distintos caracteres que lo forman no se consideran hijos suyos.
Como ya hemos indicado, los nodo atributo no son tanto hijos del nodo elemento que los contiene como etiquetas añadidas a dicho nodo elemento. Cada nodo atributo consta de un nombre, un valor (que es siempre una cadena) y un posible "espacio de nombres".
Aquellos atributos que tienen por valor el valor por defecto asignado en el DTD se tratarán como si el valor se le hubiese al escribir el documento XML. Al contrario, no se crea nodo para atributos no especificados en el documento XML, y con la propiedad #IMPLIED definida en su DTD. Tampoco se crean nodos atributo para las deficiones de los espacios de nombre. Todo esto es normal si tenemos en cuenta que no es necesario tener un DTD para procesar un documento XML.
Aparte de los nodos indicados, en el árbol también se generan nodos para cada nodo con comentarios y con intrucciones de proceso. Al contenido de estos nodos se puede acceder con la propiedadstring-value.
Una "instrucción" en lenguaje XPath se denomina una expresión. Y pongo entre comillas lo de "instrucción" porque XPath es un lenguaje declarativo, por lo que las intrucciones no son exactamente como estamos acostumbrados a ver.
Dichas expresiones pueden incluir cierta variedad de operaciones sobre distintos tipos de operanods. En nuestro caso nos vamos a ceñir a dos tipos de oprandos: llamadas a funciones y location paths (algo así como caminos de localización).
Un location path es la más importante de los tipos de expresiones que se pueden especificar en notación XPath. La sintaxis de un location path es simliar a la usada a la hora de describir los directorios que forman una unidad de disco en Unix o Linux (y similar a la de los sistemas basados en MS-DOS y Windows, si exceptuamos la unidad de disco -C:, A:- y que las barras usadas son / en vez de las típicas \ de estos últimos sistemas operativos).
Sin embargo, solo la sintaxis es lo similar al sistema de archivos. El significado de las expresiones es totalmente diferente.
Por ejemplo, el siguiente path en Unix:
/usr/home/pepeillo/docs
hace referencia a un único directorio: docs el cual cuelga de el conjunto de directorios /usr/home/pepeillo.
Sin embargo, la siguiente expresión en XPath:
/libro/capitulo/parrafo
hace referencia a TODOS los elementos parrafo que cuelguen directamente de CUALQUIER elemento capitulo que cuelgue de CUALQUIER elemento libro que, finalmente, cuelguen del nodo raíz, /. Bueno, el último "TODOS" sobra dado que solo puede haber un elemento raíz: libro.
Hay que tener en cuenta que una expresión en XPath no devuelve los elementos que cumplen con el patrón que representa dicha expresión, sino que devuelve una referencia a dichos elementos; es decir, una expresión XPath nos devuelve una lista de apuntadores a los elementos que encajan en el patrón. Dicha lista puede estar vacía o contener uno o más nodos.
Un location path siempre tiene un punto de partida llamado nodo contexto. Para enterdernos es como el directorio actual si nos referimos a un sistema de ficheros. Así, si estando en Unix, damos una orden ls obtendremos los ficheros que existen en el directorio actual, mientras que si decimos ls /usr/bin obtendremos el listado de los ficheros existentes en el directorio /usr/bin con independencia del directorio en que estemos colocados al dar la orden.
En los location path ocurre lo mismo. A menos que se indique un camino explícito, se entenderá que el location path parte del nodo que en cada momento se esté procesando.
El concepto de "nodo contexto" es imprescindible para comprender cómo se lleva a cabo la elección de los nodos que ajustan con el patrón indicado en el location path. para explicar esto, veamos cómo actuaría un motor de evaluación de expresiones XPath al leer la siguiente expresión aplicada al documento XML que manejamos desde el principio del turtorial:
/libro/capitulo/parrafo
(Aviso: lo que viene a continuación requiere de pausada lectura, descansito para ir al frigorífico a por algo de beber y pequeño masaje en los ojos... como mínimo)
En primer lugar comienza por leer /, lo cual le dice que debe seleccionar el nodo raíz, independientemente del nodo contexto que en ese momento exista. En el momento en que el evaluador de XPath localiza el nodo raíz, éste pasa a ser el nodo contexto de dicha expresión.
Los predicados se incluyen dentro de un location path utilizando los corchetes, como poe ejemplo:
/libro/capitulo[@num="1"]/parrafo
Mediante el anterior location path estamos indicando que se escojan todos los elementos parrafo de todos los elementos capitulo que tengan un atributo llamado num al cual se le haya asignado el valor "1" (recordemos que en XML todos los atributos tienen valores de tipo cadena).
Atendiendo a nuestro ejemplo, solo hay un capitulo que cumpla dichas condiciones, por lo que solo los elementos parrado que él contiene serán seleccionados.
Posteriormente, veremos qué tipo de cosas se pueden poner en un predicado.
La verdad es que no sabía muy bien como traducir el término axes que significa algo así como cercenar o podar, aunque también hacha. Lo voy a traducir por hacha que seguro que después de un par de párrafos se convierte en algo normal.
Digamos que un hacha incluída en un location path realiza una selección de nodos dentro del árbol (o mejor dicho, dentro del subárbol que cuelga del nodo o conjunto de nodos contexto) de acuerdo con algún patrón.¡¡Bingo!!, cada vez que hemos usado la barra / (salvo para denominar el nodo raíz) estábamos usando un hacha.
Veamos las distintas hachas que podemos usar para recorrer el arbolito.
Es el hacha utilizada por defecto. Se corresponde con la barra, / (aunque tiene una forma más larga que es: /child::).
Seleccionar todos los titulo de un libro:
/libro/titulo
Se corresponde con el signo de la arroba, @ (o en su forma larga que es: attribute::).
Mediante este operador podemos seleccionar aquellos nodos atributos que deseemos, especialmente indicando el nombre. Nótese, que para seleccionar los nodos elemento que muestran dichos atributos, lo que se ha de usar es un predicado (como se ha indicado ya anteriormente)
Para seleccionar el atributo num que posean los elementos capitulo
/libro/capitulo/@num
Para seleccionar todos los elementos hijo de los capitulo que posean el atributo public
/libro/capitulo[@public]/*
Para seleccionar todos los elementos hijo de parrafo cuyo atributo destacar sea igual a "si".
/libro/titulo/parrafo[@destacar="si"]
Se especifica poniendo una doble barra: // (en su forma larga: descendant::).
Sirve para seleccionar TODOS los nodos que descendiendan del conjunto de nodos contexto. Es decir, no solo los hijos de los nodos contexto, sino también los hijos de los hijos, y los hijos de estos, etc.
Para seleccionar todos los parrafo de un libro:
/libro//parrafo
Para seleccionar todos los descendientes de parrafo que tienen un atributo href.
//parrafo//*[@href]
Para ver el valor del atributo href del caso anterior:.
//parrafo//*[@href]/@href
Para seleccionar todos los elementos descendientes de capitulo
/libro/titulo//*
Se especifica mediante el ..
Es muy útil pues sirve para seleccionar el nodo contexto. Por ejemplo, supongamos que deseamos seleccionar todos los parrafo descendientes del nodo contexto. No podemos escribir //parrafo, dado que seleccionaría todos los descendientes del nodo raíz. Por ello, la forma correcta es: .//parrafo
Al igual que en los sistemas de ficheros, se utilizan los dos puntos para identificarlo: ..
El comportamiento de este hacha es un poco extraño al principio dado que realiza un paso hacia atrás en el árbol de nodos
Seleccionar todos los nodos que tienen algún hijo de tipo parrafo:
//parrafo/..
Seleccionar todos los nodos capitulo que tienen algún hijo de tipo parrafo:
//parrafo/../../capitulo
O bien:
//capitulo/parrafo/..
De todas las hachas que podemos usar, esta es la única que no tiene ninguna forma de abreviación, sino que hay que ponerla como ancestor::
Ancestor es a parent lo que descendant es a child. Es decir, devuelve todos los elementos de los cuales el nodo contexto es descendiente.
Seleccionar todos los elementos que tienen entre sus descendientes algún parrafo
//ancestor::parrafo/ancestor::*
Seleccionar todos los capitulos que tienen entre sus descendientes alguno con el atributo href
//*[@href]/ancestor::capitulo
Los nodos test son algo así como funciones que nos van a ayudar a restringir un poquito lo que nos devuelve una expresión XPath. Ya hemos visto algunos de estos nodos test en los ejemplos anteriores. Pero ahora vamos a verlos con más detenimiento.
En primer lugar hemos de distinguir entre las hachas de contenido (content axis) de las que no lo son. Las hachas de contenio son básicamente todas las vistas excepto attibute y namespace(es como attribute pero para obtener el "espacio de nombres" asociado al elemento).
El nodo test * devuelve todos los nodos de tipo prinicpal (es decir, elemento, atributo o espacio de nombres), pero no nodos de texto, comentarios, y de instrucciones de proceso.
Seleccionar todos los nodos principales descendientes de los parrafo:
//parrafo/*
El nodo test node() devuelve todos los nodos de todos los tipos.
Seleccionar todos los nodos descendientes de los parrafo:
//parrafo/node()
Cualquier nodo de tipo texto.
Seleccionar el texto de todos los nodos parrafo:
//parrafo/text()
Seleccionar TODO el texto que cuelga de todos los nodos parrafo:
//parrafo//text()
Cualquier nodo de tipo comentario.
Cualquier nodo de tipo de instrucción de proceso, independientemente de su destino.
Cualquier nodo de tipo de instrucción de proceso relativo al destino especificado.
Cualquier nodo de tipo de instrucción de proceso con el destino cursor
Ya hemos hablado algo de los predicados en párrafos anteriores. Básicamente, 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.
Seleccionar todos los elementos que tengan el atributo num:
//*[@num]
Seleccionar todos los capitulo que tengan un parrafo que tenga algún elemento con atributo href:
//capitulo[parrafo/*[@href]]
Los predicados se pueden suceder uno a otro haciendo el efecto de la operación AND. Como en el siguiente ejemplo.
Seleccionar todos los capitulo que tengan un parrafo que tenga algún elemento con atributo href y que ellos mismos (los capitulo tengan el atributo public a valor si:
//capitulo[parrafo/*[@href]][@public='si']
Aunque también se puede hacer uso del operador and encenrrando entre paréntesis los distintos predicados logicos.
Ejemplo similar al anterior
//capitulo[ (parrafo/*[@href]) and (@public='si')]
También se puede hacer uso de la operación or.
Esiste otro tipo de operacion or que utiliza la barra vertical: | separando no dos predicados, sino dos expresiones XPath.
Seleccionar todos los capitulo que tengan un parrafo que tenga algún elemento con atributo href o todos los apendice:
//capitulo[parrafo/*[@href]]|//apendice
Por último, también podemos especificar con not la negación de alguna de las negaciones del predicado.
Seleccionar todos los capitulo que no tengan el atributo public
//capitulo[not(@public)]
Existen, por último, ciertas funciones que nos van a servir para restringir el conjunto de nodos devueltos en una expresión XPath basándose en la posición del elemento devuelto. Tales funciones son: position(), last() e id().
Seleccionar el segundo capitulo:
//capitulo[position()=2]
Esta función se puede simular poniendo simplemente el número entre corchetes.
Mismo ejemplo anterior: seleccionar el segundo capitulo:
//capitulo[2]
Seleccionar el último capitulo:
//capitulo[last()]
Seleccionar todos los capitulo menos el último:
//capitulo[not(position()=last())]
Estas funciones se pueden usar con expresiones matemáticas, como en el siguiente ejemplo.
Seleccionar el penúltimo capitulo:
//capitulo[last()-1]
Seleccionar los parrafo hijos del elemento con id="capitulo_1":
id( "capitulo_1" )/parrafo