I.E.S. Suárez de Figueroa @vanza C.F.G.S. Administración de Sistemas Informáticos en Red Módulo "Lenguaje de Marcas y Sistemas de Gestión de la Información" Unidad 5. Conversión y adaptación de documentos XML. 1. Técnicas de acceso y transformación de documentos XML. Como ya sabemos, el lenguaje de marcas XML aplica una estructura a un conjunto de datos, generando un documento estructurado; por otra parte los lenguajes de marcas HTML y XHTML generan documentos con formato. Existen otros lenguajes de marcas que permiten acceder a la información de documentos XML y aplicar un formato claro y atractivo para publicarlo en páginas web. Además, también se ofrece la posibilidad de generar informes en formato PDF. El lenguaje de marcas XPath permite acceder a los datos de un documento XML, mientras que el lenguaje de marcas XSLT permite aplicar transformaciones a los documentos XML. Los documentos XML son documentos de texto con etiquetas que contienen exclusivamente información con una estructura, sin entrar en detalles del formato. Esto implica la necesidad de algún medio para expresar el acceso y la transformación de un documento XML, para que una persona pueda utilizar directamente los datos para leer, imprimir, etc. Por tanto, a partir de un documento XML podremos obtener otro documento XML, un documento HTML (o XHTML), un documento de texto, e incluso un documento PDF. Las tecnologías que entran en juego en el acceso y transformación de documentos son: XPath: permite el acceso a los diversos componentes de un documento XML XSLT: permite definir el modo de transformar un documento XML en otro. XSL-FO: Se utiliza para transformar XML en un formato legible e imprimible por una persona, por ejemplo en un documento PDF. 2. XPath. XPath es un lenguaje que permite separar la información de los documentos XML de las etiquetas de los mismos. XPath permite acceder (o referenciar) a las partes o nodos de un fichero XML; o dicho de otro modo, XPath permite identificar y seleccionar elementos o nodos del documento XML. Una expresión XPath se aplica sobre una estructura de árbol correspondiente a la jerarquía de los datos de un documento XML, y devuelve todo lo que encaja con esa expresión. Es un estándar aprobado por el W3C, que nos permite acceder a partes de un documento XML basándonos en las relaciones de parentesco entre los nodos (elementos) del documento. Su notación es similar a las de las rutas de los ficheros. Se puede decir que XPath es un lenguaje XML, pero realmente lo que hace es basarse en la estructura jerárquica de los documentos XML. Inicialmente se creó para utilizarlo con XSLT, pero en la actualidad se utiliza también con XML Schema, Xquery, Xlink, Xpointer, Xforms, etc. En https://www.w3.org/TR/XPath/ puedes encontrar el estándar de XPath que aprobó el W3C el 19 de Noviembre de 1999 y sobre el que sigue aplicando revisiones en la actualidad. 2.1. Términos básicos. Existen algunos términos que se deben conocerse para utilizar XPath. XPath considera un documento XML (con sus elementos, atributos y datos) como un árbol de nodos. Una expresión XPath es una cadena de texto que representa un recorrido en el árbol del documento, que está formado por nodos. Veamos los distintos tipos de nodos: Nodo raíz, es el nodo que contiene al ejemplar o elemento raíz del fichero XML; no es el ejemplar, sino el que lo contiene; el ejemplar o elemento raíz del XML está por debajo del nodo raíz del XPath. Se identifica por “/”. Por ejemplo, si un documento tiene el ejemplar o elemento raíz <alumno>, el nodo raíz es / y para acceder al ejemplar se forma la expresión /alumno de XPath. Se puede decir por tanto que el elemento padre del elemento raíz es el nodo raíz, por lo que todos los elementos de un XML tiene elemento padre, incluido el elemento raíz. Nodos elemento, son cada uno de los elementos del documento XML. Todos ellos tienen un elemento padre, que es el elemento al que pertenece; el padre del elemento raíz, es decir del ejemplar, es el nodo raíz del documento. Por ejemplo: <alumno> <nombre>Juan</nombre> <fecha_nac> <dia>21</dia> <mes>11</mes> <anio>1990</anio> </fecha_nac> </alumno> Nodo raíz: / Es el padre del elemento alumno Nodo alumno: /alumno Es el padre de los elementos nombre y fecha_nac Nodo nombre: /alumno/nombre Nodo fecha_nac: /alumno/fecha_nac Como vemos, para separar un nodo (alumno) del nodo hijo (nombre) se usa la / (el mismo símbolo que se utiliza para el nodo raíz). Nodos texto, son los propios datos del documento XML. Por tanto, estos nodos texto no tienen hijos. Por ejemplo, en el código anterior son nodos texto "Juan", "21", "11" y "1990". Nodos atributo, no se consideran hijos del elemento al que están asociados sino etiquetas añadidas al nodo elemento. Nodos de comentario, son los nodos que se generan para los elementos con comentarios del documento XML. Nodo actual, es el nodo sobre el que se evalúa una expresión XPath. Nodo contexto, cada expresión XPath está formada por subexpresiones que se van evaluando antes de resolver la siguiente. El conjunto de nodos obtenido tras evaluar una expresión y que se utiliza para evaluar la siguiente es el nuevo contexto. Tamaño del contexto, es el número de nodos que se están evaluando en un momento dado en una expresión XPath. Las expresiones XPath nos permiten obtener cuatro tipos de resultados diferentes: Un conjunto de nodos. Un valor booleano o lógico (valores verdadero o falso). Un valor numérico, pero no un conjunto de valores numéricos. Una cadena de caracteres, pero no un conjunto de cadenas. Una ruta de localización se corresponde con la ruta que hay que seguir en un árbol para localizar un nodo. Las rutas de localización siempre devuelven el conjunto de nodos del árbol XML sobre el que se aplican que cumplen una condición XPath dada. Dicho conjunto podría estar vacío. 2.2. Selección de nodos. Para comprobar los resultados de las sentencias o expresiones de XPath se puede utilizar el programa "XML Copy Editor". Teniendo el fichero .xml abierto, entrando en la opción del menú superior "XML - Evaluar XPath" (o directamente pulsando la tecla F9) se abre una pequeña ventana donde se escribe la expresión o sentencia XPath; al ejecutarla se muestra el resultado. En los ejemplos posteriores usaremos el siguiente fichero XML de agenda: <?xml version="1.0" encoding="iso-8859-1" standalone="yes"?> <agenda> <propietario> <nombre>Ana</nombre> <apellidos>Pinto Ramos</apellidos> </propietario> <contactos> <persona id="p01"> <identificadores> <nombre>Maribel</nombre> <apellidos>Lago Santana</apellidos> </identificadores> <direccion> <calle>Mayor 22, 2B</calle> <localidad>Badajoz</localidad> <cp>06002</cp> </direccion> <telefonos> <movil>924111111</movil> </telefonos> </persona> <persona id="p02"> <identificadores> <nombre>Roberto</nombre> <apellidos>Ramos Juan</apellidos> </identificadores> <direccion> <calle>La Central 41, 4C</calle> <localidad>Zafra</localidad> <cp>06300</cp> </direccion> <telefonos> <movil>666222222</movil> <casa>924222222</casa> </telefonos> </persona> <persona id="p03"> <identificadores> <nombre>Juan</nombre> <apellidos>Lima Ruz</apellidos> </identificadores> <direccion> <calle>Huertos 18</calle> <localidad>Badajoz</localidad> <cp>06004</cp> </direccion> <telefonos> <movil>666333333</movil> <casa>924333333</casa> <trabajo>924343434</trabajo> </telefonos> </persona> </contactos> </agenda> Algunas notaciones en XPath son: - La barra / (es decir, la barra que está en la tecla del 7) es el nodo raíz; como ya hemos indicado antes, este nodo raíz es el padre del ejemplar del XML, o lo que es lo mismo, el ejemplar es hijo de la barra /. Por ejemplo: Ejemplar XML: Notación XPath: <agenda> /agenda Como vemos, para referenciar (acceder) el elemento raíz o ejemplar del XML se usa el nodo raíz (la barra /) y el nombre del ejemplar. Si probamos esa expresión en XML Copy Editor, teniendo el documento .xml abierto que contiene la agenda, comprobaremos que el resultado es la agenda con todos sus elementos o nodos hijos; es decir, muestra todo el documento .xml, queda seleccionado el documento completo. - Para referenciar o acceder a un elemento del XML se debe comenzar por el nodo raíz hasta llegar al elemento en cuestión, según el árbol del fichero XML; por ejemplo: XPath para el nodo "propietario": /agenda/propietario Esta expresión aplicada al XML anterior selecciona lo siguiente: <propietario> <nombre>Ana</nombre> <apellidos>Pinto Ramos</apellidos> </propietario> Observamos que cuando se selecciona un nodo, también se incluye su contenido (sus nodos hijos y atributos); en este caso queda seleccionado todo lo que contiene "propietario". XPath para el nodo "persona": /agenda/contactos/persona Esta expresión selecciona todos los nodos "persona" (las tres personas) incluyendo todo su contenido. Como vemos, para acceder a un nodo se escriben los sucesivos elementos o nodos separados por la barra (la misma que se usa para indicar nodo raíz), hasta llegar al elemento en cuestión; se comienza por el nodo raíz (la barra / inicial) seguido del ejemplar del XML (agenda en este caso). Se puede deducir que la / se usa para acceder al hijo de un nodo; "persona" es hijo del nodo "contactos", por eso se usa "contactos/persona" para acceder a "persona". También existe la notación child:: en lugar de la barra (realmente es /child::), pero lógicamente es más sencillo usar sólo la barra: /agenda/contactos es equivalente a /agenda/child::contactos - Para referenciar o acceder a un atributo de un elemento se utiliza la arroba @, después de la barra / por ejemplo: XPath: /agenda/contactos/persona/@id Esta expresión realiza la siguiente selección: id="p01" id="p02" id="p03" Es decir, los identificadores id de las tres personas de la agenda. La @ se corresponde con attribute:: por lo que la expresión anterior podría quedar: XPath: /agenda/contactos/persona/attribute::id Lógicamente es más sencillo y rápido usar la @. - Las dos barras // permiten seleccionar nodos de cualquier parte del documento, sin importar donde se encuentran, es decir no tiene en cuenta cual es el nodo padre. Equivale a descendant:: (realmente equivale a /descendant::) por lo que es lo mismo escribir /descendant:: que // pero lógicamente es más sencillo //. XPath: //nombre o /descendant::nombre Esta expresión selecciona: <nombre>Ana</nombre> <nombre>Maribel</nombre> <nombre>Roberto</nombre> <nombre>Juan</nombre> Es decir, selecciona tanto el nombre del "propietario" como los nombres de los "contactos"; no importa de quién sea hijo el "nombre", selecciona todos los nodos "nombre". Para hacer lo mismo, podría usarse también, aunque la expresión queda más larga: XPath: /agenda//nombre o /agenda/descendant::nombre Con esa expresión se seleccionan todos los nodos nombre de la agenda, por tanto también quedan seleccionados los 4 nombres anteriores (propietario y 3 contactos). Otra expresión puede ser: XPath: /agenda/contactos//nombre Esta sentencia selecciona: o /agenda/contactos/descendant::nombre <nombre>Maribel</nombre> <nombre>Roberto</nombre> <nombre>Juan</nombre> En este caso, a partir del nodo "contactos" selecciona todas las etiquetas "nombre", independientemente de dónde se encuentren; aunque en este caso las tres "nombre" se encuentran en la misma posición (dentro de "identificadores"). Si en lugar de las dos barras // ponemos sólo una en la expresión anterior, quedaría: XPath: /agenda/contactos/nombre Esta sentencia no selecciona nada (conjunto vacío), porque el nodo nombre no es hijo de contactos. Si se usa sólo una barra, hay que poner la ruta completa, es decir: XPath: /agenda/contactos/persona/identificadores/nombre Con los atributos también puede utilizarse la doble barra. Por ejemplo: XPath: //@id Esta expresión selecciona: id="p01" id="p02" id="p03" En el caso de los atributos no se pueden convertir las dos barras // en /descendant:: porque un atributo no se considera hijo del elemento al que pertenece o el elemento no se considera padre del atributo. Es decir, quedan seleccionados todos los atributos "id" independientemente de donde se encuentren en el documento, aunque en este caso todos se encuentran dentro de "persona". También podría usarse para lo mismo, aunque queda una expresión más larga: XPath: /agenda/contactos//@id Esta expresión selecciona los atributos id (y sus valores) que estén dentro de los contactos de la agenda, independientemente de la posición de esos atributos (siempre que estén dentro de contactos). - El punto . selecciona el nodo actual. /agenda/contactos/. Selecciona "contactos" y todo su contenido. Obviamente selecciona lo mismo que /agenda/contactos sin la barra final y el punto. - Dos puntos seguidos .. selecciona el nodo padre. /agenda/contactos/.. Selecciona "agenda" y todo su contenido, ya que desde "contactos" se va al nodo padre (por los dos puntos .. ), y el nodo padre de "contactos" es "agenda". El símbolo .. es equivalente a parent:: por lo que la expresión anterior podría ser /agenda/contactos/parent:: pero esta expresión da error porque después de parent:: siempre hay que poner algún elemento, por lo que realmente se escribiría en este caso /agenda/contactos/parent::* para que seleccione lo mismo que /agenda/contactos/.. aunque obviamente es más sencillo y rápido usar .. en cualquier sentencia XPath. 2.3. Predicados. Los predicados se utilizan para encontrar nodos específicos o que contienen un valor específico. Se escriben entre corchetes [ ] y aplican filtros a las selecciones, para quedarnos con un conjunto de nodos más restringido. Veamos algunos ejemplos: /agenda/contactos/persona[1] Selecciona el primer nodo "persona" (y todo su contenido) dentro de "contactos". /agenda/contactos/persona[3] Selecciona el tercer nodo "persona" (y todo su contenido) dentro de "contactos". //telefonos[2] Selecciona el segundo nodo "telefonos" (y su contenido) de todo el documento, independientemente de donde esté ese nodo. //telefonos[2]/movil Selecciona el movil del segundo nodo "telefonos". /agenda/contactos/persona[2]/direccion/calle Selecciona la calle (que está dentro de direccion) de la segunda persona de la agenda. /agenda/contactos/persona[@id = "p02"] Selecciona el nodo "persona" (y todo su contenido) cuyo atributo "id" valga "p02", dentro de "contactos". Lógicamente, si hay varias personas cuyo atributo "id" vale "p02", serán seleccionadas todas esas personas. //persona[@id = "p02"] Selecciona el nodo "persona" cuyo atributo "id" valga "p02", independientemente de donde se encuentre el nodo "persona". /agenda/contactos/persona[@id] Selecciona los nodos "persona" (dentro de "contactos") que tengan atributo "id" (independientemente del valor del "id"). No se muestran los "id", sino los nodos "persona" completos. Como vemos, si entre corchetes sólo se pone el atributo, sin compararlo con ningún valor concreto, se refiere a que exista el atributo. /agenda/contactos/persona/telefonos[casa] Selecciona los nodos "telefonos" (y su contenido) que tengan nodo "casa". No se muestran los nodos "casa", sino los nodos "telefonos" completos. Como vemos, si entre corchetes sólo se pone el elemento, sin compararlo con ningún valor concreto, se refiere a que exista el elemento. Si lo que se quiere seleccionar son las personas (no sólo los nodos "telefonos"), sería como sigue. /agenda/contactos/persona/telefonos[casa]/.. Vemos que primero se aplica la restricción de que tengan nodo "casa" y después con los dos puntos .. nos situamos en el nodo padre de "telefonos", que es el nodo "persona"; de ese modo se seleccionan las personas (no sólo los teléfonos) que tengan nodo "casa". //telefonos[casa]/../@id Selecciona los identificadores de los que tienen telefono en casa. Hay que poner .. (nodo padre) porque para acceder al "id" debemos subir un nivel (en el árbol de nodos) desde el nodo "telefonos", ya que "id" pertenece al nodo "persona", y éste es el nodo padre del nodo "telefonos". Ese predicado también podría hacerse con //casa/../../@id que no es un predicado (no hay corchetes), simplemente es una expresión XPath; como vemos, se suben dos niveles en el árbol de nodos (dos veces el símbolo .. ), ya que "id" pertenece al nodo "persona" y éste está dos niveles por encima del nodo "casa". Si ponemos sólo //casa se seleccionan los nodos "casa", pero al completarla con //casa/../../@id se seleccionan los "id" que corresponden a esos nodos "casa". /agenda/contactos/persona/direccion[cp]/../telefonos/movil Selecciona el movil de las personas cuya direccion tenga cp. /agenda/contactos/persona/direccion[localidad = "Badajoz"] Selecciona las direcciones que tengan su localidad en Badajoz. Si queremos seleccionar las personas (no las direcciones) de Badajoz, sería como sigue. /agenda/contactos/persona/direccion[localidad = "Badajoz"]/.. Al usar los dos puntos .. nos situamos en el nodo padre de "direccion", es decir en "persona"; así se seleccionan los nodos "persona" de los que cumplan la condición indicada. /agenda/contactos/persona/direccion[localidad = "Badajoz"]/../identificadores/nombre Selecciona el nombre (que está dentro de identificadores) de las personas cuya localidad sea "Badajoz". /agenda/contactos/persona/direccion[cp > "06100"] Selecciona las direcciones cuyo cp sea mayor que 06100. También se puede poner sin comillas (considera cp como numérico, en lugar de cadena), es decir: /agenda/contactos/persona/direccion[cp > 06100] //direccion[cp > 06002]/cp Selecciona los cp de las direcciones cuyo cp sea mayor que 06002. En XPath un dato se considera numérico cuando está formado sólo por dígitos, el punto decimal (no la coma) y el signo - (para los negativos); si tiene alguna letra u otros caracteres, el dato se considera cadena. Los operadores que se puede utilizar con datos numéricos son: = != < > <= >= igual a distinto de menor que mayor que menor o igual que mayor o igual que Los operadores que se puede utilizar con datos no numéricos (cadenas de caracteres) son: = != igual a distinto de /agenda/contactos/persona/direccion[cp >= "06"] Selecciona las direcciones cuyo cp sea mayor o igual que "06". //persona/direccion[cp >= 06004]/localidad Selecciona la localidad de las direcciones cuyo cp sea mayor o igual que 06004. /agenda/contactos/persona/direccion[localidad != "Badajoz"]/calle Selecciona la calle de las direcciones cuya localidad no sea "Badajoz". //persona[@id != "p01"]/telefonos Selecciona los teléfonos de las personas cuyo id no sea "p01". El atributo "id" no es numérico (contiene letras). 2.4. Comodines. Existen algunos caracteres, llamados comodines, con un uso especial: - El asterisco * significa "cualquier nodo". /agenda Selecciona el nodo agenda y todo su contenido; la etiqueta "agenda" también queda incluida en la selección. /agenda/* Selecciona los nodos hijos (cualquiera que sea) de la agenda; la etiqueta "agenda" no queda incluida en la selección. //* Selecciona todos los nodos del documento, estén donde estén. //contactos/* Selecciona los hijos de "contactos" de todo el documento. La etiqueta "contactos" no queda seleccionada, sólo sus nodos hijos. //persona[@id = "p02"] Selecciona todos los nodos de la persona contacto cuyo atributo id valga "p02"; el nodo persona también queda seleccionado. Pero en el caso siguiente: //persona[@id="p02"]/* Selecciona los nodos de la persona contacto cuyo atributo id valga "p02", pero el nodo persona no queda seleccionado (sus nodos hijos sí, es decir quedan seleccionados los nodos identificadores, direccion y telefonos). //persona[@id="p02"]/*/* Selecciona los nodos de la persona cuyo atributo id valga "p02", pero el nodo persona no queda seleccionado, ni tampoco sus nodos hijos (sus nodos nietos sí, es decir los nodos nombre, apellidos, calle, localidad, cp, movil, casa). //movil/* No selecciona nada, ya que dentro del nodo "movil" no hay otros nodos, sólo datos. - La arroba con asterisco @* significa "cualquier atributo". //persona[@*] Selecciona las personas que tengan un atributo cualquiera. //persona[@* = "p02"] Selecciona los nodos "persona" que tengan algún atributo (uno cualquiera) con valor "p02" /agenda/contactos/persona/@* Selecciona todos los atributos de las personas de la agenda. /agenda/contactos/persona[@* = "p01"]/@* Selecciona todos los atributos de las personas de la agenda que tengan algún atributo con valor "p01". 2.5. Funciones. Algunas funciones incorporadas en el lenguaje XPath son: - boolean(): aplicada a un conjunto de nodos (a una expresión XPath) devuelve valor verdadero (un 1) si hay nodos en el conjunto y valor falso (un 0) si no hay nodos en el conjunto. Dicho de otro modo, si la expresión XPath selecciona algo (muestra algo), el boolean de esa expresión dará 1; si no selecciona nada dará 0. Por tanto, la función boolean devuelve un valor booleano (verdadero o falso, es decir 0 o 1). boolean(/agenda/contactos) Devuelve el valor 1 (verdadero), ya que "contactos" de agenda tiene nodos. La expresión /agenda/contactos selecciona algo. boolean(/agenda/persona) Devuelve el valor 0 (falso), ya que dentro de agenda no está el nodo "persona"; al ser conjunto vacío boolean dará falso. Si ejecutamos la expresión /agenda/persona da el mensaje (en XML Copy Editor) "Ningún nodo correspondiente encontrado", es decir que no seleccionada nada. Para saber si existe o no en la agenda alguna direccion con localidad "Zafra", podemos utilizar: boolean(//direccion[localidad="Zafra"]) Si hay alguna direccion de Zafra devuelve valor 1; en caso contrario el 0. - not(): cambia el valor booleano que se le indique dentro de los paréntesis; el verdadero lo cambia a falso; el falso lo cambia a verdadero; por tanto, devuelve un valor booleano. not ( boolean(//direccion[localidad="Zafra"]) ) Devuelve valor 0 (falso) si hay alguna dirección de Zafra, y el valor 1 (verdadero) en caso contrario. La función not() también se puede usar del modo siguiente. //telefonos[not(trabajo)] Selecciona los nodos "telefonos" que no tengan entre sus hijos el nodo "trabajo". Sería lo contrario de //telefonos[trabajo]. //telefonos[not(trabajo)]/.. Selecciona las personas que no tengan entre sus telefonos el del trabajo. No selecciona sólo los telefonos, sino las personas, porque se sube al nodo padre al poner los dos puntos .. //direccion[not(localidad = "Zafra")] Selecciona los nodos direccion cuya localidad no sea "Zafra"; por tanto, esa expresión coincide con: //direccion[localidad != "Zafra"] - true(): devuelve el valor booleano verdadero (valor 1). - false(): devuelve el valor booleano falso (valor 0). - count(): cuenta el número de nodos; esta función devuelve un valor numérico. count(/agenda) Devuelve el valor 1 porque nodo "agenda" sólo hay uno en el documento XML. count(/agenda/contactos) Devuelve el valor 1 porque sólo hay un nodo "contactos" en la agenda. count(/agenda/contactos/persona) Devuelve el valor 3 porque dentro de contactos hay tres nodos "persona"; la agenda tiene 3 personas. Es equivalente a count(//persona). count(//nombre) Devuelve el valor 4 porque hay cuatro nodos "nombre" en toda la agenda; un nombre del propietario y otros tres nombres de las personas de la agenda. count(//direccion[localidad="Badajoz"]) Devuelve el valor 2 porque en la agenda hay dos direcciones cuya localidad es "Badajoz". - name(): devuelve el nombre de un nodo, que puede ser el nombre del primer nodo de un conjunto; dentro de los paréntesis se pone una expresión XPath, la cual dará un resultado; el nombre del primer nodo de este resultado es lo que muestra la función name. name(/agenda/contactos/*) Devuelve el nombre "persona" porque dentro de contactos el primer nodo es "persona". La expresión /agenda/contactos/* selecciona una serie de nodos, de los cuales el primero es "persona". name(/agenda/contactos) Devuelve el nombre "contactos" porque la expresión /agenda/contactos selecciona una serie de nodos, de los cuales el primero es "contactos". name(//persona/@id) Devuelve "id", ya que la expresión //persona/@id muestra los atributos "id" de las personas, por lo que el primer nodo es "id" (son nodos atributo en este caso). - position(): devuelve la posición del nodo por el que se va pasando en cada momento (nodo contexto). La posición se refiere al puesto que ocupa en la relación de nodos del mismo tipo. La expresión XPath selecciona una serie de nodos, sobre la cual podemos aplicar position() para elegir sólo algunos nodos en función de su posición. //calle[position()>=2] Selecciona las calles de la agenda que tengan posición 2 o mayor. La expresión //calle selecciona todas las calles, pero al aplicar position()>=2, nos quedamos sólo con las calles a partir del puesto 2. //localidad[position()<3] Selecciona las localidades con posición menor de tres. /agenda/contactos/persona[position()>1]/direccion/calle Selecciona la calle de las personas de la agenda cuya posición (de la persona) sea mayor de 1 (es decir, desde la segunda persona en adelante). - last(): devuelve la última posición de la lista de nodos; si la lista de nodos tiene 3 personas, last() devuelve el valor 3. //persona[ last() ] Selecciona la última persona de la agenda. //persona[ last() ]/* Selecciona los nodos hijos de la última persona de la agenda; la etiqueta "persona" no queda incluida en la selección. /agenda/contactos/persona[ last()-1 ]/telefonos Selecciona los telefonos de la penúltima persona de la agenda. - normalize-space(): quita los espacios en blanco sobrantes, es decir, los que estén al inicio y al final; además, varios espacios seguidos en medio de un valor los deja en uno. Por ejemplo, si representamos el espacio con el carácter ?, el valor "??p???01?" quedaría como "p?01". Si en el documento XML se escriben espacios sobrantes, por error o por otro motivo, en la expresión XPath pueden quitarse antes de realizar una comparación con un valor, para que dicha comparación sea más fiable. //persona[normalize-space(@id)="p01"] Selecciona las personas cuyo atributo "id" sea p01 después de haberle quitado los espacios sobrantes. Si en el documento XML se ha puesto un "id" con el valor " p01 " esos espacios son eliminados antes de evaluar la expresión XPath y la comparación con "p01" dará valor verdadero. //direccion[normalize-space(cp)="06002"]/.. Selecciona las personas cuyo cp sea 06002 después de quitarle los espacios sobrantes. Al poner .. sube desde el nodo direccion al padre, que es persona, por eso selecciona la persona completa, con todo su contenido. - string(): convierte a cadena; del conjunto de nodos que devuelve una expresión XPath, la función string selecciona el valor del primer nodo y lo convierte a cadena. No selecciona las etiquetas de los nodos, sino sólo el valor. string(//calle) Como //calle selecciona el conjunto de nodos calles, al aplicarle string selecciona el valor, convertido a cadena, de la primera calle; no selecciona las etiquetas <calle> y </calle>, sino sólo el valor de la primera calle. string(//direccion[localidad="Zafra"]/cp) Devuelve el cp convertido a cadena de la direccion cuya localidad sea Zafra. string(boolean(/agenda/persona)) Devuelve la cadena "false", ya que boolean(/agenda/persona) devuelve valor 0 (falso), porque persona no es hijo de agenda; el 0 convertido a cadena corresponde con "false". string(boolean(/agenda/contactos)) Devuelve la cadena "true", ya que boolean(/agenda/contactos) devuelve valor 1 (verdadero), porque agenda tiene nodos hijos llamados contactos; el 1 convertido a cadena corresponde con "true". - string-length(): devuelve la longitud de una cadena (número de caracteres). string-length(/agenda/propietario/apellidos) Devuelve 11, ya que los apellidos del propietario de la agenda tienen 11 caracteres, ya que es "Pinto Ramos", es decir 10 letras más el espacio en blanco suman 11. - concat(): concatena o une varias cadenas; estas cadenas se escriben dentro de los paréntesis separadas por comas, o sea concat(cad1, cad2, cad3, ...) . //identificadores[concat(nombre, apellidos) = "RobertoRamos Juan"] Selecciona el nodo identificadores de la persona cuyo nombre concatenado con los apellidos sea igual a "RobertoRamos Juan". Con la funcion concat las cadenas quedan pegadas (no inserta espacios en blanco entre una y otra); en este caso el nombre queda pegado a los apellidos. Para separar el nombre de los apellidos se hace como en el siguiente ejemplo. //identificadores[concat(nombre, " ", apellidos) = "Roberto Ramos Juan"] Selecciona el nodo identificadores de la persona cuyo nombre más un espacio en blanco más los apellidos sea igual a "Roberto Ramos Juan". Como vemos, se pueden concatenar 3 cadenas. - sum(): suma valores numéricos. sum(//cp) Devuelve la suma de todos los cp. sum(//direccion[cp=06002]/cp) Devuelve la suma de los cp de las direcciones cuyo cp sea 06002; como en nuestro XML sólo hay uno, muestra el cp, sólo el dato, sin las etiquetas <cp> y </cp>. - document(): esta función realmente no es de XPath, sino de XSLT. Puede utilizarse para acceder a elementos de otro documento XML. Entre paréntesis se puede indicar la ruta o URI del otro documento XML, como document('prueba.xml'), y nos devuelve el elemento raíz de ese documento. Si entre los paréntesis se indica un nodo, la función document() nos devuelve el conjunto de nodos cuyo elemento raíz es el nodo dado. 2.6. Varias selecciones. Con el operador | se pueden realizar varias selecciones en la misma expresión XPath. //direccion | //telefonos Selecciona todas las direcciones y todos los telefonos //direccion[localidad="Badajoz"]/calle | /agenda/contactos/persona/identificadores/nombre Selecciona la calle de los que sean de Badajoz y los nombres de todos los contactos. //direccion[localidad="Badajoz"]/calle | //direccion[localidad="Zafra"]/calle Selecciona la calle de los que sean de Badajoz y de los que sean de Zafra. //persona/identificadores/nombre | //persona/identificadores/apellidos Selecciona nombre y apellidos de todas las personas de la agenda. //contactos/persona/identificadores/nombre | //contactos/persona/@id Selecciona nombre e identificador de cada contacto de la agenda. 2.7. Predicados compuestos. Con los operadores "and" y "or" se pueden crear condiciones compuestas. //direccion[localidad = "Badajoz" or localidad = "Zafra"] Selecciona las direcciones que sean de "Badajoz" o de "Zafra". Lo que hace es que para cada localidad se comprueba si coincide con "Badajoz" o con "Zafra" y selecciona las direcciones en las que esa comprobación resulte verdadera (true o 1). Para que resulte verdadera una expresión que tiene or, debe ser verdadera una de las dos comparaciones que se incluyen, o las dos. Podrían ser tres comparaciones o más. En cuanto se cumpla una de las comparaciones, el resultado del or será verdadero. //direccion[localidad = "Badajoz" and localidad = "Zafra"] No selecciona ningún elemento, ya que ningún elemento tiene localidad igual a Badajoz y a la vez igual a Zafra. Lo que hace es que para cada localidad se comprueba si coincide con "Badajoz" y a la vez con "Zafra" y selecciona las direcciones en las que esa comprobación resulte verdadera (true o 1), pero lógicamente ninguna localidad vale a la vez "Badajoz" y "Zafra". Todas las comprobaciones darán falso (false o 0). Para que resulte verdadera una expresión que tiene and, deben ser verdaderas las dos comparaciones que se incluyen. Podrían ser tres comparaciones o más. En cuanto no se cumpla una de las comparaciones, el resultado del and será falso. //direccion[localidad = "Badajoz" and cp > 06002] Selecciona las direcciones de Badajoz y con cp mayor que 06002, que cumplan las dos condiciones (and). Si se cumplen las dos comparaciones, el resultado del and será verdadero. //direccion[localidad = "Badajoz" or cp > "06002"] Selecciona las direcciones de Badajoz o con cp mayor que "06002"; es decir las direcciones que cumplan una de las dos condiciones o ambas (or). Si se cumple una (o las dos) de las dos comparaciones, el resultado del or es verdadero. 3. XSLT. 3.1. Introducción. El lenguaje de marcas XSLT (XSL Transformation o Transformación XSL) permite transformar un documento XML, por ejemplo para mostrarlo como página web. XSL significa eXtensible Stylesheet Language (Lenguaje Extensible de Hojas de Estilo) y es un lenguaje que cumple el estándar XML. XSL tiene varias partes; una de ellas es la dedicada a XSLT, es decir a la transformación o conversión de un documento XML, por ejemplo en otro XML o en un HTML. Tanto XSL como XSLT son lenguajes XML. Un documento escrito con el lenguaje XSLT (una hoja XSLT) también es un documento XML, ya que cumple las especificaciones XML de W3C. XSLT también es un estándar aprobado por el W3C, con el que podemos transformar o convertir un documento XML en: Otro documento XML. Un documento HTML. Un documento de texto. 3.2. Estructura de una hoja XSLT. Para escribir las hojas o documentos XSLT usaremos el programa "XML Copy Editor". Cuando un documento XML está asociado a una hoja o documento XSLT, debe indicarse, después del prólogo del documento XML, con la línea: <?xml-stylesheet type="text/xsl" href="fichero.xsl"?> Donde fichero.xsl será la hoja o documento XSLT asociado al XML. En ese atributo href="..." puede indicarse la ruta completa del fichero, por ejemplo: <?xml-stylesheet type="text/xsl" href="C:\carpeta\prueba\fichero.xsl"?> <?xml-stylesheet type="text/xsl" href="http://www.pagina.es/directorio/fichero.xsl"?> Veámoslo con un ejemplo concreto. Teniendo el documento XML utilizado para las expresiones XPath anteriores (la agenda), se le añade la línea anterior para indicar que lleva asociado un fichero XSLT, del modo: fichero .xml <?xml version="1.0" encoding="iso-8859-1" standalone="no"?> <!-- La siguiente línea hace referencia al documento XSLT que hará la transformación --> <?xml-stylesheet type="text/xsl" href="transformacion01.xsl"?> <agenda> <propietario> <nombre>Ana</nombre> <apellidos>Pinto Ramos</apellidos> </propietario> <contactos> <persona id="p01"> etc etc etc ....... </agenda> Como vemos, la línea ?xml-stylesheet se utiliza para especificar el fichero .xsl que contiene la transformación que se va a aplicar al .xml; en este caso ese fichero se llama transformacion01.xsl, el cual debe guardarse en la misma carpeta que el fichero.xml en este ejemplo, porque en el href no se especifica una ruta distinta. Por otra parte, la estructura de un documento XSLT comienza con las dos líneas siguientes: fichero .xsl <?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0"> En la primera línea se indica que también se trata de un documento XML. Un ejemplo completo del fichero anterior, "transformacion01.xsl", podría ser el siguiente: transformación01.xsl <?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0"> <xsl:template match="/"> <html> <head><title>Primera transformación</title></head> <body bgcolor="#00dd00"> <h1>Agenda</h1> <h2>Propietario</h2> <b>Apellidos y nombre: </b> <xsl:value-of select="agenda/propietario/apellidos"/>, <xsl:value-of select="agenda/propietario/nombre"/> <br/> <h2>Contactos</h2> <b>Apellidos y nombre: </b> <xsl:value-of select="agenda/contactos/persona[1]/identificadores/apellidos"/>, <xsl:value-of select="agenda/contactos/persona[1]/identificadores/nombre"/> <b> - identificador: </b> <xsl:value-of select="agenda/contactos/persona[1]/@id"/> <br/> <b>Apellidos y nombre: </b> <xsl:value-of select="agenda/contactos/persona[2]/identificadores/apellidos"/>, <xsl:value-of select="agenda/contactos/persona[2]/identificadores/nombre"/> <b> - identificador: </b> <xsl:value-of select="agenda/contactos/persona[2]/@id"/> <br/> <b>Apellidos y nombre: </b> <xsl:value-of select="agenda/contactos/persona[3]/identificadores/apellidos"/>, <xsl:value-of select="agenda/contactos/persona[3]/identificadores/nombre"/> <b> - identificador: </b> <xsl:value-of select="agenda/contactos/persona[3]/@id"/> <br/> </body> </html> </xsl:template> </xsl:stylesheet> Una vez que hemos escrito con el programa "XML Copy Editor" ambos documentos, el XML y el XSLT, abrimos el fichero .xml con dicho programa. Puede abrirse a la vez también el fichero .xsl, pero no es obligatorio. Si tenemos ambos abiertos en "XML Copy Editor", nos situamos en el fichero .xml y en la opción "XML - XSL Transformación" del menú superior (o pulsando la tecla F8) aplicamos la transformación y se mostrará el código resultante, que en este caso es el código HTML siguiente: <?xml version="1.0" encoding="UTF-8"?> <html> <head> <title>Primera transformación</title> </head> <body bgcolor="#00dd00"><h1>Agenda</h1><h2>Propietario</h2><b>Apellidos y nombre: </b>Pinto Ramos, Ana<br/><h2>Contactos</h2><b>Apellidos y nombre: </b>Lago Santana, Maribel<b> - identificador: </b>p01<br/><b>Apellidos y nombre: </b>Ramos Juan, Roberto<b> - identificador: </b>p02<br/><b>Apellidos y nombre: </b>Lima Ruz, Juan<b> - identificador: </b>p03<br/></body> </html> Si guardamos ese código HTML en un fichero con extensión .html y lo abrimos con un navegador web podremos ver el resultado. Un modo más sencillo y rápido de mostrar esa transformación en un navegador web es el siguiente. Se trata simplemente de abrir el fichero .xml con el navegador web. Para que funcione en Chrome debe ejecutarse con la opción --allow-file-access-from-files, para lo cual puede crearse un nuevo lanzador o acceso directo con la "Ubicación" o "Destino" siguiente: "C:\Program Files (x86)\Google\Chrome\Application\chrome.exe" --allow-file-access-from-files El navegador web aplicará la transformación xsl asociada sobre el fichero xml y mostrará algo como: En el .xsl se indica que el fondo de página sea verde #00dd00, algunas palabras estén en negrita <b>, algunas palabras más grandes <h1>, etc. Si en el navegador web usamos la opción de "Ver código fuente" se muestra el documento en formato XML, pero si usamos la opción "Archivo - Guardar como" podremos guardar el resultado en formato HTML, obteniendo un documento con formato HTML creado por medio de la transformación .xsl que se ha aplicado. Como vemos, en este caso el .xsl transforma el fichero .xml en un documento HTML, que podrá mostrarse en el navegador web. Este fichero .xsl contiene etiquetas HTML, como <body>, <h1>, etc. (cumplen la sintaxis XML) y otras etiquetas <xsl: .../>; en este caso las <xsl:value-of select="..."/> son para seleccionar los datos del .xml que se van a mostrar, por ejemplo: <xsl:value-of select="agenda/propietario/apellidos"/> Dentro del select (entre las comillas) se ponen expresiones XPath que seleccionan los datos. Por tanto, el elemento value-of resuelve la expresión XPath que se indique dentro del select, es decir permite calcular una expresión XPath. Vemos que la etiqueta <xsl: ... /> tiene al final la barra / de cierre de etiqueta. La expresión XPath incluye los nombres de las etiquetas o nodos, pero al aplicar valueof select se queda con el dato, quitando las etiquetas, es decir: XPath: /agenda/propietario/apellidos selecciona <apellidos>Pinto Ramos</apellidos> XSLT: <xsl:value-of select="agenda/propietario/apellidos"/> selecciona Pinto Ramos Como vemos, el .xsl es un documento con formato XML, por lo que su prólogo es: <?xml version="1.0" encoding="UTF-8"?> La línea siguiente indica la versión del XSL: <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0"> Esta etiqueta debe cerrarse al final del fichero .xsl, del modo: </xsl:stylesheet> La línea siguiente es: <xsl:template match="/"> La función xsl:template (plantilla) se cierra con la línea </xsl:template>. En nuestro caso se cierra al final, antes de cerrar el documento con </xsl:stylesheet>), por lo que prácticamente todo queda dentro de la función xsl:template, la cual permite definir el formato a aplicar sobre los datos XML. Aparte de la función xsl:template, existe la función xsl:apply-templates, que se escribe dentro de las etiquetas de apertura y cierre de xsl:template y sirve para aplicar una plantilla definida sobre unos datos XML. En nuestro ejemplo no la utilizamos, no es necesaria. En nuestro ejemplo, la línea <xsl:template match="/"> indica que todas las expresiones XPath que contenga el fichero .xsl parten desde el nodo raíz, es decir: match="/". En lugar de ese match se podría haber usado el siguiente: <xsl:template match="/agenda"> En tal caso, al escribir las expresiones XPath del fichero .xsl nos ahorramos repetir "agenda/" en todos los "select", quedando por ejemplo: <xsl:value-of select="propietario/apellidos"/>, <xsl:value-of select="propietario/nombre"/> ... <xsl:value-of select="contactos/persona[1]/identificadores/apellidos"/>, <xsl:value-of select="contactos/persona[1]/identificadores/nombre"/> Como vemos, las rutas de estos select no comienzan por / sino directamente por el nombre del elemento en cuestión, es decir: propietario/apellidos propietario/nombre contactos/persona[1]/identificadores/apellidos contactos/persona[1]/identificadores/nombre Si se pone la barra inicial, por ejemplo las rutas /propietario/apellidos o /propietario/nombre, la transformación no funcionará, porque busca en el fichero .xml un nodo llamado propietario que cuelgue directamente del nodo raiz, y ese nodo no existe. En un select se puede poner una ruta que no comience por la barra / y en tal caso se trata de una ruta relativa, a la que debe añadirse la ruta indicada en el match para acceder al nodo en cuestión. Pero en el select también se puede poner una ruta comenzando por la barra / siendo una ruta absoluta, y en tal caso no se tiene en cuenta la ruta indicada en el match. Por ejemplo, si tenemos el match <xsl:template match="/agenda"> los siguientes select serán: <xsl:value-of select="propietario/apellidos"/> A esta ruta relativa (no comienza por / barra) se le añade la ruta indicada en match, quedando /agenda/propietario/apellidos. Selecciona los apellidos del propietario. <xsl:value-of select="/agenda/propietario/apellidos"/> Esta ruta es absoluta (comienza por / barra); no se le añade nada; no se tiene en cuenta la indicada en match. Selecciona los apellidos del propietario. <xsl:value-of select="/propietario/apellidos"/> Esta ruta es absoluta; no se le añade nada; no se tiene en cuenta la ruta del match; pero no selecciona nada, porque el nodo propietario no cuelga del nodo raíz en el xml. Otro elemento XSLT es xsl:output, que define el formato del documento de salida, que puede ser xml, html o text, lo cual se indica en el atributo method de dicho elemento. Este elemento no es obligatorio usarlo, porque generalmente el formato de salida se detecta automáticamente. Si se utiliza, se escribe antes de xsl:template y se cierra en la misma línea poniendo la barra / al final, por ejemplo: <?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0"> <xsl:output method="html"/> <xsl:template match="/"> etc etc etc Otro elemento XSLT es xsl:preserve-space que marca los elementos que no tienen espacios en blanco antes de la transformación; dicho de otro modo, indica los elementos a los que no se les va a quitar los espacios, éstos serán respetados, que es la opción por defecto. Por el contrario, el elemento xsl:strip-space marca los elementos que tienen espacios eliminados antes de la transformación; es decir, indica los elementos a los que se les va a quitar los espacios. Estos elementos se escriben antes de xsl:template y los nombres de los elementos se indican separados por espacios, aunque se puede poner * para indicar todos los elementos. Por ejemplo: fichero01.xml <?xml version="1.0" encoding="UTF-8" standalone="yes"?> <?xml-stylesheet type="text/xsl" href="transformacion01.xsl"?> <persona id="p01"> <identificadores> <nombre>Maribel</nombre> <apellidos>Lago Santana</apellidos> </identificadores> <direccion> <calle>Mayor 22, 2B</calle> <localidad>Badajoz</localidad> <cp>06002</cp> </direccion> <telefonos> <movil>666111111</movil> </telefonos> </persona> transformacion01.xsl <?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0"> <xsl:preserve-space elements="*"/> <xsl:template match="/"> <xsl:value-of select=" persona"/> </xsl:template> </xsl:stylesheet> Si con "XML Copy Editor" aplicamos ese xsl al documento xml, con la opción "XML - XSL Transformación" (o tecla F8), tendremos el resultado: <?xml version="1.0" encoding="UTF-8"?> Maribel Lago Santana Mayor 22, 2B Badajoz 06002 666111111 Si en el xsl cambiamos preserve-space por strip-space tendremos el resultado: <?xml version="1.0" encoding="UTF-8"?> MaribelLago SantanaMayor 22, 2BBadajoz06002666111111 Vemos que en este último caso no se respetan los espacios ocupados por las etiquetas del xml (todas las etiquetas, porque hemos usado asterisco *), quedando todos los datos seguidos. Otro elemento es xsl:decimal-format que convierte datos numéricos en cadenas; realmente define un formato que se aplicará cuando se conviertan números en cadenas. 3.3. Estructura repetitiva (for-each). Una de las órdenes más utilizadas en xsl es "for-each" (bucle) que permite recorrer una serie de nodos con el mismo nombre. En el ejemplo anterior hemos recorrido las tres personas de la agenda usando los predicados [1] [2] y [3] (persona[1] persona[2] y persona[3]). Si en lugar de tres personas, la agenda tuviera muchas más (por ejemplo 200 personas), el fichero .xsl escrito de ese modo sería muy extenso (desde persona[1] hasta persona[200). La forma más correcta de resolverlo es con la orden "for-each" que significa "para-cada"; en el caso de las personas de la agenda, será "para-cada persona". Quedaría como sigue: fichero .xsl <?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0"> <xsl:template match="/"> <html> <head><title>Segunda transformación</title></head> <body bgcolor="#0000dd"> <h1>Agenda</h1> <h2>Contactos - <xsl:value-of select="count(/agenda/contactos/persona)"/></h2> <xsl:for-each select="/agenda/contactos/persona"> <b>Apellidos y nombre: </b> <xsl:value-of select="identificadores/apellidos"/> <xsl:text>, </xsl:text> <xsl:value-of select="identificadores/nombre"/> <br/> </xsl:for-each> </body> </html> </xsl:template> </xsl:stylesheet> Como vemos, el formato para procesar cada persona es: <xsl:for-each select="/agenda/contactos/persona"> Como hemos dicho antes, podría evitarse la / barra inicial y usar la ruta relativa a la indicada en match, quedando: <xsl:for-each select="agenda/contactos/persona"> En "select" se indica el nodo que se desea recorrer todas las veces que aparezca en el .xml (en este caso es /agenda/contactos/persona). Si la agenda tuviera 200 personas, ese fichero .xsl no sería más extenso, con for-each se recorren las 200 personas con la misma orden. Lógicamente, la orden for-each debe tener su etiqueta de cierre, que es: </xsl:for-each> Como vemos en el ejemplo, dentro de la etiqueta for-each, las expresiones XPath no repiten la ruta completa del nodo; es decir, si el select de for-each ya nos ha situado en el nodo "/agenda/contactos/persona", los select de value-of dentro del for-each no repiten esa ruta, sino que la continúan, por ello para mostrar los apellidos y nombre sólo se pone "identificadores/apellidos" y "identificadores/nombre" (rutas relativas). Ahí deben ponerse obligatoriamente las rutas relativas, para que el for-each vaya avanzando de persona. En ese ejemplo también vemos que para insertar texto se puede usar la etiqueta <xsl:text>. En este caso se ha utilizado para insertar una coma y espacio en blanco entre los apellidos y el nombre de cada persona: <xsl:text>, </xsl:text> Además hemos usado la función count() de XPath para mostrar cuántas personas tiene la agenda, mostrando el número 3 al lado de "Contactos - ", con la instrucción: <h2>Contactos - <xsl:value-of select="count(/agenda/contactos/persona)"/></h2> El resultado de la transformación será: 3.4. Estructura condicional (if test). La estructura condicional se utiliza para mostrar sólo los datos que cumplan una condición. Por ejemplo, para mostrar sólo los contactos cuya localidad sea 'Badajoz', sería: fichero .xsl <?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0"> <xsl:template match="/"> <html> <head><title>Tercera transformación</title></head> <body bgcolor="#dd0000"> <h1>Agenda</h1> <h2>Contactos de Badajoz - <xsl:value-of select="count(//direccion[localidad='Badajoz'])"/></h2> <xsl:for-each select="/agenda/contactos/persona"> <xsl:if test="direccion/localidad='Badajoz'"> <b>Apellidos y nombre: </b> <xsl:value-of select="identificadores/apellidos"/> <xsl:text>, </xsl:text> <xsl:value-of select="identificadores/nombre"/> <br/> </xsl:if> </xsl:for-each> </body> </html> </xsl:template> </xsl:stylesheet> Como vemos, la instrucción "if test" permite aplicar una condición; 'Badajoz' se escribe con comillas simples porque las comillas dobles ya están usadas para el test: if test="...". Al incluir el "if test" dentro de la estructura repetitiva (for-each), comprueba la condición para cada persona de la agenda. Como ya se ha indicado previamente, el for-each nos ha situado en el nodo persona, dentro del for-each sólo se especifica la ruta partiendo de ese nodo, por eso en el "if test" sólo se escribe "direccion/localidad", en lugar de escribir la ruta completa que sería: "/agenda/contactos/persona/direccion/localidad". El resultado de esa transformación es: Podemos decir que la función xsl:if permite decidir entre dos formatos distintos en función de una condición. Como vemos, con la función de XPath count(//direccion[localidad='Badajoz']) mostramos el número de contactos de Badajoz. Ese count() usa una ruta absoluta (comienza por / barra, ya que pone //direccion.... ), pero está fuera del for-each, por tanto es correcto. Lógicamente la condición indicada en test puede ser compuesta, utilizando los operadores "and" y "or" de XPath, por ejemplo: <xsl:if test="direccion/localidad='Badajoz' and telefonos/casa"> Muestra los contactos que sean de 'Badajoz' y tengan teléfono en casa. <xsl:if test="direccion/localidad='Badajoz' or direccion/localidad='Zafra'"> Muestra los contactos que sean de 'Badajoz' o de 'Zafra'. 3.5. Estructura condición múltiple (choose when otherwise). Cuando se desea especificar varias condiciones anidadas se utiliza "choose when otherwise", para decidir qué formatos se aplican en función de esas condiciones. Por ejemplo: - Si una persona de la agenda tiene teléfono trabajo, mostrar nombre con el teléfono trabajo. - Si no tiene teléfono trabajo, si tiene teléfono de casa, mostrar nombre con tfno casa. - Si tampoco tiene teléfono de casa, si tiene teléfono móvil, mostrar nombre con tfno móvil. - Si tampoco tiene teléfono móvil, mostrar nombre y el texto "No tiene teléfono". Esa situación se escribiría como sigue: <?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0"> <xsl:template match="/"> <html> <head><title>Transformación</title></head> <body bgcolor="#00dddd"> <h1>Agenda</h1> <h2>Contactos</h2> <xsl:for-each select="/agenda/contactos/persona"> <xsl:choose> <xsl:when test="telefonos/trabajo"> <b>Apellidos y nombre - tfno trabajo: </b> <xsl:value-of select="concat(identificadores/apellidos, ', ',identificadores/nombre, ' ', telefonos/trabajo)"/> <br/> </xsl:when> <xsl:when test="telefonos/casa"> <b>Apellidos y nombre - tfno casa: </b> <xsl:value-of select="concat(identificadores/apellidos, ', ',identificadores/nombre, ' ', telefonos/casa)"/> <br/> </xsl:when> <xsl:when test="telefonos/movil"> <b>Apellidos y nombre - tfno movil: </b> <xsl:value-of select="concat(identificadores/apellidos, ', ',identificadores/nombre, ' ', telefonos/movil)"/> <br/> </xsl:when> <xsl:otherwise> <b>Apellidos y nombre - : </b> <xsl:value-of select="concat(identificadores/apellidos, ', ',identificadores/nombre, ' ', 'No tiene teléfono')"/> <br/> </xsl:otherwise> </xsl:choose> </xsl:for-each> </body> </html> </xsl:template> </xsl:stylesheet> El resultado será: En este caso el <xsl:choose> está dentro de un for-each. Como vemos, se pone una sóla sentencia <xsl:choose> dentro de la cual se incluyen varias <xsl:when test...>. La forma de procesar cada persona de la agenda (for-each) es la siguiente: - Se comprueba la condición del primer "when test"; si la cumple se ejecutan las sentencias que están dentro (hasta la etiqueta de cierre </xsl:when>) de ese primer when; y ya no comprueban más condiciones (es decir, no realiza más when ni el otherwise), por lo que se continua por la línea siguiente al cierre </xsl:choose>. Si no la cumple, se pasa al segundo "when test". - Si cumple la condición de ese segundo "when test", ejecuta las sentencias que tenga dentro y ya no comprueban más condiciones (ni when ni otherwise). Si no la cumple, pasa al tercer "when test". Y así sucesivamente. - Si no cumple ninguna de las condiciones de los "when test", se ejecutan las sentencias incluidas en "otherwise". Debe tenerse en cuenta que todo lo que se incluya en xsl:choose debe estar dentro de los xsl:when o dentro de xsl:otherwise, no puede ponerse información fuera de los mismos. Podemos deducir de lo explicado que el xsl:choose podría sustituirse por varios xsl:if, uno por cada xsl:when y otro para el xsl:otherwise. Quedarían en este ejemplo 4 xsl:if. Sin embargo, para este tipo de situaciones es mejor xsl:otherwise, por ahorro de código y por rapidez de ejecución. Como vemos en ese ejemplo, con la función concat() de XPath se pueden concatenar varias cadenas antes de mostrarlas. Otro ejemplo de "when test": para cada persona de la agenda, si es de Badajoz mostrar sus apellidos y su teléfono móvil; si es de Cáceres mostrar sus apellidos y su teléfono de casa; si no es ni de Badajoz ni de Cáceres, mostrar apellidos y el mensaje "Esta persona no es de Badajoz ni de Cáceres". Sería: <?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0"> <xsl:template match="/"> <html> <head> <title>Transformación</title> </head> <body bgcolor="#dd00dd"> <h1>Agenda</h1> <h2>Contactos</h2> <xsl:for-each select="/agenda/contactos/persona"> <xsl:choose> <xsl:when test="direccion/localidad='Badajoz'"> <xsl:value-of select="concat(identificadores/apellidos, ' - Tfno móvil: ', telefonos/movil)"/> <br/> </xsl:when> <xsl:when test="direccion/localidad='Cáceres'"> <xsl:value-of select="concat(identificadores/apellidos, ' - Tfno. casa: ', telefonos/casa)"/> <br/> </xsl:when> <xsl:otherwise> <xsl:value-of select="concat(identificadores/apellidos, ' - Esta persona no es de Badajoz ni de Cáceres')"/> <br/> </xsl:otherwise> </xsl:choose> </xsl:for-each> </body> </html> </xsl:template> </xsl:stylesheet> El resultado es: Otro ejemplo de "when test": mostrar el nombre y cp en tamaño <h3> de las personas cuyo cp esté entre 06001 y 06010; del resto de personas mostrar su nombre y cp en tamaño <h4>. Sería: <xsl:for-each select="/agenda/contactos/persona"> <xsl:choose> <xsl:when test="direccion/cp &gt; '06001' and direccion/cp &lt; '06010' "> <h3><xsl:value-of select="concat(identificadores/nombre, ' direccion/cp)"/></h3> </xsl:when> cp: ', <xsl:otherwise> <h4><xsl:value-of direccion/cp)"/></h4> </xsl:otherwise> </xsl:choose> </xsl:for-each> select="concat(identificadores/nombre, ' - cp: ', Como vemos, para escribir la condición de cp entre 06001 y 06010 se usa "and" (condición compuesta): <xsl:when test="direccion/cp &gt; '06001' and direccion/cp &lt; '06010' "> Además, en lugar del signo "mayor que" > se usa la entidad predefinida &gt; y en lugar del signo "menor que" < se usa la entidad predefinida &lt; para evitar errores sintácticos. 3.6. Ordenación (sort). La orden sort nos permite ordenar datos antes de mostrarlos, según el criterio que se indique. La sintaxis es <xsl: sort select=" ... criterio de ordenación ... "/> La orden sort debe escribirse justo debajo de la orden <xsl: for-each ...>. Por ejemplo, mostrar apellidos, nombre, localidad y el id de todas las personas de la agenda, ordenadas por apellidos. <?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0"> <xsl:template match="/"> <html> <head><title>Transformación</title></head> <body bgcolor="#aaaa00"> <h1>Agenda</h1> <h2>Contactos</h2> <xsl:for-each select="/agenda/contactos/persona"> <xsl:sort select="identificadores/apellidos"/> <xsl:value-of select="identificadores/apellidos"/>, <xsl:value-of select="identificadores/nombre"/> - <xsl:value-of select="direccion/localidad"/> - <xsl:value-of select="./@id"/> <br/> </xsl:for-each> </body> </html> </xsl:template> </xsl:stylesheet> El resultado será: Como vemos, para acceder al "id" se pone ./@id es decir que se usa el punto para comenzar en el nodo actual, ya que el for-each nos ha situado en /agenda/contactos/persona y el "id" pertenece a "persona2. Desde el nodo persona para acceder al "id" la ruta es esa: ./@id Si en for-each ponemos otro nodo, por ejemplo /agenda/contactos/persona/identificadores, el código cambiaría del modo siguiente: <xsl:for-each select="/agenda/contactos/persona/identificadores"> <xsl:sort select="apellidos"/> <xsl:value-of select="apellidos"/>, <xsl:value-of select="nombre"/> - <xsl:value-of select="../direccion/localidad"/> - <xsl:value-of select="../@id"/> <br/> </xsl:for-each> Es decir, si estamos situados en el nodo "identificadores", en "sort select" sólo debe indicarse "apellidos" (que es nodo hijo de identificadores). Sin embargo, para acceder a la localidad y el id se usan los dos puntos seguidos .. para subir al nodo padre (nos situamos en nodo persona); el nodo padre de identificadores es persona. Por ello, si partimos de identificadores el nodo ../direccion/localidad permite acceder a la localidad; y ../@id permite acceder al "id". El orden por defecto de sort es de menor a mayor (ascendente); pero se puede indicar que sea orden de mayor a menor (descendente). Esto se elige con la opción order y los valores "ascending" o "descending", del modo: <xsl:sort select="apellidos" order="ascending"/> o <xsl:sort select="apellidos" order="descending"/> Si no se indica order, coge el valor "ascending", es el valor por defecto. 3.7. Otro ejemplo. Lógicamente, se puede incluir cualquier etiqueta HTML en el .xsl, para dar el formato más conveniente. Por ejemplo, para crear una tabla a partir de la agenda se deben insertar las etiquetas <table>, <tr> y <td> en los puntos adecuados; podría ser algo como: <?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0"> <xsl:template match="/"> <html> <head><title>Transformación</title></head> <body bgcolor="#88aaaa"> <h1>Agenda</h1> <h2>Contactos</h2> <table border="3"><tr bgcolor="#00dddd"><td>Apellidos</td><td>Nombre</td><td>Localidad</td><td>Id</td></tr> <xsl:for-each select="/agenda/contactos/persona/identificadores"> <xsl:sort select="apellidos" order="descending"/> <tr><td><xsl:value-of select="apellidos"/></td> <td><xsl:value-of select="nombre"/></td> <td><xsl:value-of select="../direccion/localidad"/></td> <td><xsl:value-of select="../@id"/></td> </tr> </xsl:for-each> </table> </body> </html> </xsl:template> </xsl:stylesheet> El documento .xml con ese formato .xsl quedaría: Como vemos el orden aplicado es de mayor a menor, porque se ha indicado order="descending". 3.8. Procesadores y depuradores XSLT. Un procesador XSLT es capaz de interpretar lógicamente el lenguaje XSLT y no otros lenguajes. Se trata de un software que lee un documento XSLT y otro XML, creando un documento de salida aplicando las instrucciones de la hoja de estilos XSLT a la información del documento XML. Existen procesadores XSLT que pueden ejecutarse desde la línea de comandos, como Xalan de Apache o SAXON (Open Source). Como hemos podido comprobar, los navegadores web llevan integrado dicho procesador XSLT, por eso al abrir un documento XML que lleva asociado una hoja XSLT, el navegador es capaz de aplicar la transformación y mostrar el resultado. También los servidores web pueden llevar integrados procesadores XSLT, lo cual les permitirá alojar documentos XML y XSLT, aplicando las transformaciones correspondientes. Además, la mayoría de editores XML, como "XML Copy Editor", tienen la opción de aplicar la transformación XSLT al documento XML. Ya hemos mencionado que en "XML Copy Editor" el procesador XSLT está en la opción "XML - XSL Transformación" (o tecla F8). También existen los llamados depuradores XSLT (debbuger). Se trata de software que permite ver cómo se transforma el documento XML al ir ejecutando las sentencias del documento XSLT. Es decir, con los depuradores se puede seguir la generación de un documento, a partir de los datos del fichero .xml a los que se le aplica la hoja de estilo .xsl.