3.1. XPath¶
En muchas de las operaciones que se hacen con un documento XML, como extraer información o escribir reglas para su transformación es necesario referirse a nodos o conjuntos de nodos. XPath es un lenguaje estándar para la selección de uno o varios componentes (nodos, atributos, etc.) dentro de un documento XML.
XPath es, pues, un estándar del W3C para seleccionar con qué nodos de un documento XML se desea operar para cuya operación posterior se requerirá el uso de otra herramienta como XQuery, XSLT o alguna biblioteca de programas de propósito general. Es, por tanto, una herramienta que no tiene sentido utilizar aislada.
Prudencia
XPath no es el único lenguaje para esta tarea. En HTML (que no es XML, pero tiene bastantes semejanzas) es muy común usar para la selección de nodos los selectores CSS, diseñados en principio para definir su estilo (o sea, su aspecto). A pesar de nacer para documentos HTML, los selectores CSS son perfectamente válidos para identificar nodos XML, por lo que son una alternativa muy utilizada. Por ejemplo, Javascript los soporta para la selección de nodos en general (véase document.querySelectorAll), en vez de XPath, a pesar de que existe la función document.evaluate, que permite usar XPath.
El nombre de la tecnología deriva de la fusión de XML y la palabra inglesa path, que en un sistema de archivo es la manera de indicar la ruta que hay que seguir para llegar a un determinado archivo. La analogía no es gratuita, puesto que en el caso de los sistemas de archivos se habla del árbol de directorios, de la misma manera que en un documento XML hay un árbol de nodos.
Desde su creación en 1999, XPath se ha ido actualizando con distintas versiones:
- Versión 1,
que apareció en el año 1999, y es universalmente soportada. Es la versión que soporta libxml, la librería que utiliza xmlstarlet.
- Versión 2,
que apareció en 2007, e introduce mejoras muy significativas. No todo el software soporta esta especificación.
- Versión 3,
que apareció en 2014. El cambio respecto a la versión anterior, no es tan significativo como el que se produjo entre la 1 y la 2. En el mundo del software libre soportan esta versión la librería Saxon para Java y Xerces, que tiene versión para C++. En la CLI de Linux puede usarse a través de algunos programas de software libre:
xqilla, que, aunque tiene paquete en Debian, desgraciadamente no está disponible para Bookworm, por problemas de compilación con g++-11.
xidel, que no tiene paquete oficial, pero puede descargarse de su página oficial.
- Versión 3.1,
publicada en 2017, que añade soporte para dos tipos de datos nuevos, la secuencia y el mapa, lo que posibilita que el lenguaje sea apto para consultar también documentos JSON.
Ver también
El W3C mantiene una página que enumera todas las versiones. Para una exhaustiva comparación entre las versiones 1.0 y 2.0 se encuentra en este documento y para una comparación más sucinta este artículo en la web de Microsoft.
En estos apuntes desarrollaremos primero la norma de XPath 1.0 (por su amplio soporte) y expondremos luego cuáles son los cambios y ampliaciones que presenta XPath 2.0.
3.1.1. Procesadores¶
Como en unidades anteriores, antes de entrar en profundidad en el asunto, es conveniente conocer qué herramientas tenemos para procesar nuestras expresiones Xpath tenemos muchas alternativas. Hay muchas entre las que señalaremos:
XPather, que permite evaluar online expresiones XPath 2.0.
Visual Studio Code con la extensión XPath Tester permite evaluar expresiones XPath 1.0. Para evaluar expresiones, basta con pulsar Ctrl+Shift+P y buscar XPath para que accedamos al cuadro de diálogo que nos permite hacer evaluaciones.
BaseX, que soporta XQuery 3.0 (y consecuentemente XPath 3.1). Podemos usarlo directamente o a través de Visual Studio Code.
xmlstarlet, que soporta expresiones XPath 1.0.
El programa Xidel, que soporta XPath 3.0.
3.1.2. Sintaxis¶
Una expresión XPath es, simplemente, una expresión evaluable que devuelve un resultado. Por lo general, dentro de esta expresión se refirieron nodos del XML, pero no necesariamente. Ésta es una expresión XPath perfectamente válida:
1 = 1
¿Es 1 igual a 1? Al ser una tautología, la respuesta es obviamente
verdadera, así que el resultado de esa expresión será true
. Cuando
involucramos nodos en la expresión:
/nodo1/nodo2/nodo3
se advierte mejor la analogía entre XPath y las rutas de archivos. En este caso, la evaluación de expresión devolverá todos los nodo3 incluidos dentro de los nodo2 incluidos dentro del nodo1. O sea, lo mismo que devolvería una ruta de archivos, con la salvedad de que los directorios son únicos y no se podrían devolver varios.
Traigamos el ejemplo del claustro de profesores para ilustrar las rutas que expresemos a continuación:
XML de casilleros
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE claustro SYSTEM "casilleros.dtd">
<claustro centro="IES Pepe Botella">
<profesor id="p1">
<apelativo>Pepe</apelativo>
<nombre>José</nombre>
<apellidos>Suárez Lantilla</apellidos>
<departamento>&ING;</departamento>
</profesor>
<profesor id="p13">
<apelativo>Cristina</apelativo>
<nombre>María Cristina</nombre>
<apellidos>Prieto Monagas</apellidos>
<departamento>&ByG;</departamento>
</profesor>
<!-- Si se sustituye a sí mismo es que está de baja -->
<profesor id="p15">
<apelativo>Manolo</apelativo>
<nombre>Manuel</nombre>
<apellidos>Páez Robledo</apellidos>
<departamento>&MAT;</departamento>
</profesor>
<!-- Profesor con casillero(s) adicional(es) -->
<profesor id="p17" casillero="17 43">
<apelativo>Lucía</apelativo>
<nombre>Lucía</nombre>
<apellidos>Gálvez Ruiz</apellidos>
<departamento>&ING;</departamento>
</profesor>
<!-- En principio, si el casillero está vacío
la aplicación les asigna el que indica su identificador,
pero se puede asignar uno distinto.
-->
<profesor id="p28" casillero="28">
<apelativo>Miguel Ángel</apelativo>
<nombre>Miguel Ángel</nombre>
<apellidos>Campos Sánchez</apellidos>
<departamento>&HIS;</departamento>
</profesor>
<profesor id="p81" casillero="">
<apelativo>Verónica</apelativo>
<nombre>Verónica</nombre>
<apellidos>Martín Díaz</apellidos>
<departamento>&ByG;</departamento>
</profesor>
<!-- Profesor sustituto -->
<profesor id="p86" sustituye="p13">
<apelativo>Roberto</apelativo>
<nombre>Roberto</nombre>
<apellidos>Mínguez Torralbo</apellidos>
</profesor>
</claustro>
En este caso, la expresión:
/claustro
representa el nodo claustro.
Nota
En XPath el nodo claustro no es el nodo raíz, sino «/», de ahí que
la expresión /claustro
indique el nodo claustro que es inmediatamente
hijo del nodo raíz. Por ese motivo, si este es el documento XML:
<?xml version="1.0" encoding="utf-8"?>
<!-- Este es un ejemplo de documento XML -->
<grafico unidades="cm">
<punto id="p01">
<x>4</x>
<y>10</y>
</punto>
</grafico>
La expresión:
/grafico
devuelve, exclusivamente, el elemento grafico, mientras que la expresión
/
devuelve también el nodo comentario (<!-- Este es un ejemplo de documento XML -->
).
Por su parte, la expresión:
/claustro/profesor
representa todos los nodos profesor que son hijos de claustro. Es posible también hacer referencia a descendientes no directos como en:
/claustro//nombre
en que seleccionamos todos los nodos nombre que son descendientes de claustro. Para este caso particular, obtenemos los nombres de todos profesores que pertenecen al claustro. Otro ejemplo podría ser:
//profesor
Del mismo modo que en las rutas de archivos existen rutas relativas, en las
rutas XPath también existen, y del mismo modo que puede surgir la necesidad de
seleccionar un directorio o archivo cuando nos encontramos trabajando en otro
directorio, es posible que necesitemos hacer referencia a otros nodos cuando
nos encontramos procesando un determinado nodo. Para las rutas relativas, como
en el caso de los archivos, se usa el punto (.
), que significa este nodo, y
dos puntos seguidos (..
), que significan el nodo padre. Así pues, si
estuviésemos procesando un determinado nodo profesor, la ruta:
./nombre
o, simplemente:
nombre
representaría el nodo nombre de ese profesor; mientras que, si procesando el nodo apelativo de un profesor, hacemos referencia a:
../nombre
haremos referencia al nombre del profesor cuyo nodo estuviéramos procesando. En realidad la expresión de la ruta es algo más complicada que indicar simplemente su nombre como hemos ilustrado:
/nodo1/nodo2/nodo3
Cada paso de la ruta (ésta consta de tres) tiene, en realidad, la siguiente estructura completa:
eje::filtro[predicado]
Advertencia
Antes de proseguir es importante hacer una aclaración terminológica. Hasta ahora es posible que hubiéramos tratado los términos «nodo» y «elemento» como sinónimos. Pero ello no es así: un elemento, en realidad, es un tipo de nodo llamado nodo elemento. Nodo es un término más general que engloba también otros tipos de nodos: los atributos, que son nodos atributo; los comentarios, que son nodos comentario; el texto, que son nodos texto, etc.
3.1.2.1. Filtros¶
El filtro es aquella parte de la ruta XPath que expresa qué nodo o nodos quieren referirse con tal ruta. Puede ser:
- QName (nombre cualificado)
Los nodos elemento y los nodos atributo tienen nombres cualificados, por lo que, cuando se usa un nombre cualificado, queremos indicar que queremos escoger un nodo con tal nombre de uno de estos dos tipos. Por ejemplo:
/claustro
selecciona el nodo claustro, hijo del nodo raíz, mientras que:
/claustro/profesor
selecciona los nodos profesor hijos del nodo claustro.
Prudencia
Aquí es importante hacer una puntualización: claustro tiene un atributo con nombre cualificado nombre; sin embargo:
/claustro/centro
no devuelve el nodo atributo de nombre cualificado centro, porque los atributos no son hijos del nodo elemento al que pertenecen. Los nodos elemento (y otros tipos de nodos como los comentarios o las instrucciones de procesamiento) están en un eje, el eje child, distinto al eje de los atributos, los cuales se encuentran en el eje attribute. Cuando no se hace mención al eje se sobrentiende que es child, razón por la cual las expresiones anteriores en las que hemos utilizado nombres cualificados hacen referencia exclusivamente a elementos.
Además, puede usarse el carácter especial
*
, que concuerda con cualquier nombre cualificado. Por ejemplo:/claustro/profesor[1]/*
selecciona los elementos apelativo, nombre, apellidos y departamento hijos del primer profesor.
Ahora bien, si los atributos también tienen nombre cualificado, ¿cómo podemos seleccionarlos? La respuesta es sencilla: expresando explícitamente el eje en el que se encuentran. Los ejes se tratan a continuación, pero adelantemos que para los atributos podemos hacerlo así:
/claustro/attribute::nombre
o, como alternativa más compacta, expresando el eje mediante la anteposición de una arroba (
@
) al nombre cualificado:/claustro/@nombre
Por supuesto, el carácter
*
también es válido, por lo que://profesor[4]/@*
devolverá todos los atributos asociados al cuarto profesor.
- text()
Selecciona los nodos de texto. Por ejemplo:
/claustro/profesor[1]/nombre/text()
devuelve exclusivamente la cadena «José», mientras que la expresión:
/claustro/profesor[1]/nombre
devuelve el nodo nombre, esto es:
<nombre>José</nombre>
Nota
Nótese que a efectos de su definición gramatical este nodo:
<profesor id="p1" sexo="hombre"> <apelativo>Pepe</apelativo> <nombre>José</nombre> <apellidos>Suárez Lantilla</apellidos> <departamento>&ING;</departamento> </profesor>
y este otro:
<profesor id="p1" sexo="hombre"><apelativo>Pepe</apelativo><nombre>José</nombre><apellidos>Suárez Lantilla</apellidos><departamento>&ING;</departamento></profesor>
son equivalentes: un nodo profesor que contiene cuatro nodos elemento: apelativo, nombre, apellidos y departamento. En cambio, para XPath la primera expresión de profesor contiene nodos texto entre los nodos elemento, cada uno de los cuales contiene, simplemente, caracteres de espaciado.
- node()
Todos los nodos, sean del tipo que sean. Por ejemplo:
/claustro/node()
seleccionará todos los nodos hijo de claustro, que en el ejemplo resultan ser los nodos profesor y los nodos texto hijo constituidos por cambios de línea y espacios, que hemos incluido para lograr sangrar con tanta pulcritud el documento.
Nota
En cambio:
/claustro/*
selecciona exclusivamente los nodos profesor y no los de texto, ya que el comodín sólo refiere nombres cualificados.
Nota
node()
también obtiene nodos atributo, pero del mismo modo en que ocurrió al usar nombres cualificados, están en un eje distinto al de los hijos de claustro. Por ese motivo, la expresión no los devuelve. En cambio, si hacemos:/claustro/attribute::node()
sí obtendremos nodos atributo.
- comment()
Todos los nodos que sean comentarios:
/claustro/comment()
- processing-instruction()
Todas las instrucciones de procesamiento. Por ejemplo:
//procesing-instruction()
selecciona todas las instrucciones de procesamiento del documento. Puede incluirse como argumento el destino, que es la palabra adyacente al signo «?»:
//procesing-instruction(xml-stylesheet)
- element() (XPath 2/3)
A partir de XPath 2.0, existe
element()
que concuerda con nodos elemento:/claustro/profesor[1]/element()
Este filtro permite expresar el nombre del elemento:
/claustro/profesor[1]/element(apodo)
lo que sería equivalente a haber usado directamente el nombre cualificado. Sin embargo, si el procesador es capaz de validar con una gramática el documento, entonces conocerá el tipo de dato que almacena y puede filtrarse por tipo de valor:
/claustro/profesor[1]/element(*, xs:integer)
- attribute() (XPath 2/3)
A partir de XPath 2, existe también el filtro
attribute()
que lleva implícito que se refiere al eje attribute:/claustro/attribute()
Admite los mismos argumentos que element() para afinar el filtro.
Nota
Obsérvese que estas expresiones responden a cuáles son los siete tipos de nodos que el modelo de datos de XPath define:
Nodo de documento (o nodo raíz)
Nodo elemento.
Nodo texto.
Nodo atributo.
Nodo de espacio de nombres.
Nodo de instrucción de procesamiento.
Nodo comentario.
De entre estos siete tipos, dos (los nodos texto y los nodos atributo) pueden a su vez estar tipados de distinta forma (ser cadenas, ser números, etc.). Más adelante profundizaremos en ello.
XPath permite agregar rutas de manera que los nodos seleccionados sean la
unión de los nodos que seleccionan las distintas rutas. Para ello usa el
operador «|
»:
//profesor/apelativo | //profesor/nombre
La expresión selecciona todos los nodos apelativo y nombre de todos los profesores.
3.1.2.2. Ejes¶
El eje es la parte de la ruta XPath que determina el sentido que se tomará al interpretar la siguiente parte de la ruta. Recordemos que, si nos encontramos procesando un nodo y queremos referirnos a otro, podemos buscar entre sus descendientes, entre sus ascendientes, entre sus hermanos o entre sus atributos.
Sentido |
Nombre |
Abreviatura |
Ejemplo |
Descripción |
---|---|---|---|---|
Descendente |
child |
/claustro/profesor
/claustro/child::profesor
|
Selecciona hijos. |
|
descendent |
/ |
/claustro//apelativo
/claustro/descendent::apelativo
|
Selecciona descendientes. |
|
self |
. |
/claustro/.
/claustro//self::*
|
Selecciona el propio nodo. |
|
descendent-or-self |
/claustro/descendent-or-self::* |
Selecciona descendientes o el propio nodo |
||
following-sibling |
//profesor[1]/following-sibling::profesor |
Selecciona los hermanos posteriores |
||
following |
//profesor[1]/following::* |
Selecciona los nodos posteriores |
||
attribute |
@ |
//profesor/@sexo
//profesor/attribute::sexo
|
Selecciona atributo (puede usarse
|
|
Ascendente |
parent |
.. |
//profesor[1]/apelativo/..
//profesor[1]/apelativo/parent::*
|
Selecciona el nodo padre. |
ancestor |
//profesor[1]/apelativo/ancestor::*
|
Selecciona ascendientes. |
||
ancestor-or-self |
//profesor[1]/apelativo/ancestor-or-self::* |
Seleccioa ascendientes o el propio nodo |
||
preceding |
//profesor[2]/nombre/preceding::nombre |
Selecciona nodos anteriores |
||
preceding-sibling |
//profesor[2]/nombre/preceding-sibling::* |
Selecciona los hermanos anteriores |
3.1.2.3. Predicados¶
El predicado es la parte de la ruta que permite añadir condiciones al filtro al que acompaña y se escribe entre corchetes. Por ejemplo:
//profesor[@sexo = 'hombre']
Nota
En XPath las cadenas pueden notarse indistintamente con comillas simples o con comillas dobles.
selecciona, entre todos los nodos profesor, sólo los que representan profesores varones.
Dos son aspectos fundamentales a la hora de componer un predicado:
Al representar el predicado una condición, la expresión se evalúa a verdadero o falso. En el ejemplo, comprobábamos una igualdad.
Las rutas XPath expresadas dentro del predicado, si son relativas, son relativas al nodo adyacente (profesor en el caso del ejemplo). En consecuencia,
@sexo
es el atributo sexo del profesor en cuestión.
Nota
Para poder explicar mejor los predicados supondremos a lo largo de estas explicaciones que un profesor puede tener ninguno, uno o varios apodos, esto es, que su definición usando DTD es:
<!ELEMENT profesor (apelativo*, nombre, apellidos, departamento)>
Para expresar condiciones necesitamos operadores y funciones, aunque ni unos ni otras deben forzosamente aparecer dentro de un predicado. Por ejemplo, esto es una expresión XPath válida:
1 + 1
que devuelve 2 y esta otra, que devuelve el número de profesores, también:
count(//profesor)
Sin embargo, como habitualmente aparecen dentro de predicados las introduciremos como apartados dentro de este epígrafe.
3.1.2.3.1. Operadores¶
Dentro de los predicados pueden usarse los siguientes operadores:
Operador |
Significado |
---|---|
+ |
Suma |
- |
Resta |
* |
Multiplicación |
div |
División |
mod |
Módulo (resto) |
Operador |
Significado |
---|---|
= |
Igualdad |
!= |
Desigualdad |
< |
Menor |
<= |
Menor o igual |
> |
Mayor |
>= |
Mayor o igual |
Operador |
Significado |
---|---|
and |
Conjunción |
or |
Disyunción |
not()[1] |
Negación |
Ejemplos:
//profesor[@sexo = 'hombre']
//profesor[nombre != 'José']
//profesor[not(nombre = 'José')]
//profesor[@sexo = 'hombre' and departamento = 'Historia']
Nota
Pueden yuxtaponerse dos predicados y en ese caso deberán cumplirse ambos predicados. Por tanto, el último ejemplo es equivalente a:
//profesor[@sexo = 'hombre'][departamento = 'Historia']
3.1.2.3.2. Funciones¶
Junto a nodos y operadores, en los predicados[2] también es posible usar funciones. En el sitio para desarrolladores de mozilla puede consultarse una relación completa de funciones para XPath 1.0, de las cuales aquí citaremos algunas muy socorridas:
- count()
Cuenta el número de nodos. Por ejemplo, podemos seleccionar los profesores que tienen más de un apelativo de este modo:
//profesor[count(apelativo) > 1]
- local-name()
Devuelve el nombre del elemento (sin el espacio de nombres). Si no se incluye como argumento un nodo, se devuelve el nombre del nodo actual. Por ejemplo:
//profesor/*[local-name() = 'apelativo' or local-name() = 'nombre']
selecciona los nodos hijo de profesor que son apelativo o nombre.
Nota
Hay modos alternativos de lograr esto mismo:
//profesor/*[self::apelativo or self::nombre]
o también usando el operador de unión
//profesor/apelativo | //profesor/nombre
- not()
Invierte el resultado lógico de la expresión. Ya se trató al hablar de los operadores.
- position()
Devuelve la posición del elemento actual. Para seleccionar el segundo profesor se puede hacer:
//profesor[position() = 2]
que puede simplificarse simplemente a:
//profesor[2]
Advertencia
A diferencia de lo que suele ocurrir en Informática, el primer elemento se identifica con la posición 1, no con la 0.
Nota
Para indicar el último, puede usarse la función last().
- sum()
Suma los nodos que se incluyen como argumento, supuesto que estos sean números.
3.1.3. XPath 1.0¶
Los aspectos introducidos hasta ahora sobre XPath son generales e independientes de qué versión utilicemos. Han sido expuestos de forma tan general que pecan (a sabiendas) de superficialidad y dejan aún muchos aspectos indefinidos. Por ejemplo, la expresión:
//profesor[@casillero = 28]
¿Funciona o no funciona realmente? Porque quizás es necesario escribir el número 28 entre comillas si el valor del atributo es una cadena. Hay, pues, que afinar para poder escribir las expresiones con propiedad.
Para empezar hay que aclarar dos aspectos importantes:
- Resultado
esto es, ¿qué devuelve una expresión XPath? En la versión 1.0, se pueden obtener resultados de dos naturalezas distintas:
Valores atómicos, que se devuelven en expresiones como:
1 = 1
o:
1 + 1
o:
count(//profesor)
Un conjunto de nodos (a veces constituido por un solo nodo), que es lo que devuelven expresiones como:
//profesor/nombre
que devuelve un conjunto de nodos elemento, o esta:
//profesor/@id
que devuelve un conjunto de nodos atributo, o esta:
//profesor[@id = 'p28']/@casillero
que devuelve un conjunto de nodos atributo, constituido por un nodo solo.
Nota
Obsérvese que se ha usado la palabra «conjunto» y no «secuencia». Es importante el matiz, porque las secuencias ordenan sus elementos, mientras que los conjuntos, no. Esto quiere decir que los conjuntos de nodos, en teoría, no aseguran cuál es el orden en que se obtendrán nodos. En la práctica, sin embargo, los nodos siempre se devuelven en el orden en que se encuentran en el documento XML.
- Tipos de datos
¿Cuáles son los tipos de datos (no de nodos, que ya hemos enumerado) que reconoce XPath 1.0? Solamente 4:
Booleanos (notados mediante las funciones
true()
yfalse()
).Números.
Cadenas.
Conjuntos de nodos.
Es muy conveniente tener presente esto, porque los operadores pueden comportarse de distinto modo según sea el tipo de los operandos y también habrá que saber cómo se actúa cuando los operandos son de distinto tipo. Lo que podemos adelantar es que XPath 1.0 (a diferencia de XPath 2/3) tiene tipado débil.
Por tanto, para afinar por completo nuestro conocimiento de XPath, es necesario saber cómo realiza XPath la evaluación de las operaciones. Por ejemplo, observe esta expresión aparentemente simple:
//profesor[nombre = "José"]
Intuitivamente entendemos que esta expresión devuelve el conjunto de profesores
(en puridad, nodos profesor) cuyo nombre es «José». Sin embargo, si
profundizamos un poco, llegaremos a la conclusión de que «José» es un cadena,
pero el nodo nombre
con el que se compara no lo es: es un nodo elemento que,
a su vez contiene un nodo texto, cuyo valor sí es una cadena:
//profesor[nombre/text() = "José"]
¿Tendríamos que haber expresado la comparación así para que ambos operandos fueran una cadena? Lo cierto es que no llega a ser necesario y la primera expresión también funciona, porque existen mecanismos de conversión automática de tipos. Este es un aspecto que debemos revisar.
- Conversión de tipos
Al evaluarse expresiones es común que se realicen conversiones automáticas de tipos, aunque pueden forzarse algunas conversiones utilizando las funciones boolean(), string() o number().
Las conversiones automáticas operan del siguiente modo:
Si el operador es
=
o!=
y alguno de los dos operandos es booleano, el otro se convertirá a booleano; si no es así y uno de los dos es número, el otro se convierte a número; y, en cualquier otro caso, ambos se convierten a cadena.Si los operadores son de comparación (
<
,<=
,>
y>=
), los operandos se convierten a número.Advertencia
Nótese que esto implica que en XPath 1.0 no pueden compararse alfabéticamente cadenas. Y como no existe el tipo «fecha», no pueden compararse fechas (que son cadenas), aunque tengan el formato
AAAA-MM-DD
. Para comparar fechas, primero hay que quitar los guiones para que quede la cadenaAAAAMMDD
que sí puede convertirse en un número. Por ejemplo, si queremos comprobar si un nodo fecha es anterior al 21 de diciembre de 2020:translate(fecha, "-", "") < 20201221
En esta expresión hemos usado la función para cadenas translate.
Ante operadores aritméticos, ambos operandos se convierten a número.
Otro aspecto a considerar es qué resultado produce la conversión a otro tipo:
Conversión a cadena:
Los números se convierten a cadenas según lo que se esperaría de ellos.
Los valores booleanos false y true se convierten a las cadenas «false» y «true».
Un conjunto de nodos se convierte a cadena, convirtiendo a cadena el primero de los nodos. Si el conjunto está vacío, entonces se obtiene la cadena vacía. El valor de cadena de un nodo es el valor del atributo, si el nodo es un nodo atributo; o la concatenación de los nodos de texto descendientes, si el nodo es un nodo elemento.
Conversión a número:
Las cadenas que representan números son convertidas al número equivalente; si no representan ningún número, se obtiene NaN.
true se convierte a 1, y false a 0.
Para convertir en número un conjunto de nodos se convierte primero el conjunto en cadena.
Conversión a booleano:
Los números negativos y positivos se consideran verdaderos; 0 y NaN se consideran falsos.
Un conjunto de nodos es verdadero, a menos que esté vacío. Esta es la razón por la que para seleccionar los profesores que no tienen apelativo basta con hacer:
//profesor[not(apelativo)]
Una cadena es verdadera, a menos que sea la cadena vacía. Nótese que la conversión de la cadena "false" resulta en un valor verdadero.
Sabido esto ya podemos desentrañar el misterio de por qué funciona:
//profesor[nombre = "José"]
El primer operando, «nombre», es un conjunto de nodos elemento que contiene un único nodo elemento y el segundo operando es una cadena. Como el operador es «=», esto implica que «nombre» se convierte a cadena. Esto implica que el primer elemento del conjunto (sólo hay uno en cualquier caso), se transforma a cadena. ¿Cómo se transforma un nodo elemento a cadena? Se yuxtaponen los valores de todos los nodos de texto que contiene. Sólo tiene uno, el que contiene el propio nombre, por lo que la comparación se puede llevar a cabo.
- Pluralidad
Existe, además del de la conversión, otro aspecto a tener en cuenta. Recordemos que habíamos modificado el DTD para que un profesor pudiera tener varios apodos. ¿Cómo encontraríamos los profesores a los que se conoce como «Pepe»? La respuesta inmediata es:
//profesor[apodo = "Pepe"]
que es correcta, pero que tiene más miga de la que aparenta. Descontado el hecho de que hemos escrito
apodo
y noapodo/text()
(que acabamos de explicar por qué funciona), un mismo profesor puede tener varios apodos, así que el operador de la izquierda no tiene que ser un único nodo, puede ser un conjunto de nodos, caso de que a nuestro profesor don José, lo conozcan como Pepe, pero también como Pepito. Así que esa comparación no es de uno contra uno, sino de varios contra uno, y en otros casos nos podríamos encontrar con operaciones binarias[3] de varios contra varios.Cuando las operaciones implican conjuntos de nodos, nos encontramos con lo que denominaremos el problema de la pluralidad. XPath lo resuelve del siguiente modo:
Cuando se compara una pluralidad de nodos con un único valor, la expresión es verdadera con que al menos uno de los nodos de la pluralidad cumpla con la expresión. En consecuencia, en el ejemplo anterior bastaría con que uno de los apodos del profesor fuera «Pepe» para que ese profesor quedara seleccionado.
Cuando ambos operandos de la expresión son una pluralidad de nodos, la expresión es verdadera si hay un nodo del primer conjunto y un nodo del segundo conjunto, que al ser comparados entre sí como cadenas hacen que la expresión sea verdadera.
Prudencia
Es importante tener presente que cuando uno o los dos operandos son una pluralidad, lo contrario a:
//profesor[apelativo = "Pepe"]
no es:
//profesor[apelativo != "Pepe"]
sino:
//profesor[not(apelativo = "Pepe")]
Sólo esta segunda expresión nos devolvería los nodos de los profesores cuyo apelativo no sea «Pepe», ya que la segunda expresión sería cierta con que alguno de los apodos del profesor no fuera «Pepe». Por ejemplo, un profesor que tenga por apodos «Pepe» y «Pepito» hace verdadero el primer predicado, pero también el segundo, ya que «Pepito» no es igual a «Pepe». Sólo el tercer predicado es cierto cuando ninguno de los apodos del profesor es «Pepe».
3.1.4. XPath 2.0/3.0¶
La versión 2.0 del lenguaje supuso un gran cambio hasta el punto de que, aunque procuró mantenerse cierta compatibilidad con la primera versión, no todas las expresiones se evalúan del mismo modo. Además, la especificación permite que los procesadores se ejecuten en dos modos de funcionamiento: uno de compatibilidad con XPath 1.0 y otro sin ella, en que las incompatibilidades son mayores. A grandes rasgos las novedades se pueden resumir en estos puntos:
Dos redefiniciones conceptuales de suma importancia referentes al resultado de evaluar la expresión y a los tipos de datos.
Nuevos operadores.
Nuevas funciones.
Algunas construcciones novedosas.
XPath 3.1 incorporó soporte para arrays y mapas y, con ello, soporte para JSON.
- Resultado
A diferencia de lo que ocurre en XPath 1.0, en las versiones posteriores el resultado de una expresión es siempre una secuencia de ítems, esto es, una serie ordenada de valores o nodos. Por tanto, la expresión:
1 = 1
devuelve una secuencia constituida por un único valor lógico (verdadero), o
/claustro/profesor
devuelve una secuencia constituida por todos los nodos profesor ordenada según aparecen en el documento. Las secuencias pueden construirse explícitamente:
(1 = 1, 1 + 1, "Soy una cadena", //profesor)
lo cual es muy útil porque nos sirve para predicados como este:
//profesor[nombre = ("Manuel", "José")]
que devolverá todos los nodos profesor en que el profesor se llame «Manuel» o «José», ya que el operando de la izquierda es único, el de la derecha varios y ya sabe cómo se resuelve el problema de la pluralidad.
Una consecuencia de que la naturaleza del resultado haya cambiado, es que empiezan a tener sentido expresiones que en la versión 1.0 estaban vetadas. Por ejemplo, la expresión
//profesor/nombre/concat("don/doña ", .)
ahora sí es válida, puesto que concatenar el tratamiento «don» o «doña» al elemento nombre de cada profesor genera una cadena y en consecuencia, la expresión genera una secuencia de cadenas. Tal resultado, sin embargo, era imposible en XPath 1.0, pues éste sólo permite como resultado un valor atómico o un conjunto de nodos, lo que hacía inválida la expresión.
Las secuencias tienen algunas particularidades:
Cuando están compuestas por un único ítem, pueden escribirse con o sin el paréntesis. En consecuencia, esta expresión:
1
y esta otra:
(1)
son equivalentes.
Las secuencias no se anidan a otras secuencias, sino que si se produce este hecho, se aplanan. En consecuencia esto:
(1, 2, (3, 4), 5)
es equivalente a esto otro:
(1, 2, 3, 4, 5)
Las secuencias que representan rangos de números enteros pueden expresarse con una sintaxis abreviada. Así, la secuencia anterior puede escribirse como:
(1 to 5)
- Tipos
El otro gran cambio sustancial son los tipos atómicos de los datos (que no de nodos, pues siguen siendo los siete mismos). De XPath 2.0 en adelante se asumen los tipos definidos para XML Schemas, más algún pequeño añadido que, resumidos por cortesía de Wikipedia, los podemos ver en el siguiente esquema:
Así pues, nos encontraremos con tipos de datos como
xs:integer
oxs:string
. Sin embargo, que el procesador haya sido capaz de determinar el tipo de dato de un nodo es sólo posible si existe definida una gramática con la que pudo validarlo. Esta es una diferencia capital con XPath 1.0. En la primera versión, la gramática es irrelevante para el procesador, mientras que en las siguientes versiones la existencia de una gramática determina su comportamiento a la hora de evaluar las expresiones. Sin validación el comportamiento podrá ser semejante a la primera versión (o no), pero con validación la evaluación diferirá en muchos casos. Cuando no hay validación, el tipo de dato quedará indefinido (xs:untypedAtomic
), cuya conversión generalmente se comporta como la de las cadenas.Prudencia
Recuerde, pues, que el procesador sólo sabrá el tipo concreto del dato almacenado en un nodo si ha sido capaz de utilizar una gramática para determinarlo. Por ese motivo, ante este XML:
<cifras> <n>11</n> <n>9</n> <n>123</n> </cifras>
la expresión:
//n[not(. < //n)]
devuelve el segundo nodo «n» (9) en vez de devolver el nodo tercero que es el que tiene el número mayor (123).
Al haberse ampliado la cantidad de tipos, ha aumentado la complejidad y ha cambiado la forma de tratarlos respecto a XPath 1.0, por lo que mucho de lo indicado respecto a sus conversiones en XPath 1.0 ya no es válido. Muy resumidamente:
Existen los constructores
xs:string()
,xs:integer()
,xs:boolean()
, etc. que permiten convertir el tipo del valor que se proporcione como argumento. El argumento debe ser un valor único o, si se prefiere decir así, una secuencia con uno y sólo un ítem. Por tanto, la expresión:xs:string((1, false()))
provocará un error, ya que intenta convertirse una secuencia de dos elementos. Como alternativa al uso de estos constructores como funciones para realizar una conversión, existe
cast as
que tiene el mismo efecto. Por tanto, vale tanto hacer:xs:boolean("false")
como hacer:
"false" cast as xs:boolean
y, en ambos casos, la expresión devuelve el valor lógico falso. Para prevenir un posible error, existe
castable as
, que permite inquirir si una conversión es posible o no. Así, es verdadera la expresión:"false" castable as xs:boolean
pero falsa:
(1, false()) castable as xs:string
Además de los constructores, siguen existiendo las funciones fn:boolean(), fn:string() y fn:number(), que se comportan de un modo más parecido a XPath 1.0 de como lo hacen los constructores. Por ejemplo,
boolean("false")
es verdadero yboolean("")
es falso, mientras que al usar el constructorxs:boolean
la primera expresión es falsa y la segunda devuelve un error. Sin embargo, la conversión con la funciónstring()
de una secuencia de dos o más elementos provoca un error, a diferencia de lo que ocurre en XPath 1.0 al convertir un conjunto de nodos en cadena, que se duelve la conversión a cadena del primer nodo,No existen los constantes
true
nifalse
, sino las funciones true() y false().El valor efectivo booleano, que es el valor lógico que el procesador tiene que calcular automáticamente en determinados casos (para el resultado de un predicado, para el argumento de la función not, etc.) se obtiene al aplicar la función
boolean()
y no el constructorxs:boolean()
. Por ese motivo, la expresión:/claustro/profesor[apodo]
devolverá los profesores con algún apodo (tal como ocurre en XPath 1.0) en vez de generar un error si algún profesor presentara más de un apodo.
Prudencia
Este valor no es aplicable siempre que se realiza una promoción automática al tipo lógico. Por ejemplo, en una comparación dentro el otro operando es lógico o como argumento de una función que espera un lógico (y que no sea
not()
), la conversión se realiza conxs:boolean
.instance of
permite preguntar sobre el tipo del ítem. Así:'José' instance of xs:string
devolverá verdadero, o:
/claustro/profesor[1]/nombre instance of element()
también lo hará.Es también verdadero:
/claustro/profesor[1]/nombre instance of node()
ya que un elemento es un tipo particular de nodo.
element()
(nonode()
) permite ser más exhaustivo e indicar como primer argumento el nombre del nodo. Por tanto, es verdadera esta expresión:/claustro/profesor[1]/nombre instance of element(nombre)
Como segundo argumento de
element()
, además, se puede indicar el tipo del contenido, por lo que también podríamos hacer:/claustro/profesor[1]/nombre instance of element(nombre, xs:string)
si existiera una gramática con la que el procesador hubiera validado el documento. En ausencia de ésta, sería cierto:
/claustro/profesor[1]/nombre instance of element(nombre, xs:untypedAtomic)
ya que el contenido del elemento nombre es un valor atómico. Para documentos no validados el tipo de todos los elementos (incluido el de aquellos que contienen a su vez elementos y, por tanto, su contenido no es un valor atómico) es
xs:untyped
:/claustro/profesor[1] instance of element(profesor, xs:untyped)
Cuando se pregunta sobre secuencias de ítems, es posible usar la nomenclatura que se usa al definir la cardinalidad en DTD:
/claustro/profesor/nombre instance of element()+
La función data() devuelve el valor atómico del nodo que se le proporciona como argumento. Por ello, la expresión:
data(/claustro/profesor[1]/nombre)
devolverá el valor atómico del elemento nombre del primer profesor en el documento y no el elemento en sí. Por tanto, es verdadera la expresión:
data(/claustro/profesor[1]/nombre) instance of xs:untypedAtomic
ya que se ha supuesto que el procesador no ha analizado una gramática que proporcione el tipo del elemento[4]. A la función se le puede proporcionar una secuencia para que obtenga el valor atómico de cada ítem de la secuencia:
data(/claustro/profesor/nombre)
La expresión devuelve los valores atómicos que contiene cada uno de los elementos nombre de profesor. Lo habitual es que no haya que usar esta función al construir las expresiones XPath, puesto que, cuando un nodo actúa como operando, el procesador obtiene su valor atómico automáticamente, en un proceso denominado atomización. Así, por ejemplo:
/claustro/profesor/nombre/concat('Hola, ', .)
logrará concatenar a la cadena literal "Hola, " el valor atómico del elemento nombre de cada profesor sin necesidad de usar
data()
.Los tipos de los valores atómicos de los atributos y los elementos pueden servir como filtro en las expresiones. Por ejemplo, esta expresión devolvería los nodo atributo de profesor cuyo valor fuera un entero:
/claustro/profesor/attribute(*, xs:integer)
aunque si el procesador no hubiera validado con una gramática el documento, la expresión anterior sería inútil, ya que todos los atributos serán de tipo
xs:untypedAtomic
:/claustro/profesor/attribute(*, xs:untypedAtomic)
- Operadores
Los operadores que ya existían en XPath 1.0 se mantienen, aunque con algunos cambios, y se añaden otros nuevos. Además, el comportamiento de los valores como operando (o como argumentos de funciones o dentro de las construcciones, que veremos más adelante) puede variar dependiendo de en qué caso nos encontremos:
No hay validación y el procesador corre en modo compatible con XPath 1.0.
No hay validación y el procesador no corre en modo compatible con XPath 1.0.
El procesador ha validado el documento y conoce los tipos de datos.
Ver también
Para un análisis pormenorizado es indispensable echarle un vistazo a qué dice la especificación de XPath 3.1 sobre su comportamiento según estas circunstancias. Lo que se enumeran aquí son reglas más o menos generales que resumen la especificación, pero que dejan sin definir algunos casos particulares.
- Aritméticos
A los ya existentes se añade
idiv
que realiza la división entera (o sea,10 div 3
devuelve 3). Como estas operaciones sólo tienen sentido para valores numéricos:En caso de compatibilidad, el comportamiento será el que se espera en XPath 1.0 (conversiones automáticas para poder realizar la operación).
En caso contrario:
Si uno de los operandos es un conjunto de nodos (una secuencia, en realidad), se generará un error en vez de tomar el primero, que es lo que ocurre en XPath 1.0.
Si los tipos están definidos[5], se producirá un error en caso de que alguno de los operandos no sea numérico.
- Comparativos
En principio, se mantienen los operadores de comparación ya existentes con su característica particular de permitir comparar pluralidades:
Incluso aunque se habilite la compatibilidad, si uno de los operandos es lógico, XPath convierte el otro a lógico, en contra de lo que ocurre en la versión 1.0 en que ambos se convierten a número.
true() > number("0.5")
es pues falso, mientras que en XPath 1.0 se evaluaría como verdadero.En caso de que no haya compatibilidad:
Existe definida la comparación entre cadenas según su orden alfabético. Por tanto, expresiones como
"aaa" < "bbb"
cobran sentido.Las comparaciones entre operandos de distinto tipo (p.e. una cadena con un número o incluso un número con un booleano) provocan error.
Si alguno de los operandos no tiene tipo definido porque no hubo validación (
xs:untypedAtomic
), entonces se producirá una promoción automática al tipo del otro operando para realizar la comparación. Por ejemplo, ante este documento sin gramática:<r> <x>false</x> <x>true</x> <x>false</x> </r>
La expresión:
//x = true()
provocará que el procesador convierta todos los nodos de tipo indefinido a booleano. Por el tratamiento de la pluralidad, ya sabemos que el resultado será cierto, ya que hay un nodo que se evalúa a verdadero. En cambio, si un elemento x hubiera contenido un valor que no puede convertir
xs:boolean
(cualquier cadena arbitraria), se genera un error.Nota
Advierta que esta expresión en XPath 1.0 devuelve falso sólo si no existe ningún elemento x.
Ante tipos como
xs:NMTOKENS
oxs:IDREFS
la comparación de igualdad será cierta con que haya igualdad con uno de los componentes de la serie. Por ejemplo, ante<fondo color="azul verde rojo"/>
la expresión://fondo/@color = "rojo"
es verdadera.
Además de estos operadores hay definidos unos nuevos:
¶ Operador
Significado
eq
Igualdad
ne
Desigualdad
ne
Menor
le
Mayor
La diferencia con los anteriores es que éstos sólo permiten las comparaciones uno a uno, por lo que si alguno de los operandos es una secuencia de longitud distinta a uno, genera un error.
- Lógicos
La única diferencia es que, cuando no actúa en modo de compatibilidad, en expresiones como
expr1 and expr2
oexpr1 or expr2
el procesador puede intentar evaluar la segunda expresión aun cuando no sea necesario.
- De identidad
Se introduce el operador
is
que comprueba si dos nodos son el mismo. Por eso, ante este documento XML<r> <x>false</x> <x>true</x> <x>false</x> </r>
la expresión:
//x[1] = //x[last()]
es verdadera, pero:
//x[1] is //x[last()]
no lo es.
- De concatenación
Se ha definido un nuevo operador
||
que sirve para concatenar cadenas de la forma en la que lo hace la funciónconcat()
. Así, la expresión://profesor/nombre/("Hola, " || .)
es equivalente a:
//profesor/nombre/concat("Hola, ", .)
- De iteración
Otro nuevo operador es
!
que permite aplicar a cada uno de los elementos de la secuencia que se incluya en el operando de la izquierda, la acción que se indique en el operando de la derecha:(1, 2, 3) ! (. * 2)
La expresión devuelve la secuencia
(2, 4, 6)
. Cuando el operando de la izquierda es un conjunto de nodos[6], tiene un efecto similar al operador/
que se usa en las rutas, por lo que la expresión que se usó como ejemplo para ilustrar el operador de concatenación, también podría haberse escrito como://profesor/nombre ! ("Hola " || .)
Sin embargo, no es equivalente porque no obliga a que a la izquierda haya un conjunto de nodos y, cuando lo hay, puede no devolver el resultado en el mismo orden. Por ejemplo, la expresión:
//profesor/(nombre, apelativo)
devolverá los elementos nombre y apelativo de cada profesor, pero en el orden en que aparecen, esto es, primero el apelativo y, luego, el nombre, porque los procesadores se encargan de que el orden sea el que tienen los elementos dentro del documento. Sin embargo al usar este operador:
//profesor ! (nombre, apelativo)
sí se respetará el orden de mostrar primero el nombre y luego el apelativo.
Nota
Este operador permite realizar un acción equivalente a la construcción for o a la función
fn:for-each()
.
- Funciones
XPath ha ampliado muchísimo el número de funciones definidas (véase XPath and XQuery Functions and Operator 3.1 o esta relación mejor sistematizada), lo que hace inviable enumerarlas en este documento todas. Sí, en cambio, añadiremos algunas muy socorridas a las que referimos al hablar de XPath 1.0.
Es importante también significar que las funciones incorporadas dentro del lenguaje se encuentran en el espacio de nombres fn, aunque este puede no expresarse. Por tanto, la función
concat()
, aunque puede escribirse así, esfn:concat()
.Otra novedad relativa a las funciones que incorpora XPath a partir de su versión 3.1 es el operador flecha (
=>
), que por su relación con las funciones incluimos bajo este epígrafe y no bajo el dedicado a los operadores.La nueva sintaxis alternativa es
argumento => funcion()
y equivale afuncion(argumento)
.Este operador no es más que azúcar sintáctico para mejorar la legibilidad del código, sobre todo cuando se usan funciones anidadas. Por ejemplo, imaginemos que nuestra intención es hacer un cifrado César de la cadena «hola»:
fn:codepoints-to-string(for $c in fn:string-to-codepoints('hola') return $c + 3)
Pues bien, gracias al operador flecha, podemos pasarle el argumento a la función de este modo:
for $c in fn:string-to-codepoints('hola') return $c + 3 => fn:codepoints-to-string()
Nota
En realidad, en esta expresión hemos seguido escribiendo el argumento pasado a
fn:string-to-codepoints()
del modo tradicionalEn cuanto a las nuevas funciones:
Hay muchísimas funciones matemáticas que permiten redondear, calcular razones trigonométricas, realizar operaciones complejas (algoritmos, exponenciación, etc.).
Dentro de las funciones para cadenas hay:
fn:string-join()
que permite concatenar cadenas usando una cadena como elemento de yuxtaposición. Es, por tanto, una generalización defn:concat()
[7]. Por ejemplo:fn:string-join(("a", "b", "c"), ".")
se evalúa a
a.b.c
.fn:upper-case()
yfn:lower-case()
permiten pasar a mayúsculas o minúsculas la cadena pasada como argumento.fn:matches()
permite comprobar si una cadena cumple con el patrón de una expresión regular. Por ejemplo, se evalúa como verdadera la expresión:fn:matches("abcd", "^a")
Admite un tercer argumento que permite indicar flags que modifican el comportamiento predeterminado. Por ejemplo, esta expresión se evalúa como verdadera:
fn:matches("abcd", "^A", "i")
fn:tokenize()
es la inversa afn:string-join()
: toma una cadena y la convierte en una secuencia partiéndola según la expresión regular proporcionada en el segundo argumento. Así, la expresión:fn:matches("a.b.c", "\.")
devuelve:
("a", "b", "c")
fn:codepoints-to-string()
yfn:string-to-codepoints()
que permiten respectivamente generar los caracteres Unicode que representan una cadena o viceversa. Por ejemplo, la expresiónfn:string-to-codepoints("Hola")
devuelve los números:
72 111 108 97
por lo que:
fn:codepoints-to-string((72, 111, 108, 97))
devuelve la cadena "Hola".
Nota
Ya hemos tratado los caracteres unicode al hablar de las entidades predefinidas de XML. Allí ya se explicó un modo de averiguar el código asociado a un carácter extraño (como la Ø danesa). Tenga presente, no obstante, que en XPath deberemos escribir el código hexadecimal como
0xd8
o directamente el código decimal equivalente (216
). Por tanto:fn:string-to-codepoints(0xd8) || "rsted"
devolverá el apellido del padre del electromagnetismo (Ørsted).
Nota
XPath no entiende el socorrido «
\n
» para escribir un salto de línea. Pero no es un problema, ya que ahora sabemos qué podemos escribirlo comofn:codepoints-to-string(10)
.Hay muchas funciones para los tipos relacionados con las fechas.
También hay funciones relacionadas con las secuencias como:
fn:empty()
ofn:exists()
que devuelven verdadero si la secuencia respectivamente está vacía o tiene algún elementofn:subsequence()
que devuelve un trozo de la secuencia pasada como argumento a partir de la posición que se le dé y la cantidad de elementos que se quieran obtener:fn:subsequence((0, 1, 2, 3 ,4), 2, 1)
Si no se expresa la longitud, se devuelve hasta el final de la secuencia original.
La expresión devuelve la secuencia
(1)
.fn:index-of()
devuelve las posiciones que ocupa en la secuencia el valor que se proporciona. Por ejemplo, la expresión:fn:index-of((1, 2, 1), 1)
devuelve
(1, 3)
.fn:reverse()
invierte los ítems de la secuencia.fn:distinct-values()
elimina los ítems repetidos.
Hay funciones relacionadas con los identificadores.
Funciones que permiten crear XML a partir de una cadena[8].
Y funciones relacionadas con programación funcional como por ejemplo
fn:for-each()
, que si se usa así:fn:for-each((1,2), function($e) { 2 * $e})
devuelve la secuencia
(2, 4)
.
- Construcciones
A la XPath se le ha dotado de una cuantas construcciones nuevas:
- Condicional
Permite usar la típica sentencia condicional if-then-else de la programación estructurada. Por ejemplo, ya que algunos profesores pueden no tener apelativo:
//profesor/(if (apelativo) then apelativo else nombre)
donde la condición debe escribirse siempre entre paréntesis.
- Iteración
Permite usar una estructura for típica también de la programación estructurada para iterar sobre los ítems de una secuencia:
for $nombre in //profesor/nombre return "Hola, " || $nombre
Ya hemos visto el operator de iteración que permite hacer lo mismo.
- Cuantificación
Tiene dos variantes, una con
some
que permite comprobar si algún ítem de una secuencia cumple una determinada condición. Por ejemplo, la expresión:some $nombre in //profesor/nombre satisfies $nombre = "Lucía"
será cierta si alguno de los profesores se llama Lucía. En realidad, habríamos conseguido lo mismo haciendo:
//profesor/nombre = "Lucía"
La otra variante se construye con
every
y comprueba si todos los ítem de la secuencia cumplen la condición. Por ejemplo, la expresión:every $nombre in //profesor/nombre satisfies $nombre != "Lucía"
será cierta si nadie se llama Lucía, lo cual también podríamos haber conseguido con esta expresión de XPath 1.0:
not(//profesor/nombre = "Lucía")
Estas expresiones, en realidad, sirven como azúcar sintáctico si no acabamos de entender cómo maneja la pluralidad XPath.
Ver también
La página altova.com tiene una guía bastante completa de XPath 3.1 que incluye bastantes ejemplos de uso. También es bastante revelador el primer capítulo del libro «XSLT Cookbook», que trata sobre XPath y que ofrece de balde la editorial O’Reilly.
Por hacer
Añadir explicaciones sobre mapas, arrays y consulta de documentos JSON con XPath.
3.1.5. Ejercicio resuelto¶
Dado el XML propuesto para representar el negocio de una cadena de restaurantes, determine las expresiones XPath apropiadas para:
Obtener todas las recetas disponibles.
Solución…
//receta
Obtener el nodo que representa la receta de nombre «ensalada».
Solución…
//receta[@nombre="ensalada"]
Obtener la carta del restaurante de identificador «re01».
Solución…
/cadena/restaurante[@id="re01"]/carta
Obtener el código postal del restaurante «El tragón feliz»-
Solución…
/cadena/restaurante[@nombre="El dragon feliz"]/domicilio/cp
Contar las recetas de las que dispone la cadena.
Solución…
count(//receta)
Contar cuántos ingredientes necesita la receta de la ensalada.
Solución…
count(//receta[@nombre="ensalada"]/ingrediente)
Contar cuántos ingredientes de «ensalada» se deben medir en gramos.
Solución…
count(//receta[@nombre="ensalada"]/ingrediente[@unidad="gramo"])
Obtener la carta del restaurante «El tragón feliz».
Solución…
/cadena/restaurante[@nombre="El tragón feliz"]/carta
Obtener el nombre de las recetas en las que se usa lechuga.
Solución…
//receta[ingrediente/@nombre = "lechuga"]/@nombre
Obtener el nombre de las recetas en las que no se usa lechuga.
Solución…
//receta[not(ingrediente/@nombre = "lechuga")]/@nombre
Obtener el identificador de los restaurantes en los que se sirven tapas.
Solución…
/cadena/restaurante[carta/plato/@tipo = "tapa" ]/@id
o mejor:
/cadena/restaurante[carta/plato[contains(@tipo, "tapa")]]/@id
Obtener el identificador de los restaurantes en los que no se sirven tapas.
Solución…
/cadena/restaurante[carta/plato[not(contains(@tipo, "tapa"))]]/@id
Obtener el nombre de los restaurantes con más de 10 platos en la carta.
Solución…
/cadena/restaurante[count(carta/plato) > 10]/@nombre
Obtener los nombres de las recetas que se sirven en «El tragón feliz».
Solución…
//receta[@id = /cadena/restaurante[@nombre="El tragón feliz"]/carta/plato/@ref]/@nombre
Obtener el nombre de los restaurantes situados en la provincia de Huelva (hágalo a partir del código postal)
Solución…
/cadena/restaurante[substring(domicilio/cp, 1, 2) = "21"]/@nombre
Obtener el nombre de las recetas que se sirven como tapa en algún restaurante.
Solución…
//receta[@id = /cadena/restaurante/carta/plato[contains(@tipo, "tapa")]/@ref]/@nombre
Obtener el nombre de los restaurantes que sirven bocadillos de anchoa.
Solución…
/cadena/restaurante[carta/plato/@ref = //receta[@nombre="bocadillo de anchoas"]/@id]/@nombre
Contar los restaurantes que usan lechuga en su carta.
Solución…
count(/cadena/restaurante[carta/plato/@ref = //receta[ingrediente/@nombre = "lechuga"]/@id])
Contar los restaurantes que no usan lechuga en su carta.
Solución…
count(/cadena/restaurante[not(carta/plato/@ref = //receta[ingrediente/@nombre = "lechuga"]/@id)])
¿Qué receta requiere más aceite? Suponga que el aceite siempre se mide en las mismas unidades.
Solución…
//receta[not(ingrediente[@nombre = "aceite"]/@cantidad < //receta/ingrediente[@nombre = "aceite"]/@cantidad)]
3.1.6. Ejercicios propuestos¶
Dada la solución propuesta para almacenar la información sobre las facturas de una empresa, determinar la expresión XPath que selecciona:
Las facturas que tenían algún descuento
//factura[not(@descuento) or @descuento = 0]
Las facturas anteriores al año en curso
XPath 1.0: no hay función que devuelva en año en curso.
//factura[substring(@fecha,1, 4) < 2023]
XPath 2/3:
//factura[fn:year-from-date(@fecha) < fn:current-date => fn:year-from-date()]
El número de facturas a las que se les aplicó un descuento mayor del 5%
count(//factura[@descuento > 5])
Las facturas con cinco productos diferentes.
//factura[count(item) = 5]
Las facturas en las que se facturó el producto «p01».
//factura[item/@producto = "p01"]
Los clientes que viven en Isla Cristina.
//cliente[direccion/poblacion = "Isla Cristina"]
Los clientes que viven en la provincia de Huelva (el código postal puede servir para ello).
//cliente[substring(direccion/cp, 1, 2) = 21]
Los nombres de los productos con iva superreducido.
//producto[@iva = "superreducido"]/@nombre
El número de productos que tienen un iva superreducido.
count(//producto[@iva = "superreducido"])
Todas las facturas en las que el número total de artículos comprados fuese de más de 10. Para que tenga sentido esta pregunta, hágase la suposición de que todos los productos que se venden, se venden por unidades.
//factura[sum(item/@cantidad) > 10])
El número de facturas emitidas en noviembre de 2012.
count(//factura[substring(@fecha, 1, 7) = "2012-11"])
El número de facturas que sólo contienen un producto.
count(//factura[count(item) = 1])
Los productos que se han vendido al menos una vez.
//producto[@codigo = //item/@producto]
Las facturas a nombre de Perico de los Palotes.
//factura[@cliente = //cliente[nombre = "Perico de los Palotes"]/@id]
Los productos de la factura con identificador «f01» cuyo iva es normal.
//producto[@iva = "normal"][@codigo = //factura[@codigo = "f01"]/item/@producto]
Todas las facturas en las que se facturaron altramuces.
//factura[item/@producto = //producto[@nombre = "altramuces"]/@codigo]
El total de sandías vendidas.
sum(//factura/item[@producto = //producto[@nombre = "sandía"]/@codigo]/@cantidad)
Las facturas emitidas a residentes en Isla Cristina.
//factura[@cliente = //cliente[direccion/poblacion = "Isla Cristina"]/@id]
Las facturas sin productos gravados con iva normal.
//factura[not(item[@producto = //producto[@iva = "normal"]/@codigo])]
Las facturas cuyos productos están todos gravados con iva normal.
//factura[not(item[@producto != //producto[@iva = "normal"]/@codigo])]
[XPath 2/3] El importe total de la primera factura sin tener en cuenta el descuento.
sum(//factura[1]/item/(@precio * @cantidad))
[XPath 2/3] El importe total de cada factura sin tener en cuenta el descuento.
//factura/sum(item/(@precio * @cantidad))
[XPath 2/3] El importe total de cada factura.
//factura/((1 - (if (@descuento) then @descuento else 0) div 100)*sum(item/(@precio * @cantidad)))
Dada la solución propuesta para almacenar la información sobre los libros y préstamos en una biblioteca, determinar la expresión XPath que devuelve:
El número de socios de la biblioteca
count(//lector)
La dirección del socio que se llama Perico de los Palotes.
//lector[nombre="Perico de los Palotes"]/direccion
Los libros cuyo año de edición sea 1985.
//libro[año=1985]
Los libros de la editorial Alfaguara.
//libro[editorial="Alfaguara"]
El número total de ejemplares que hay en la biblioteca.
count(//ejemplar)
Todos los libros cuyo autor sea «Miguel de Cervantes Saavedra».
//libro[autor="Miguel de Cervantes Saavedra"]
El número de ejemplares del libro con ISBN 00-9081-234.
count(//libro[isbn="00-9081-234"]//ejemplar)
El número total de ejemplares actualmente en préstamo.
count(//prestamo[not(@entrega)])
El número total de ejemplares que no se encuentran prestados.
count(//ejemplar) - count(//prestamo[not(@entrega)])
Los libros que se han prestado al socio l01.
count(//ejemplar) - count(//prestamo[not(@entrega)])
La cantidad de ejemplares de libros de la editorial Castalia.
count(//libro[editorial="Castalia"]//ejemplar)
Los identificadores de los socios que tienen algún libro en préstamo.
//lector[@registro = //prestamo[not(@entrega)]/@lector]/@registro
Pero si usamos XPath 2/3 podemos hacer:
fn:distinct-values(//prestamo[not(@entrega)]/@lector)
Los libros que no están prestados al socio l01.
//libro[not(.//@codigo = //prestamo[not(@entrega)][@lector = "l01"]/@ejemplar)]
Los libros que se han prestado al socio l01, pero no al socio l02.
//libro[.//@codigo = //prestamo[@lector = "l01"]/@ejemplar][not(.//@codigo = //prestamo[@lector = "l02"]/@ejemplar)]
Los ejemplares aún disponibles (no prestados) de Don Quijote de La Mancha.
//libro[nombre="Don Quijote de la Mancha"]//ejemplar[not(@codigo = //prestamo[not(@entrega)]/@ejemplar)]
Los libros de los que quedan menos de dos ejemplares disponibles en la biblioteca.
//libro[count(.//ejemplar[not(@codigo = //prestamo[not(@entrega)]/@ejemplar)]) < 2]
Los libros de los que no se ha prestado nunca ningún ejemplar.
//libro[not(.//ejemplar/@codigo = //prestamo/@ejemplar))]
El número de ejemplares prestados al socio Perico de los Palotes.
count(//prestamo[not(@entrega)][@lector = //lector[nombre="Perico de los Palotes"]/@registro])
Los libros que tienen todos los ejemplares prestados
//libro[count(.//ejemplar) = count(.//ejemplar[@codigo = //prestamo[not(@entrega)]/@ejemplar])]
Los libros que tienen más ejemplares prestados que en la biblioteca.
//libro[2*count(.//ejemplar[@codigo = //prestamo[not(@entrega)]/@ejemplar]) > count(.//ejemplar)]
Dada la solución propuesta para almacenar la información sobre los coches que vende un concesionario, determinar la expresión XPath que devuelve:
La marca de coches que vende el concesionario.
/concesionario/@marca
El nombre de los modelos que vende el concesionario.
//modelo/@nombre
Los modelos de cinco plazas.
//modelo[plazas=5]
Los modelos que tienen una cilindrada mayor a 300.
//modelo[cilindrada>300]
El modelo León.
//modelo[@nombre="León"]
El último modelo del archivo
//modelo[last()]
El número de modelos que vende el concesionario.
count(//modelo)
El número de modelos que no son de cinco plazas
count(//modelo[plazas!=5])
El cliente de identificador c01.
//cliente[@id='p01']
El número de clientes.
count(//cliente)
Los coches que ha reservado el cliente c01
//coche[@reservado="c01"]
Los coches nuevos
//coche[@tipo="nuevo"]
Los coches reservados.
//coche[@reservado]
El número de coches León.
count(//coche[@modelo = //modelo[@nombre="León"]/@id])
Los ibizas reservados
//coche[@modelo = //modelo[@nombre="Ibiza"]/@id][@reservado]
Los coches de segunda mano reservados.
//coche[@tipo="2mano"][@reservado]
Los coches reservados que sean ibiza o león
//coche[@modelo = //modelo[@nombre="León" or @nombre="Ibiza"]/@id][@reservado]
Los coches nuevos no reservados aún.
//coche[@tipo="nuevo"][not(@reservado)]
El número de coches del modelo León
count(//coche[@modelo = //modelo[@nombre="León"]/@id])
Los coches reservados a los habitantes de Villabajo.
//coche[@reservado = //cliente[.//poblacion="Villaabajo"]/@id]
Dada la solución propuesta para almacenar la información sobre los parques nacionales, determinar la expresión XPath que devuelve:
El número de especies vegetales.
count(//especie[@tipo = "flora"])
Las especies vegetales con un peligro alto de extinción.
//especie[@tipo = "flora"][@peligro = "alto"]
El número de especies animales en peligro bajo de extinción.
//especie[@tipo = "fauna"][@peligro = "bajo"]
El número de especies en peligro alto de extinción.
count(//especie[@peligro = "alto"])
El nombre común de las especies vegetales en peligro alto de extinción
//especie[@tipo = "flora"][@peligro = "alto"]/comun
El peligro de extinción del animal de nombre común «Lince ibérico».
//especie[comun = "Lince ibérico"]/@peligro
El número de parques nacionales.
count(//parque)
El número de parques en Canarias.
count(//parque[@ca = "Canarias"])
Los parques de la provincia de Huelva.
count(//parque[@provincia = "Huelva"])
Los parques con una extensión mayor de 30.000 hectáreas.
//parque[@extension > 30000]
El total de presupuestos.
sum(//parque/@presupuesto)
El parque de Timanfaya.
//parque[@nombre = "Timanfaya"]
El total de ejemplares de Lince ibérico que hay en los parques.
sum(//parque/especimen[@ref = //especie[comun = "Lince ibérico"]/@id]/@ejemplares)
El número de especies de interés en Doñana.
count(//parque[@nombre = "Doñana"]/especimen)
El número de ejemplares animales de interés en Doñana.
sum(//parque[@nombre = "Doñana"]/especimen[@ref = //especie[@tipo = "fauna"]/@id]/@ejemplares)
Las especies animales de interés del parque de Garajonay.
//especie[@tipo = "fauna"][@id = //parque[@nombre = "Garajonay"]/especimen/@ref]
Las comunidades autónomas con parques nacionales.
//parque/@ca
Los parques nacionales con menos de veinte especies de interés
//parque/[count(especimen) < 20]
El número de parques nacionales que sólo tiene como especies de interés animales.
count(//parque[not(especimen[@ref = //especie[@tipo = "flora"]/@id])])
Los parques donde hay más especies animales de interés que vegetales
//parque[count(especimen[@ref = //especie[@tipo = "fauna"]/@id]) > count(especimen[@ref = //especie[@tipo = "flora"]/@id])]
Dada la solución propuesta para almacenar la información sobre los habitantes de un municipio, determinar la expresión XPath que devuelve:
El número de habitantes del municipio.
count(//persona)
Los varones del municipio.
//persona[@sexo="hombre"]
Las mujeres del municipio.
//persona[@sexo="mujer"]
El número de habitantes de los que se tiene registrado el padre.
//persona[@padre]
El número de habitantes de los que se tienen registrados padre y madre.
//persona[@padre][@madre]
El número de habitantes de los que no se tienen registrados padre ni madre.
//persona[not(@padre)][not(@madre)]
Los nombres de los habitantes de los que se tiene registrada la madre, pero no el padre.
//persona[not(@padre)][@madre]/nombre
Los varones cazorleños de los que se tiene registrado el padre.
//persona[@sexo = "hombre"][@padre][origen = "Cazorla"]
Los habitantes que se llaman Perico.
//persona[nombre = "Perico"]
El listado de identificadores de personas que son padre.
//persona/@padre
El listado de identificadores de personas que son madre.
//persona/@madre
Los habitantes de padre registrado que han nacido en Cazorla.
//persona[@padre][origen = "Cazorla"]
Los datos de los habitantes cuyo padre tienen identificador p01.
//persona[@padre = "p01"]
Los habitantes que son padre de alguien.
//persona[@id = //persona/@padre]
Las habitantes que son madre de alguien.
//persona[@id = //persona/@madre]
Los habitantes que son padre de alguien y, a la vez, tienen padre registrado.
//persona[@id = //persona/@padre][@padre]
¿Hay alguna persona que haya nacido en Villaconejos.
boolean(//persona[origen = "Villaconejos"])
El listado de localidades en las que hayan nacido los habitantes que son padre.
//persona[@id = //persona/@padre]/origen
Nota
En XPath 2/3 podríamos aññadir
fn:distinct-values()
para evitar las repeticiones.Los varones que no son padre.
//persona[not(@id = //persona/@padre)][@sexo = "hombre"]
Las mujeres que no son madre de ninguna niña.
//persona[not(@id = //persona[@sexo = "mujer"]/@madre)][@sexo = "mujer"]
Dada la solución propuesta para almacenar la información de un dibujo técnico en dos dimensiones, determinar la expresión XPath que selecciona:
Todas las circunferencias.
//circunferencia
La cuarta recta.
//recta[4]
Los centros de las circunferencias.
//circunferencia/@x | //cicunferencia/@y
El último punto.
//punto[last()]
El rectángulo con identificador «e32».
//rectangulo[@id = 'e32']
Todos los radios de las circunferencias.
//circunferencia/@r
El radio de la circunferencia de identificador «e2».
//circunferencia[@id = 'e32']/@r
El radio de la quinta circunferencia.
//circunferencia[5]/@r
Todos los radios de circunferencia mayores de 10.
//circunferencia/@r[. > 10]
Las unidades en las que se han expresado todos los números.
/grafico/@unidad
El identificador de la quinta entidad de dibujo.
/grafico/*[5]/@id
Las rectas cuyo punto inicial sea el centro de coordenadas (0,0).
//recta[@x1 = 0][@y1 = 0]
Las rectas cuyo punto inicial esté en el segundo cuadrante.
//recta[@x1 < 0][@y1 > 0]
Los puntos que se encuentren en el cuarto cuadrante.
//recta[@x > 0][@y < 0]
El cuarto de los rectángulos
//rectangulo[4]
Todas las coordenadas y de todas las rectas.
//recta/@y
Todas las coordenadas y de todas las entidades de dibujo.
/grafico/*/@y
Los puntos finales de todos los rectángulos.
//rectangulo/@x2 | //rectangulo/@y2
Todas las rectas cuya coordenada x del punto inicial sea mayor que la coordenada x del punto final.
//recta[@x1 > @x2]
¿Cuántos puntos hay en total?
count(//punto)
¿Cuántos puntos hay en el primer cuadrante?
count(//punto[@x > 0][@y > 0])
¿Cuántas circunferencias tienen radio mayor de 20?
count(//circunferencia[@r > 20])
¿Cuántas entidades de dibujo hay en total?
count(/grafico/*)
¿Cuántas coordenadas x hay definidas en total?
count(/grafico/*/@x | /grafico/*/@x1 | /grafico/*/@x2)
Todos los rectángulos cuyo lado horizontal sea el doble de largo que el vertical.
//rectangulo[(@x2-@x1)*(@x2-@x1) = 4(@y2-@y1)*(@y2-@y1)]
Notas al pie