Página principal del curso de XML
Usando hojas de estilo para generar WML (teléfonos WAP)
Tutorial avanzado de generación de HTML a partir de XML/XSLT
Otros cursos y tutoriales: comercio electrónico, WAP, Webmaster
Página principal del grupo GeNeura
Ficheros de ejemplo

Generación de páginas Web usando XSLT y XML

J. J. Merelo

Al igual que XML, XSLT es un lenguaje de programación. Forma parte de la trilogía transformadora de XML, compuesta por las CSS (Cascading Style Sheets, hojas de estilo en cascada), que permite dar una apariencia en el navegador determinada a cada una de las etiquetas XML; XSLT (XML Stylesheets Language for Transformation , o lenguaje de transformación basado en hojas de estilo); y XSL:FO, (Formatting Objects, objetos de formateo), o transformaciones para fotocomposición, o, en general, para cualquier cosa que no sea XML, como por ejemplo HTML "del viejo" o PDF (el formato de Adobe).

XHTML sí es XML, sigue un DTD (varios, en realidad), y sólo admite documentos "bien formados". HTML no lo es, aunque pude convertirse fácilmente en XHTML usando utilidades tales como Tidy.

XSLT es pues, un lenguaje que se usa para convertir documentos XML en otros documentos XML; puede convertir un documento XML que obedezca a un DTD a otro que obedezca otro diferente, un documento XML bien formado a otro que siga un DTD, o, lo más habitual, convertirlo a "formatos finales", tales como WML (usado en los móviles WAP) o XHTML.

Los programas XSLT están escritos en XML, y generalmente, se necesita un procesador de hojas de estilo, o stylesheet processor para procesarlas, aplicándolas a un fichero XML.

El estilo de programación con las hojas XSLT es totalmente diferente a los otros lenguajes a los que estamos acostumbrados (tales como C++ o Perl), pareciéndose más a "lenguajes" tales como el AWK, o a otros lenguajes funcionales, tales como ML o Scheme. En la práctica, eso significa dos cosas:

SAX significa Simple API for XML; un filtro SAX trata un fichero XML como un canal (stream) sobre el cual se van aplicando transformaciones según se va leyendo. Para transformaciones simples tales como extraer un tipo de etiqueta determinada, y, de hecho, la mayoría de las incluidas en este tutorial, es mucho mejor usar un filtro SAX. El principal problema es que carecen de un lenguaje para describir esas transformaciones, y la sintaxis y aplicación depende totalmente del lenguaje en el que se trabaje. En Perl, por ejemplo, se puede usar XML::SAX, un módulo que permite hacer este tipo de transformaciones. Generalmente, en caso de ficheros muy grandes a los que haya que hacerle un procesamiento relativamente simple, SAX es una opción mejor, más rápida y con menos consumo de memoria.

En resumen, programar con las hojas XSLT (en inglés se les llama stylesheets o logicsheets) puede ser un poco frustante, pero cuando uno aprende a usarlas, no puede vivir sin ellas. En realidad, son la única alternativa cuando uno quiere adaptar un contenido descrito con XML a diferentes clientes (por ejemplo, móviles de diferente tamaño, diferentes navegadores), y la mejor alternativa cuando uno quiere procesar documentos XML (aunque haya otras: filtros SAX, expresiones regulares...). Otra alternativa, sobre todo si se está trabajando ya con un documento XML en forma de DOM (Document object model) es trabajar directamente sobre él. En este claso, de todas formas, se pueden usar transformaciones XSL, sólo que se aplicarán en memoria, en vez de leerlas desde un fichero.

Lo que consiguen las hojas de estilo es separar la información (almacenada en un documento XML) de su presentación, usando en cada caso las transformaciones que sean necesarias para que el contenido aparezca de la forma más adecuada en el cliente. Es más, se pueden usar diferentes hojas de estilo, o incluso la misma, para presentar la información de diferentes maneras dependiendo de los deseos o de las condiciones del usuario.

Aparte del hecho habitual de procesar documentos XML, XSLT es un lenguaje de programación, y por tanto se podría hacer cualquier cosa con ellas; incluso calcular la célebre criba de Eratóstenes o ejecutar un algoritmo genético. Pero a nosotros nos va a interesar más como simple herramienta de transformación de XML.

Actualmente hay varias versiones del estándar XSLT: la versión 1.0, que es la que implementan la mayoría de los procesadores, y se denomina "recomendación", es decir, para el consorcio W3, lo equivalente a un estándar, y la versión 2.0 , que, a fecha de 4 de noviembre del 2004, es un "working draft", o borrador de trabajo, que es el paso previo a un estándar. Algunos procesadores, tales como el Saxon, implementan ya esta última versión. Hay algunas diferencias importantes: el tratamiento uniforme de los árboles (técnicamente, se pueden convertir fragmentos de árboles de resultados en nodesets), uso de múltiples documentos de salida, y funciones definidas por el usuario que se pueden definir en XSLT, y no sólo en Java u otro lenguaje, como sucedía en estándares anteriores. .

Las convenciones que seguimos en los ejemplos son las siguientes: cada etiqueta XML en el documento XML van en diferente color, dependiendo de su posición en la jerarquía; en las hojas XSL, el código XML está en rojo, el código XSLT en verde, y el código que no es exclusivo ni de uno ni de otro, y que aparecerá tal cual en el documento final, en azul.

Para Windows, en sus diferentes versiones, hay dos herramientas que permiten editar XML y hojas de estilo XSLT, y aplicar directamente la una a la otra. Una de ellas es XMLSpy, que tiene un IDE muy bonito, pero que casca con relativa frecuencia. De hecho, he sido incapaz de aplicar una hoja de estilo a un documento XML. Otra opción es usar eXcelon Stylus, un peaso de programa, pero que sólo está disponible para WindowsNT/2000 (y no sé si XP); la versión 3.0 beta es gratuita para desarrolladores.

En realidad, para editar XML y XSLT no hace falta ningún editor especial, pero viene bien un editor de XML o incluso un entorno integrado que te ayude a indentar bien el código, e incluso a cerrar las etiquetas en el orden correcto, o te saque la etiqueta y atributos admisibles en cada momento en función del DTD. En ese sentido, si se trabaja en Windows, el mejor es el eXcelon Stylus; en Linux, se puede uno apañar bien con el XEmacs (que te valida usando un DTD, si es necesario).

Este tutorial no pretende ser una introducción exhaustiva al XML, en realidad, para los efectos de este tutorial, hay que saber poco XML: sólo que hace falta emparejar bien las etiquetas, y la estructura de árbol que sigue un documento XML (es lo que se denomina XML bien formado). En realidad, HTML se puede definir como un documento bien formado, de hecho, la última versión de HTML, XHTML, también lo es, o sea que no es un salto grande pasar de WML/XHTML a XML. En todo caso, los que quieran saber un poco más sobre el tema, pueden consultar los diferentes tutoriales del curso GeNeura de XML.

Ejercicios
1. Crear un documento XML que describa una ficha de alumno. Tiene que tener una etiqueta raíz (por ejemplo, acta), una etiqueta por cada alumno, y dentro de ellas, etiquetas para el DNI, nombre, apellidos y nota.

Hay muchas formas de usar las hojas de estilo. Lo más normal es usarlas dentro de un entorno de publicación tal como el Cocoon, o el IBM Transcoding Publisher, AxKit u otros por el estilo. Un entorno de publicación de XML permite mantener sitios completos basados en XML, y generar páginas en diferentes formatos a partir de ellos. En general, recomendamos Cocoon, que es una herramienta gratuita y Open Source basada en Java, y además una de las más avanzadas en el sector. Para una solución profesional, es mejor el IBM TP, pues forma parte del servidor de aplicaciones WebSphere, y cuenta con interfaz gráfico avanzado; el problema es el coste.

La principal diferencia entre la versión 2 de Xalan y las anteriores es que implementa el llamado TrAX, una API para poder aplicar transformaciones a árboles XML; probablemente incorpore también la versión 1.1 de XSLT

En muchos casos, lo que se necesita es aplicar hojas de estilo dentro de una aplicación, o usarlas desde línea de comandos a partir de otra aplicación o otro lenguaje de programación. En ese caso, lo más útil es usar un procesador de hojas de estilo, que habitualmente se encuentra en conjunción con un parser XML, con o sin validación. De estos, hay los siguientes:

Finalmente, para este curso, se puede usar un formulario simple que aplica hojas de estilo XSLT a documentos XML usando Perl: aplicador de hojas de estilo.

Contenido de esta sección
  • Ejecutando hojas de estilo
  • Hoja de estilo básica: templates
  • xsl:stylesheet, xsl:apply-templates, xsl:value-of

Para empezar, vamos a tratar de presentar una hoja XML de lo más simple (tienda0.xml):

<?xml version="1.0" encoding='ISO-8859-1'?> <?xml-stylesheet href="tienda0.xsl" type="text/xsl"?> <tienda>   <nombre>La tiendecilla  </nombre>   <telefono>953 87 12 23  </telefono> </tienda>

Este ejemplo lo iremos extendiendo hasta que contenga un catálogo de una tienda virtual. Por lo pronto incluimos solamente datos básicos sobre la tienda.

Para convertirlo en HTML, usaremos la siguiente hoja de estilo (tienda-html.xsl): 1 <?xml version="1.0" encoding="UTF-8"?> 3 <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> 5 <xsl:template match='/'> 6 <html> 7 <head><title>Generado con tienda-html.xsl</title></head> 8 <body> 9 <h1> <xsl:apply-templates /> </h1> 10 </body> 11 </html> 12 </xsl:template> 13 </xsl:stylesheet>

Tal como está, se puede cargar directamente con Firefox, que mostrará algo similar a la imagen. Asimismo se puede usar cualquier entorno integrado que incluya la posibilidad de hacer transformaciones, como el XMLShell o XMLSpy mencionados anteriormente.

Para procesarlo con el Xalan, tendremos que hacer lo siguiente:

  1. Añadir la máquina virtual Java al PATH: unix$ export PATH=$PATH:/usr/bin/java (o donde quiera que esté)
  2. Añadir al CLASSPATH los .jar (ficheros de librería) que incluyen Xerces (el parser de XML) y Xalan (el procesador de XSLT): unix$ export CLASSPATH=$CLASSPATH:/usr/local/xalan/bin/xalan.jar:/usr/local/xalan/xerces.jar (o donde quiera que estén)
  3. java org.apache.xalan.xslt.Process -IN tienda0.xml -XSL tienda-html.xsl -OUT tienda.html.

Esto debería dar como resultado algo como lo que hay en esta página web.

El saxon es otro procesador de hojas de estilo, que está disponible como un fichero .jar, y que por tanto se puede ejecutar en cualquier plataforma. Para usarlo, se escribe (después de definir el camino a la máquina virtual Java y al .jar correspondiente):

java com.icl.saxon.StyleSheet tienda0.xml tienda-html.xsl

El Instant Saxon es una versión de Saxon que ocupa poco y es relativamente fácil de usar. Hay que tener instalada una máquina virtual Java en Windows para que funcione; si tenemos instalado el Internet Explorer, con eso vamos que ardemos. Una vez instalado el explorer, basta escribir:

saxon -o tienda.html tienda0.xml tienda-html.xsl

Que nos dará el mismo resultado que anteriormente, con un poco de suerte.

También se puede usar el script en geneura, editando los ficheros anteriores y cortándolos y pegándolos en el sitio adecuado. Este script usa dos librerías de Perl: XML::LibXML y XML::LibXSLT, que a su vez están basadas en las librerías de Gnome LibXML2 equivalentes.

Para empezar, el código del documento XML contiene una etiqueta, tienda, que incluye otras dos etiquetas, nombre y telefono, que contienen el texto que queremos que aparezca en la página web. En el resto, la primera línea simplemente describe que el resto del documento es XML, y asocia la hoja de estilo XSLT (tienda0.xsl) al documento. Esta segunda línea se usa en entornos de publicación tales como el cocoon; algunos procesadores de XSLT la usan para tomar el nombre de la hoja de estilo que se va a usar; también lo suelen usar navegadores como el Mozilla/Netscape/Firefox o el Internet Explorer. Cada una de estas etiquetas se denominan elementos; y en principio, un documento puede tener tantas etiquetas como queramos.

El XML original, como se ve, es simplemente XML bien formado, no usa un DTD ni lo necesita: basta con que haya una etiqueta raíz (tienda, en nuestro caso), las etiquetas estén emparejadas correctamente y los atributos entre comillas. Diferentes documentos XML podrían ser procesados con la misma hoja de estilo, y darían diferente resultado. Ahora mismo, tal como está, un documento XML tiene asociada una sola hoja de estilo XSLT, y para ese viaje no hacen falta alforjas, salvo por el hecho de que muchos documentos XML pueden tener la misma hoja XSL y de esa forma mantener una apariencia común. También se verá más adelante la forma de que el procesador XSLT seleccione diferentes hojas XSLT dependiendo de las circunstancias (principalmente, el cliente).

La primera hoja de estilo con la que nos enfrentamos es relativamente simple. El "esqueleto" es un documento HTML normal, al cual tenemos que añadir "contenido dinámico", es decir, contenido generado a partir del XML original. Para hacernos una idea, en este caso simple, una hoja XSLT es como una plantilla sobre la que se cambian los contenidos por sus valores al aplicarla. En realidad, tendríamos que pensar en una hoja XSLT con en un jardinero que va podando árboles, partiendo de la raíz. Al llegar a una rama, hace algo con ella: la poda, inserta otra cosa, o simpelemente la deja tal cual; el árbol, en este caso, es realmente el árbol DOM del documento XML que está tratando.

Para empezar, se incluyen una serie de instrucciones, de la línea 1 a la 5: no son instrucciones en sí, sino que modifican el aspecto de la salida. La primera declara el documento XML y el tipo de codificación (que podría ser ISO-8859-1 en vez de UTF-8 si quisiéramos incluir acentos y demás caracteres idiosincráticos) y la segunda es la etiqueta raíz de la hoja de estilo (cerrada en la última línea; recordemos que una hoja de estilo XSL es también un documento XML y por tanto tiene que seguir todas sus convenciones), que declara la versión de XSLT que se está usando y el espacio de nombres (namespace) que vamos a usar, es decir, el prefijo que usarán todas las instrucciones propias de XSLT; en este caso, usamos xsl, pero cambiando esta instrucción podríamos usar otro cualquiera.

Ese esqueleto está organizado en "templates", que son partes de la hoja que se "disparan" cuando encuentran una etiqueta que corresponda a lo que hay en su atributo match. El primer y único template comienza en la línea 5, y, en este caso, el template que corresponde a la etiqueta raíz genera un esqueleto de página HTML. La "orden" en la línea 9 cede el control a los otros templates, es decir, trata de aplicar todos los demás templates que haya en el documento, incluyendo el resultado de aplicarlos precisamente en ese punto (es decir, entre las etiquetas h1. En este caso no hay más templates, salvo los llamados los templates por defecto, que lo único que hacen es incluir el contenido de las etiquetas en el documento de salida. Es decir, en la práctica lo que hemos hecho es decirle dónde tiene que incluir en el esqueleto los valores del documento original, sin procesarlos más.

La orden <xsl:apply-templates /> podría haberse sustituido, en este caso, por <xsl:value-of select='tienda' /> y habría tenido exactamente el mismo efecto, es decir, incluir el contenido de la etiqueta tienda (y todas las que descienden de ella).

Ejercicios
1. Crear un documento con otra etiqueta raíz, por ejemplo zzzxxx, y ver qué efecto tiene en la salida .
2. Crear un documento con varias etiquetas, con diferentes niveles de anidación, y aplicar la hoja de estilo, para ver qué efecto tiene.

Contenido de esta sección
  • Documentos XML con más etiquetas
  • Templates múltiples

Vamos a ver algún documento XML algo más complicado; en concreto, vamos a tratar de procesar documentos XML que describen diferentes artículos de una tienda. Una tienda virtual está formada por diferentes productos, que a su vez tienen una serie de características: código de producto, tipo de artículo, sección. Una tienda virtual se describiría en un fichero como el siguiente tienda1.xml

<?xml version="1.0" encoding='ISO-8859-1'?>
<?xml-stylesheet href="tienda1-html.xsl" type="text/xsl"?>
<tienda>   <nombre>La tiendecilla  </nombre>   <telefono>953 87 12 23  </telefono>   <producto>   <codigo>92  </codigo>   <cantidad>10  </cantidad>   <articulo>Radio-Casette  </articulo>   </producto>   <producto>   <codigo>103  </codigo>   <cantidad>50  </cantidad>   <articulo>Reloj Cocina  </articulo>   </producto>   <producto>   <codigo>1312  </codigo>   <cantidad>3  </cantidad>   <articulo>Sofá  </articulo>   </producto> </tienda>

Este se transforma con la siguiente hoja XSLT tienda1-html.xsl:

5 <xsl:template match='/'> 6 <html> 7 <xsl:apply-templates /> 8 </html> 9 </xsl:template> 11 <xsl:template match='tienda'> 12 <head><title><xsl:value-of select='nombre' /> (Generado por tienda1-html.xsl)</title></head> 13 <body> 14 <h1><xsl:value-of select='nombre' /> </h1> 16 <h2>Teléfono: <xsl:value-of select='telefono' /> </h2> 18 <h2>Nuestros mejores productos </h2> 19 <table> 20 <tr><th>Código</th><th>Existencias</th><th>Artículo</th></tr> 21 <xsl:apply-templates select='producto' /> 22 </table> 23 </body> 24 </xsl:template> 26 <xsl:template match='producto'> 27 <tr><xsl:apply-templates /></tr> 28 </xsl:template> 30 <xsl:template match='codigo|cantidad|articulo'> 31 <td><xsl:apply-templates /></td> 32 </xsl:template>

Esta hoja, a la que se le han suprimido los elementos comunes con la hoja anterior (comienzo y final), se compone de cuatro templates diferentes, mientras que la anterior tenía solamente uno. Cada template trata elementos de diferente profundidad dentro del árbol del documento XML: el primer template trataría la raíz, el segundo las ramas que descienden de él, y el tercer y cuarto ramas progresivamente más bajas.

Por eso el primer template simplemente incluye el código de apertura y cierre del documento HTML, y cede control al siguiente template, que trata la primera etiqueta, tienda, tal como aparece en el atributo match. En este template se incluyen dentro del patrón en HTML el nombre y al telefono, usando xsl:value-of, que simplemente incluye el contenido del elemento correspondiente. También se crea el esqueleto de una tabla en HTML (líneas 19 en adelante), con su cabecera, y cede control al siguiente template, que se encargará de cada uno de los productos; para que se procesen sólo las etiquetas de este tipo que desciendan de tienda, y no las otras (nombre, telefono), que ya están incluidas en la salida, se usa el atributo select (línea 21): con xsl:apply-templates select='producto' se aplicarán sólo los templates que correspondan a este elemento.

Ese template comienza en la línea 26 y crea el esqueleto de una fila, cediendo el control a los templates que traten con los descendientes. Hay un sólo template que lo hace, el que comienza en la línea 30; en vez de procesar un sólo tipo de elemento, procesa 3, y para ello usamos | para indicar que puede procesar cualquiera de ellos, incluyéndolos dentro de un demarcador de celda de tabla en HTML

El orden en el que se incluyen los elementos en la salida es el mismo en el que se encuentran en la entrada, es lo que se denomina orden de documento. Este orden se puede alterar usando etiquetas value-of adecuadamente y usando otros métodos, que veremos a continuación.

Ejercicios
1. Crear un documento para una clasificación de equipos de fútbol en XML, y transformarlo mediante XSLT en una tabla HTML, con cada equipo en una fila, diferentes valores (puntuación, goles a favor y en contra) en columnas, con el nombre del equipo en negrita.

Contenido de esta sección
  • Atributos
  • Bucles

El XML es infinito, y tiene muchas más cualidades. Para empezar, los elementos pueden tener atributos, que se añaden dentro de la etiqueta para cualificarla <elemento atributo1='valor1' atributo2='valor2' />; en este sentido, funciona igual que el HTML, pero los atributos tienen que ir siempre entre comillas (simples o dobles). De la misma forma, cuando un elemento se repite varias veces, y hay que hacer algo diferenciado con cada una de ellas, conviene tener a mano bucles. Ambas estructuras las pondremos en práctica sobre el siguiente ejemplo tiendecilla.xml (que se muestra incompleto, porque es un rato largo): <tienda>   <nombre>La tiendecilla  </nombre>   <telefono>953 87 12 23  </telefono>   <url etiqueta="URL: ">http://www.tiendecilla.es  </url>   <url prefijo="mailto:" etiqueta="Informacion: ">info@tiendecilla.es  </url>   <producto>   <codigo>92  </codigo>   <cantidad>10  </cantidad>   <articulo>Radio-Casette  </articulo>   <seccion>Electrónica  </seccion>   <marca>Sony  </marca>   <modelo>MKJ-800  </modelo>   <caracteristicas>       <linea>Auto-reverse      </linea>       <linea>Dolby-sorround      </linea>       <linea>Doble pletina      </linea>       <linea>Ecualizador de cinco bandas      </linea>       <linea>Garantía de 9 meses.      </linea>   </caracteristicas>   <precio moneda="euro">90  </precio>   </producto>   <producto>   <codigo>103  </codigo>   <cantidad>50  </cantidad>   <articulo>Reloj Cocina  </articulo>   <seccion>Electrónica  </seccion>   <marca>Kenwood  </marca>   <modelo>Blue ONE  </modelo>   <caracteristicas>       <linea>Varios diseños      </linea>   </caracteristicas>   <opciones nombre="color" tipo="unica">       <opcion valor="rojo"/>       <opcion valor="azul"/>       <opcion valor="blanco"/>   </opciones>   <opciones nombre="forma" tipo="unica">       <opcion valor="cuadrado"/>       <opcion valor="triangular"/>       <opcion valor="redondo"/>       <linea>Garantía de 6 meses.      </linea>   </opciones>   <precio moneda="euro">12  </precio>   </producto> <!-- Más cosas por aquí... --> </tienda>

Este catálogo lo podemos presentar directamente como un formulario HTML, usando la hoja de estilo siguiente: (tiendecilla-html.xsl; sólo están las líneas que difieren de los ejemplos anteriores)

11 <xsl:template match='tienda'> 18 <xsl:for-each select='url' > 19 <xsl:value-of select='@etiqueta' /> <a href='{@prefijo}{.}'> <xsl:value-of select='.' /></a> <br /> 20 </xsl:for-each> 21 <h2>Nuestros mejores productos </h2> 22 <table border='1' borderwidth='2'> 37 <xsl:template match='caracteristicas'> 38 <td><ul> 39 <xsl:for-each select='linea'> 40 <li><xsl:value-of select='.' /></li> 41 </xsl:for-each> 42 </ul></td> 43 </xsl:template> 44 45 <xsl:template match='opciones'> 46 <td><xsl:value-of select='@nombre' /><select> 47 <xsl:for-each select='opcion'> 48 <option><xsl:value-of select='@valor' /></option> 49 </xsl:for-each> 50 </select></td> 51 </xsl:template> 52 53 <xsl:template match='precio'> 54 <td>Precio: <xsl:value-of select='.' /> <xsl:value-of select='@moneda' />s </td> 55 </xsl:template> 56 57 </xsl:stylesheet>
Si todavía no sabes lo que es un XPath, esta es una ocasión tan buena como otra cualquiera. Se trata de una forma de expresar o seleccionar de forma única nodos o grupos de nodos de un árbol DOM que represente a un documento XML. Hay un excelente tutorial que explica qué tipos de nodos hay, y cómo expresar un nodo determinado.

En la línea 18 (la tercera del ejemplo) aparece la primera estructura nueva: xsl:for-each, que realiza un bucle sobre una serie de elementos que cumplan la condición que pone en el atributo select; en este caso, todos los elementos url que desciendan del contexto actual (estamos al nivel de la etiqueta tienda). En este atributo se puede incluir cualquier XPath que dé como resultado un node-set, y se ejecutará una vez por cada elemento del mismo. Por ejemplo, si pusiéramos <xsl:for-each select='producto'> se ejecutaría una vez por cada producto, exactamente a como se hace arriba con xsl:apply-templates (en realidad, para lo que sabemos en este punto del tutorial, son prácticamente iguales).

Justamente dentro del bucle usamos atributos; en realidad, forman parte de la definición de XPath y no tienen mucha historia: para usar un atributo, se le pone la arroba delante y punto. En este caso, se ponen las llaves alrededor para separar el atributo del valor del elemento que se está procesando (., en la línea 19). Las características y opciones se procesan de manera análoga: se ejecuta un bucle para cada uno de los subelementos, generando elementos de una lista en un caso, y opciones de un formulario en otro caso.

Ejercicios
1. Hacer el ejercicio anterior, usando atributos para alguna de las cantidades.

Contenido de esta sección
  • Parámetros: xsl:param
  • Decisiones: xsl:when

Si sólo se pudieran generar páginas estáticas, poco habríamos ganado con respecto al HTML; la gracia del binomio XML/XSLT es que se puede generar contenido diferente dependiendo de las entradas. Para ello debe haber alguna forma de pasarle parámetros a las hojas, y eso se hace, cómo si no, con la orden xsl:param. A la vez, teniendo en cuenta las entradas, habrá que tomar decisiones; o quizás dependiendo de los atributos. Cambiaremos la hoja de estilo anterior para que liste sólo productos de una sección, y tomaremos decisiones sobre qué poner dependiendo de los valores de atributos: ( tiendecilla-html-1.xsl; mostramos como de costumbre sólo las líneas relevantes)

4 5 <xsl:param name='seccion' /> 16 <h1><xsl:value-of select='nombre' /> Sección : <xsl:value-of select='$seccion' /> 17 </h1> 27 <xsl:apply-templates select='producto[seccion=$seccion]' /> 48 <xsl:template match='opciones'> 49 <td><xsl:value-of select='@nombre' /> 50 <xsl:choose> 51 <xsl:when test='@tipo="unica"' > 52 <select> 53 <xsl:for-each select='opcion'> 54 <option><xsl:value-of select='@valor' /></option> 55 </xsl:for-each> 56 </select> 57 </xsl:when> 58 <xsl:when test='@tipo="si_no"'> 59 : <xsl:value-of select='opcion/@valor' /> <input type="checkbox" /> 60 </xsl:when> 61 </xsl:choose> 62 </td> 63 </xsl:template>

Para usar esta hoja pasándole parámetros desde la línea de órdenes, habrá que hacerlo así (usando saxon):

java com.icl.saxon.StyleSheet tiendecilla.xml tiendecilla-html-1.xsl seccion='Muebles'

o bien, usando Xalan (para más información, mirar las instrucciones

java org.apache.xalan.xslt.Process -IN tiendecilla.xml -XSL tiendecilla-html-1.xsl -PARAM seccion Muebles -OUT muebles.html

Si se usa la hoja de estilo dentro de un entorno de publicación tal como el Cocoon, habrá que pasarle los parámetros al documento XML, bien directamente desde el URL o desde un formulario, algo así: http://misitio.com/cocoon/tiendecilla.xml?seccion=Muebles. En Mozilla/Firefox y otros navegadores debería funcionar algo similar, pero no funciona. Si alguien encuentra como se hace, que avise.

En cualquier caso, el resultado será una página que incluirá solamente los productos correspondientes a una sección.

El truco para hacer esto está en los primeros templates. Para empezar, XSLT es bastante idiosincrático, y no puede recoger los parámetros que se le pasan a no ser que se declare un parámetro interno, y se le asigne el nombre. Este parámetro es similar a las variables de los lenguajes de toda la vida, salvo que no se le puede cambiar el valor: una vez que se le asigna uno al principio, se queda con él; el ámbito del parámetro será toda la hoja, y mientras se esté ejecutando. Eso se hace en la línea 5; posteriormente, en la línea 16 se usa el valor de la variable en una sentencia xsl:value-of; para acceder a su valor, se usa $seccion.

En la línea 27 se usan los predicados de XPath para seleccionar sólo aquellos nodos en los que el elemento seccion tenga el mismo valor que la variable; en este atributo se puede usar cualquier XPath, incluyendo este tipo de decisiones; conocer XPath y usarlo con sabiduría nos puede ahorrar un montón de código.

Para tomar decisiones (más que para seleccionar) un poco más complejas, con diferentes opciones, XSLT incluye las órdenes xsl:choose/xsl:when, que se usan conjuntamente (como el switch/case en C), tal como se ve en la línea 53. En este caso se usa para seleccionar diferente presentación, dependiendo del valor del atributo tipo; en caso de que sea "unica" usamos unos menuses, mientras que si es "si_no" usamos una "checkbox", que se pueda pulsar o no dependiendo de la opción.

Ejercicios
1. Cambiar la clasificación, de forma que se usen sólo los equipos que tengan una puntuación mayor que una dada, que se pase como parámetro.
2. Cambiar la clasificación, de forma que aparezcan en una tabla los tres primeros con un color, y los siguientes con un color diferente. Usar expresiones XPath para seleccionarlos o <xsl:number>; usar si es necesario una variable para establecer el color.

Contenido de esta sección
  • Seleccionando hojas de estilo
  • xsl:sort
  • Diferentes modos de invocar los templates

Un documento XML puede llevar asociadas diferentes hojas de estilo, y el propio sistema de publicación (tal como el cocoon) se encargará de seleccionarla dependiendo del navegador que use el cliente. Por ejemplo, se puede añadir esta línea a un fichero XML tal como este:

<?xml-stylesheet href="encuesta1-lynx.xsl" type="text/xsl" media="lynx"?>

[Lo feo que se ve con el
lynx

En este caso, Cocoon usará la hoja de estilo encuesta1-lynx.xsl cuando el navegador sea el Lynx. Esta hoja de estilo está bastante más simplificada que la anterior, dando un código HTML más simple y visible en modo texto. En el Lynx, se vería como aparece en la imagen.

¿Qué ocurre si queremos sacar los resultados por orden alfabético, en vez del mismo orden que en el documento? Habrá que usar una nueva opción de apply-templates, sort. Por ejemplo, si queremos formatear el siguiente fichero XML (ordenar.xml):

<raiz>
  <cosa>Pepe  </cosa>
  <cosa>Juan  </cosa>
  <cosa>Enrique  </cosa>
  <cosa>Xabier  </cosa>
  <cosa>Aarón  </cosa>
</raiz>

Se puede hacer con la siguiente hoja de estilo (ordenar.xsl), de la cual extraemos solo las órdenes pertinentes:

11 <xsl:apply-templates select='cosa'> 12 <xsl:sort select='.' data-type='text'/> 13 </xsl:apply-templates><br />

El resultado se puede ver en el fichero ordenar.htm. En este caso, en vez de aplicar la orden apply-templates en un solo tag "vacío" (con el / al final) se aplica en forma de tag y antitag, con las opciones en medio. La opción xsl:sortsirve para ordenar, y como clave de ordenación se puede usar cualquier atributo, XPath, o en este caso, el contenido en sí de la etiqueta cosa. En la salida, saldrán ordenados, Aarón primero y Xabier el último. El atributo data-type sirve para indicar el criterio de ordenación,< code>text para texto y number para ordenación numérica.

Todavía se puede cambiar un poco más el sistema: a veces resulta que uno quiere procesar de forma diferente un elemento, dependiendo de la entrada. Meter decisiones puede ser un poco coñazo, así que el propio estándar XSL tiene la forma de hacerlo: usando modos, tal como se hace en la siguiente y bonita hoja de estilo (ordenar2.xsl):

1 <?xml version="1.0" encoding='ISO-8859-1'?>
2 3 <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> 4 5 <xsl:param name='modo' /> 6 <xsl:output encoding='iso-8859-1' /> 7 <xsl:template match='raiz'> 8 <xsl:processing-instruction name="cocoon-format">type="text/html"</xsl:processing-instruction> 9 <html> 10 <head><title>Ordenando</title></head> 11 <body> 12 Mode: <xsl:value-of select='$modo' /> 13 <ol> 14 <xsl:choose> 15 <xsl:when test='$modo = verbose'> 16 <xsl:apply-templates select='cosa' mode='verbose'> 17 <xsl:sort select='.' data-type='text'/> 18 </xsl:apply-templates><br /> 19 </xsl:when> 20 <xsl:otherwise> 21 <xsl:apply-templates select='cosa' mode='normal'> 22 <xsl:sort select='.' data-type='text'/> 23 </xsl:apply-templates><br /> 24 </xsl:otherwise> 25 </xsl:choose> 26 </ol> 27 </body> 28 </html> 29 </xsl:template> 30 31 <xsl:template match='cosa' mode='normal'> 32 <li><xsl:value-of select='.'/></li> 33 </xsl:template> 34 35 <xsl:template match='cosa' mode='verbose'> 36 <li>Nombre: <xsl:value-of select='.'/></li> 37 </xsl:template> 38 39 </xsl:stylesheet>

En esta hoja de estilo se usa el atributo mode al llamar y al definir un template; los nombres que le hemos asignado son arbitrarios, pero lo que hacen es que, en caso de que se le pase como parámetro "verbose" imprimirá "nombre" delante del nombre de la persona; si no, lo imprimirá como antes. Para que funcione, se toma una decisión (a partir de la línea 14): en caso de que el modo sea "verbose", se llama al template usando como atributo de modo esa palabra; en otro caso, se llama al template "normal". Ese código tiene el efecto de que se llame a un template o a otro, según el modo que se haya elegido.

Pero claro, esto se puede resolver, en este caso, de una forma mucho más simple. ¿No sería ideal que se pudiera llamar a un template con parámetros, como si de una subrutina se tratara? Pues sí, se puede, tal como se hace en la hoja siguiente (ordenar3.xsl):

6 <xsl:output encoding='iso-8859-1' /> 7 <xsl:template match='raiz'> 8 <html> 9 <head><title>Ordenando 3</title></head> 10 <body> 11 Mode: <xsl:value-of select='$modo' /> 12 <ol> 13 <xsl:apply-templates select='cosa'> 14 <xsl:with-param name='miModo' select='$modo' /> 15 <xsl:sort select='.' data-type='text'/> 16 </xsl:apply-templates><br /> 17 </ol> 18 </body> 19 </html> 20 </xsl:template> 21 22 <xsl:template match='cosa'> 23 <xsl:param name='miModo' /> 24 <li><xsl:choose><xsl:when test='$miModo = "verbose"'>Nombre: </xsl:when></xsl:choose> 25 <xsl:value-of select='.'/></li> 26 </xsl:template> 27 28 </xsl:stylesheet>

La principal diferencia con los dos anteriores está en la línea 14: la orden xsl:with-param hace que se llame al template con un parámetro determinado, cuyo nombre y valor queda establecido por los atributos. En este caso, estamos asignándole al parámetro miModo el mismo valor que el del parámetro modo (y por eso es innecesario, peor admitámoslo por mor de la pedagogía). Ese parámetro se recibe dentro del template con una sentencia similar a la que se usa para los parámetros globales: xsl:param; si el parámetro no se declara aquí, al principio del template, XSL no le hará ni puto caso.

Y ya dentro del template, usamos una decisión, como las que se han visto anteriormente, para imprimir el introito a cada nombre en caso de que el modo sea el adecuado. Y aquí paz y después gloria.

Ejercicios
1. Cambiar el programa de procesamiento del XML de los equipos de fútbol, de forma que salgan ordenados por orden de puntuación (se supone que el original puede estar desordenado), o por orden alfabético, dependido de un parámetro.
2. Añadir modos a la hoja de procesamiento de las quinielas, de forma que, en un modo, salga en forma de tabla, y en otro modo, en forma de lista ordenada.
3. Cambiar la hoja de procesamiento de las quinielas, de forma que cada fila aparezca con un color diferente.

Contenido de esta sección
  • Llamando a templates explícitamente
  • Usando recursividad
  • xsl:call-template/xsl:param/xsl:with-param/xsl:if, variables
  • Usando extensiones de XSL en el procesador Saxon

Como era de esperar, el jefe no está contento; ya que el tema de las quinielas está agotado, vamos a por las encuestas. Y la encuesta hay que hacerla medianamente bien, poniendo diferentes elementos de formularios, y no dejando que el usuario conteste lo que le dé la gana. Usando el fichero encuesta2.xml, habría que aplicar la siguiente hoja (encuesta3.xsl): 61 <xsl:when test='@tipo="numero"'> 62 <tr> 63 <td colspan ='2' width="100%" bgcolor="white"> 64 <select name='numero'> 65 <xsl:call-template name='option-range'> 66 <xsl:with-param name='range' select='@rango'/> 67 </xsl:call-template> 68 </select> 69 </td> 70 </tr> 71 </xsl:when> 115 <xsl:template name='option-range'> 116 <xsl:param name='range' /> 117 <xsl:param name='i'>0</xsl:param> 118 <xsl:if test='$range != $i'> 119 <option><xsl:value-of select='$i' /></option> 120 <xsl:call-template name='option-range'> 121 <xsl:with-param name='range' select='$range'/> 122 <xsl:with-param name='i' select='$i+1'/> 123 </xsl:call-template> 124 </xsl:if> 125 </xsl:template> 126 </xsl:stylesheet>

Como siempre, aquí está la salida. Se han puesto las líneas que introducen elementos nuevos solamente.

La principal complicación de este tipo de problemas, que se resuelven fácilmente con un bucle for en cualquier lenguaje de programación pachanguero, en lenguajes funcionales pueden ser un poco más complicados. Por eso, muchos procesadores de XSL tienen extensiones que permiten hacer bucles convencionales, como veremos más adelante.

El principal problema es que, dado que no se pueden actualizar variables por la ausencia de efectos secundarios, no se puede crear un bucle que vaya comprobando el valor de una variable y actualizándolo en cada iteración. La única solución es sustituir los bucles clásicos por bucles recursivos, en los cuales se llama recursivamente a una subrutina (en este caso, template) hasta que se cumple una condición.

En este caso, vamos a incluir una etiqueta select y sus correspondientes option, una para cada uno de las opciones que haya. Y no tenemos más remedio que llamar a una rutina recursivamente. Eso se hace en la línea 65. En este caso en vez de dejar que los templates se disparen según los valores de una etiqueta, lo llamamos explícitamente con la orden xsl:call-template. Y dado que se trata de una especie de subrutina o procedimiento, hay que llamarlo también con sus correspondientes parámetros, para lo cual se usa xsl:with-param. Es decir, las siguientes órdenes

<xsl:call-template name='option-range'> <xsl:with-param name='range' select='@rango'/> </xsl:call-template>

corresponderían en cualquier otro lenguaje de programación a option-range( range ).

De la misma forma, la "declaración" de un template es un tanto peculiar (línea 115):

<xsl:template name='option-range'> <xsl:param name='range' /> <xsl:param name='i'>0</xsl:param>

Esto correspondería a algo así como void function option-range(var range, var i=0), es decir, declaración de dos variables, una de las cuales tiene un valor por defecto, el 0, y otra sin valor por defecto, que tiene que ser dado por el usuario.

El resto de ese template, a partir de la línea 118, comprueba si el range es distinto al contador que usamos, i, y si lo es, imprime las etiquetas HTML, y vuelve a llamarse recursivamente, incrementando el contador. Cuando range y i sean iguales, la recursión para.

En este template se usan también variables, las que se pasan como parámetro. Las variables, aunque no lo son en el sentido clásico, se pueden actualizar dentro de un template, asignársele valor y pasar como parámetro a otros templates; para asignarles valor se puede usar esta construcción:

<xsl:param name='i'>0</xsl:param>

o bien con esta, que es equivalente

<xsl:param name='i' select='0' />

En ambos casos se le asignaría el valor 0 a la variable i. Para recuperar el valor de la variable i se usa con el $ delante, de esta forma $i. Por ejemplo, si queremos asignar a una variable el valor de otra, se podría hacer de dos formas: <xsl:param name='i' select='$j'> <xsl:param name='i'>{$j}<xsl:param>

En el segundo caso, las llaves indican que, a pesar de encontrarse la variable fuera de una etiqueta XSL, debe ser evaluada.

Las variables y parámetros se declaran sin tipo: pueden contener una cadena, un node-set, o un resulting tree fragment, que se puede convertir en una cadena, pero no en un node-set; según el contexto, se usarán como conjuntos de nodos, árboles o cadenas. La transformación a cadenas se hace automáticamente, pero hacerlo al revés es bastante fastidiado. Algunos procesadores incluyen extensiones que permiten

También se usa la orden xsl:if, cuyo significado es similar a cualquier otro lenguaje. Si la condición que se le pasa en el atributo test es cierta, se incluye en la salida el fragmento correspondiente; si no, no se hace nada. Como no tiene nada parecido al "else", si hay más de una opción, lo mejor es usar xsl:when en tal caso.

En resumen, con esta hoja de estilo se obtiene una página HTML (encuesta3.html) similar a la anterior, salvo que las respuestas de valor numérico se seleccionan a través de un menú.

Y como evidentemente hacer las cosas así puede convertirse en un poco pesado, algunos procesadores como el Saxon lo solucionan a base de extensiones, es decir, etiquetas propias, tal como se muestra en el ejemplo encuesta3-saxon.xsl. En este caso, se tiene que añadir al principio del fichero una declaración de espacio de nombres y de prefijo:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:saxon="http://icl.com/saxon" extension-element-prefixes="saxon">

y con esa declaración (y lo necesario para usar Saxon, que se ha explicado anteriormente), se puede incluir el menú de la forma siguiente:

<xsl:for-each select='saxon:range(0,@rango)'> <option><xsl:value-of select='.' /></option> </xsl:for-each>

Esta construcción saxon:range equivale a un bucle que se ejecuta desde 0 hasta el número indicado en el atributo rango (@rango). Con esto, nos ahorramos toda la historia de la recursión y todo eso. Y el resultado es exactamente el mismo. En realidad lo que hace es que crea un conjunto de nodos, cada uno de los cuales tiene un valor en el rango indicado; pero qué se le va a hacer, es la forma de hacer las cosas. El problema de usar estas extensiones es que solamente funcionan con el procesador que las implementa, no con el resto. Una construcción usando elementos estándar XSL funcionará, teóricamente, en cualquier procesador. En realidad, la orden xsl:for-each la veremos más adelante.

Ejercicios
1. Hacer un template que sume números desde 1 hasta el número indicado. Por ejemplo, si se le llama con el 10, sumará todos los números de 1 a 10. El número se pasará en el atributo de una etiqueta XML. Hacerlo usando recursión primero, y luego usando extensiones del procesador XSLT.

Contenido de esta sección
  • Funciones XPath
  • xsl:variable
  • Bucles y node-sets

El jefe ya está un poquitín más contento, la encuesta sale chuli y tal, pero, puntilloso como siempre, nos indica que toda encuesta que se precie tiene los numeritos de la pregunta delante de las preguntas mismamente. De nada sirve explicarle la ausencia de bucles for (o casi ausencia) en XSLT. Si hay que poner numeritos, hay que poner numeritos. Además, la encuesta tal cual tiene buena pinta, pero en realidad no sirve para nada, porque cada campo no tiene nombre propio ni nada de eso. O sea, que hay que ponerle nombre propio, para poder procesarlo de verdad como un formulario. Como el jefe no dice que haya que hacerlo de verdad, ya veremos como lo haremos luego. Y hacemos la siguiente hoja de estilo (encuesta4.xsl), de la cual ponemos sólo los sitios que más han cambiado.:

59 <xsl:template match="cuestion"> 60 <p><br/></p> 61 <table border="0" width="90%" bgcolor="#000000" cellspacing="0" cellpadding="0"> 62 <tr> 63 <td width="100%"> 64 <table border="0" width="100%" cellpadding="4"> 65 <tr> 66 <td width="100%" colspan='2' bgcolor="#e0e0e0"> 67 <big> <xsl:value-of select="pregunta"/></big> 68 </td> 69 </tr> 70 <xsl:choose> 71 <xsl:when test='@tipo="multiple"'> 72 <xsl:variable name='pos' select='position()' /> 73 <xsl:variable name='varios' select='@varios' /> 74 <xsl:for-each select='.//respuesta'> 75 <tr> 76 <td width="100%" bgcolor="white"> 77 <xsl:value-of select='position()' />. <xsl:value-of select='.' /> 78 </td> 79 <td width="100%" bgcolor="white"> 80 <xsl:choose> 81 <xsl:when test='$varios=1'> 82 <input type='radio' name='radio{$pos}' /> 83 </xsl:when> 84 <xsl:otherwise> 85 <input type='checkbox' name='cb{$pos}' /> 86 </xsl:otherwise> 87 </xsl:choose> 88 </td> 89 </tr> 90 </xsl:for-each> 91 </xsl:when> 92 <xsl:when test='@tipo="numero"'> 93 <tr> 94 <td colspan ='2' width="100%" bgcolor="white"> 95 <select name='{concat("select",position())}'> 96 <xsl:call-template name='option-range'> 97 <xsl:with-param name='range' select='@rango'/> 98 </xsl:call-template> 99 </select> 100 </td> 101 </tr> 102 </xsl:when> 103 <xsl:when test='@tipo="rollete"'> 104 <tr> 105 <td colspan='2' width="100%" bgcolor="white"> 106 <input type='text' name='{concat("radio",position())}'/> 107 </td> 108 </tr> 109 </xsl:when> 110 <xsl:when test='@tipo="bool"'> 111 <tr> 112 <td bgcolor="white"> 113 Si 114 </td> 115 <td bgcolor="white"> 116 <input type='radio' name='{concat("radio",position())}'/> 117 </td> 118 </tr> 119 <tr> 120 <td bgcolor="white"> 121 No 122 </td> 123 <td bgcolor="white"> 124 <input type='radio' name='{concat("radio",position())}' /> 125 </td> 126 </tr> 127 </xsl:when> 128 </xsl:choose> 129 </table> 130 </td> 131 </tr> 132 </table> 133 </xsl:template>

Los resultados se pueden ver en forma de página.. En cualquier caso, una de las estructuras nuevas (o casi) está en la línea 74:<xsl:for-each select='.//respuesta'>, que introduce la orden xsl:for-each. Esta orden es lo más parecido a un bucle que tiene XSLT: realiza el código que le sigue (hasta el antitag correspondiente) aplicándolo una vez por cada nodo del node-set incluido en el atributo select. En este caso, usa un XPath que indica que se tienen que coger todos los tags respuesta que desciendan del tag actual (.), sean o no descendientes directos; aunque en este caso son todos descendientes directos; en este caso, serán todas las respuestas correspondientes a una pregunta determinada.

Previamente al bucle, en las líneas 72 y 73, se usa xsl:variable, que es bastante parecido a xsl:param hasta el punto que yo no sabría diferenciarlas... En realidad, se diferencian en cómo se les puede asignar un valor en el caso de que sean variables globales. A una xsl:variable se le puede asignar cualquier valor fuera de un template, pero a un xsl:param sólo se le puede asignar valores externos, procedentes de parámetros con los que se llama a la página. Además, las variables no se pueden usar para pasar valores a templates. En lo que sí se diferencian ambos de las variables "normales", es que su valor no se puede guardar entre una invocación y otra de un template; si se quiere conservar el valor de una variable, hay que llamar al template explícitamente. En este caso, usamos las variables para tomar valores que son dependientes del contexto, la posición y un atributo, que se pierden cuando se está procesando otro tag.

También en la línea 77 se usa una función XPath, position(), que devuelve la posición del nodo actual dentro del contexto que se está procesando, es decir, en este caso, el número de orden de la respuesta actual dentro de todas las respuestas que hay.

Las dos variables declaradas, varios y pos, se usan más adelante. varios se usa para generar diferentes tags HTML en el caso de que se permita respuestas múltiples (una novedad en esta hoja), o si no se permiten; en el primer caso se usarán checkboxes, y en el segundo botones de radio (se podía haber usado en ambos casos una combo box con la opción múltiple o no, pero bueno...). El nombre de los elementos del formulario se halla concatenando un prefijo tal como "cb" o "radio" a la posición de la pregunta en el contexto, multiplicada por dos (porque en realidad, procesamos uno de cada dos nodos). En todo caso, consigue lo que nos interesa: que cada elemento del formulario tenga un nombre único.

En la línea 95 se usa otra función XPath, concat, que aunque en realidad, que nos permite concatenar la posición del nodo al prefijo. Algo similar se usa en la línea 106. concat es una función que devuelve como salida una cadena que concatena todo lo que se le meta como entrada.

Ejercicios
1. A partir de un fichero XML con los equipos de fútbol tales como los que se han usado en ejercicios anteriores, devolver otro fichero XML que contenga sólo los equipos que tengan más goles a favor que en contra.
2. A partir de un fichero XML con direcciones tales como los que se han usado en ejercicios anteriores, devolver sólo aquellos cuyo número de teléfono comience por 958. Usar la función contains.

Contenido de esta sección
  • Procesando con XSL como si fuera un CGI
  • Algunas extensiones Xalan para escribir ficheros, y lo mismo en XSLT 1.1

En fin, que el jefe que dice que vale, que el formulario está perfecto, pero a ver, tanto XML y tanta gaita gallega, pero al final el formulario habrá que procesarlo usando PERL o algún otro lenguaje serio. Así que, para demostrarle que no tiene razón, tendremos que procesar también el formulario usando XSLT, aunque sea nada más que por eso. Y lo hacemos sobre el siguiente fichero de encuesta (encuesta5.xml):

<encuesta>
  <cuestion tipo='multiple'>
    <pregunta>¿Eres un listo?    </pregunta>
    <respuesta>Si    </respuesta>
    <respuesta>No    </respuesta>
    <respuesta>Lo que diga mi señora    </respuesta>
    <respuesta>La gallina    </respuesta>
  </cuestion>
  <cuestion tipo='multiple'>
    <pregunta>¿Porque pierdes tu tiempo contestando encuestas?    </pregunta>
    <respuesta>Porque no tengo nada mejor que hacer    </respuesta>
    <respuesta>Porque me lo ha dicho mi señora    </respuesta>
    <respuesta>A tí te lo voy a decir, so listo    </respuesta>
    <respuesta>La gallina    </respuesta>
  </cuestion>
  <cuestion tipo='multiple'>
    <pregunta>¿Cómo considera la coyuntura actual de la mejora de las prestaciones en orden a una europeización más completa?    </pregunta>
    <respuesta>Epatante    </respuesta>
    <respuesta>No contestaré a preguntas excluyentes    </respuesta>
    <respuesta>La considero positivante y conducente a unas relaciones más normalizadas con el conjunto de los agentes socioeconómicos    </respuesta>
    <respuesta>La gallina    </respuesta>
  </cuestion>
</encuesta>

Esta encuesta la procesaremos con la siguiente hoja de estilo (encuesta5.xsl), de la cual sólo mostramos las líneas en las que se diferencia con la anterior:

1 <?xml version="1.0" encoding='ISO-8859-1'?>
2 3 <xsl:stylesheet version="1.0" 4 xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 5 xmlns:redirect='org.apache.xalan.xslt.extensions.Redirect' 6 extension-element-prefixes="redirect"> 7 8 <xsl:param name='radio2'></xsl:param> 9 <xsl:param name='radio4'></xsl:param> 10 <xsl:param name='radio6'></xsl:param> 11 12 <xsl:template match='/'> 13 <xsl:choose> 14 <xsl:when test="$radio2"> 15 <xsl:call-template name='procesa-encuesta'> 16 </xsl:call-template> 17 </xsl:when> 18 <xsl:otherwise> 19 <xsl:apply-templates /> 20 </xsl:otherwise> 21 </xsl:choose> 22 </xsl:template> 23 24 <xsl:template name="procesa-encuesta"> 25 <xsl:processing-instruction name="cocoon-format">type="text/html"</xsl:processing-instruction> 26 <redirect:write select='"/tmp/encuesta.resultados.1"'> 27 Resultados: 28 <xsl:value-of select='$radio2' /> - 29 <xsl:value-of select='$radio4' /> - 30 <xsl:value-of select='$radio6' /> 31 </redirect:write> 32 <html> 33 <head> 34 <title>Procesando encuesta</title> 35 </head> 36 <body> 37 <h1>Procesando encuesta</h1> 38 39 <table border="0" width="80%" bgcolor="#000000" cellspacing="0" cellpadding="0"> 40 <tr> 41 <td width="100%"> 42 <table border="0" width="100%" cellpadding="4"> 43 <tr> 44 <td width="100%" bgcolor="#c0c0c0" align="right"> 45 <big><big>Encuesta chuli</big></big> 46 </td> 47 </tr> 48 <tr> 49 <td width="100%" bgcolor="#ffffff" align="center"> 50 <xsl:for-each select='.//cuestion'> 51 <table border="0" width="90%" bgcolor="#000000" cellspacing="0" cellpadding="0"> 52 <tr> 53 <td width="100%"> 54 <table border="0" width="100%" cellpadding="4"> 55 <tr> 56 <td width="100%" bgcolor="#e0e0e0"> 57 <big> <xsl:value-of select="pregunta"/></big> 58 </td> 59 <td width="100%" bgcolor="white"> 60 <xsl:choose> 61 <xsl:when test='position()=1'> <xsl:value-of select='$radio2' /> </xsl:when> 62 <xsl:when test='position()=2'> <xsl:value-of select='$radio4' /> </xsl:when> 63 <xsl:when test='position()=3'> <xsl:value-of select='$radio6' /> </xsl:when> 64 </xsl:choose> 65 </td> 66 </tr> 67 </table> 68 </td> 69 </tr> 70 </table> 71 <p><br/></p> 72 73 </xsl:for-each> 74 </td> 75 </tr> 76 </table> 77 </td> 78 </tr> 79 </table> 80 </body> 81 </html> 82 </xsl:template> 83

En realidad, estamos haciendo trampa; lo cierto es que XSLT, sólo, no puede realizar cosas tan potentes como un CGI o cualquier otro método de procesamiento en el servidor, tales como los JSPs; XSLT está enfocado sólo a transformaciones de documentos XML, y por eso, tenemos que forzar un poco el estándar usando extensiones. En este caso, sería mucho más adecuado usar XSP, por ejemplo, que permite incluir programas en Java (u otro lenguaje) dentro de un documento XML, o incluso las extensiones Xalan, que permiten incluir JavaScript dentro del documento.

En este caso, si queremos generar la página usando Xalan, hay que añadir una librería más al CLASSPATH:

export CLASSPATH=$CLASSPATH:/usr/local/xalan-j_1_2_2/bsf.jar

(sustituyendo /usr/local/xalan... por dondequiera que esté instalado Xalan). La librería que se añade es la que contiene el Bean Scripting Framework, que permite usar diferentes lenguajes dentro de la máquina virtual Java; las extensiones de Xalan están escritas usando BSF, por eso hay que hacerlo. Y una vez hecho, se puede generar la página siguiente (encuesta5.htm).

Por eso, para empezar, a partir de la línea 3, se tienen que declarar las extensiones de Xalan en la declaración de la hoja de estilo. En este caso se declara un namespace redirect, que será el prefijo que usen las extensiones que se van a usar, y se indica que corresponde a una clase determinada dentro del conjunto de clases en Java que incluye Xalan. Estas extensiones se usarán más adelante para poder escribir en disco desde una hoja de estilo.

Más adelante, en las líneas 8,9 y 10, se tienen que declarar los parámetros del formulario que se van a usar. En este caso, el formulario que se ha creado tiene 3 elementos: radio2, 4 y 6. A la vez, se inicializan con un valor nulo; en la misma sentencia se le podría asignar un valor por defecto, simplemente metiéndolo dentro de los tags. En nuestro caso, no nos conviene, porque vamos a usarlo para saber si el fichero XML se está procesando "de primeras" o no, o se está procesando después de rellenar el formulario. Hay otra alternativa a declarar esas variables, y es usar las taglibs que vienen con el Cocoon; en concreto, request:get-parameter-names. Una vez más, no son universales, o sea que no van a funcionar en todos los sistemas, aunque probablemente haya algo muy similar en el resto de los sistemas.

Si se está usando Xalan desde la línea de comandos, se le pueden incluir también los parámetros de la forma siguiente:

java org.apache.xalan.xslt.Process -in encuesta5.xml -xsl encuesta5.xsl -out encuesta5-form.htm -param radio2 3 -param radio4 5 -param radio6 3

que daría como salida un fichero HTML (encuesta5-form.html), y a la vez crea el fichero de salida, como veremos más adelante.

De hecho, eso es lo que se intenta dilucidar en las líneas a partir de la 12. Esta hoja de estilo sirve a la vez para presentar la encuesta en forma de formulario, y para procesar el formulario una vez rellenado; y todo ello sobre el mismo documento XML y usando la misma hoja de estilo XSLT. ¡Toma ya!

El template que comienza en la línea 12 comprueba si existe alguno de los elementos del formulario; si existe, llama al template procesa-encuesta, si no, aplica los templates de toda la vida, empezando por el raíz.

Pasamos entonces al nuevo template, procesa-encuesta, que para empezar, inserta en la salida la instrucción de siempre, que le dice al Cocoon que tiene que servir la página como HTML, y luego viene lo bueno: la instrucción específica de Xalan redirect:write, que hace lo que es de esperar: escribe un fichero con el nombre que se le pasa en el atributo select. El nombre está entrecomillado para indicar que se trata de una cadena, no de un XPath o algo peor; el fichero se escribirá en el directorio /tmp, que está en todas las distribuciones Unix con permisos de escritura. Pero, ojo, no quiero decir que uséis esto en un entorno de producción: podría ser muy peligroso. En tal caso sería mejor que usárais una base de datos, con las autorizaciones adecuadas. En el fichero se escribe lo que hay entre los dos tags, es decir, "Resultados " y el valor de los parámetros. Saldrá algo así:

<?xml version="1.0" encoding="ISO-8859-1"?> Resultados: 3 - 5 - 3

con su orden XML al principio y tó, tan mona. Si os dáis cuenta, hemos tenido que hacer una verdadera chapuza: tenemos que poner de uno en uno los valores de los parámetros; y además, solo admitimos un tipo de parámetro, el más fácil de procesar. En realidad, lo cierto es que XSLT no es tan potente, para este tipo de cosas, como un CGI, al menos sin extensiones.

A continuación, a partir de la línea 50, imprimimos también cada una de las preguntas y la respuesta correspondiente en la página HTML que se muestra al usuario; usamos for-each y el XPath .//cuestion, que forma un node-set con todas las etiquetas cuestion que desciendan del nodo actual. Para seleccionar la respuesta a esa pregunta que se va a imprimir, se hace un truco un tanto sucio en la línea 60: con un xsl:choose, se mira si la posición de la pregunta es la primera, segunda o tercera, usando la función de XPath position(), que ya hemos visto anteriormente, y se selecciona el parámetro correspondiente. Y es una chapuza porque lo suyo es que se carculara, pero es muy difícil convertir el contenido de una variable en parte del nombre de un nodo, salvo que se usen extensiones, así que lo dejamos como está.

En otros procesadores XSLT, habrá que usar otras alternativas. Y si usamos un procesador XSL que siga la especificación XSLT 1.1, se puede usar xsl:document de esta forma:

<xsl:document href="encuesta_resultados_s.1"> Resultados: <xsl:value-of select='$radio2' /> - <xsl:value-of select='$radio4' /> - <xsl:value-of select='$radio6' /> </xsl:document>

La última versión de Saxon, usa esa orden. Probablemente, en siguientes versiones de Xalan (y por tanto de Cocoon) se incluya también. Para usar los parámetros desde Saxon, y obtener el mismo resultado, hay que escribir

java com.icl.saxon.StyleSheet -o encuesta5-saxon.html encuesta5.xml encuesta5-saxon.xsl radio2=3 radio4=6 radio6=1

Es decir, se le pasan los parámetros de la forma parametro=valor y ya está. El resultado es exactamente el mismo.

En realidad, lo más adecuado sería usar XSP, JSP o un servlet para procesarla. Pero bueno, por lo menos hemos probado lo que se puede intentar hacer.

Ejercicios
1. Sobre cualquier documento XML, hacer una hoja de estilo que tome como parámetro un XPath y presente ese XPath del documento original.
2. Sobre una encuesta con el formato anterior, hacer una hoja de estilo que tenga dos parámetros, el número de pregunta y el número de respuesta, y escribir ese número de pregunta y respuesta. Habrá que construir un XPath a partir de los parámetros.
3. A partir de un documento XML que contenga una tabla HTML, hacer una hoja de estilo, que, según el valor de un parámetro, escriba sólo el valor de la primera columna o sólo el contenido de la tabla, sin etiquetas.

Esta es la bibliografía que ha aparecido hasta el momento sobre el tema, especialmente en castellano

Hay también algunos tutoriales bastante buenos en inglés

Este tutorial es (c) Juan J. Merelo Guervós. Se podrá reproducir en todo o en parte, siempre que se mantenga un enlace al sitio original.