Página principal del curso de XML
Usando hojas de estilo para generar HTML
Otros cursos y tutoriales: comercio electrónico, WAP, Webmaster
Página principal del grupo GeNeura

Generación de documentos WML usando XSLT y XML

J. J. Merelo

Introducción

Para empezar habría que plantearse la necesidad de generar páginas WML a partir de XML, en vez de servirlas directamente. Y se pueden dar las mismas razones que para generar páginas HTML: XML es un formato de almacenamiento de información mucho más potente, en el cual los datos están descritos por etiquetas que indican su semántica. Además, el principal problema de los dispositivos WAP es que no son uno solo, sino muchos, cada uno con su geometría, capacidad de memoria, y capacidades: unos admiten tablas, otros no, algunos admiten WMLscript, otros no, y algunos incluso admiten Java o HTML. Es más, lo normal es que la información se quiera servir no sólo a dispositivos móviles, sino a cualquier otro tipo: navegadores en ordenadores normales, navegadores para palmtop, y que se quiera mantener una sola fuente de información.

Por eso, lo más natural es tener una sóla fuente de información, generalmente una base de datos, a partir de la cual se modelen los datos en un dialecto XML adaptado a la aplicación (noticias, cotizaciones en bolsa), y que sea un sistema de publicación XML tal como el Cocoon el que se encargue de convertir el XML al formato final, sea HTML o WML, o incluso otra forma de XML, en el caso de transacciones B2B (business to business) entre dos sistemas que entiendan XML.

En este tutorial se van a ver las nociones básicas sobre como convertir XML en WML usando las hojas de estilo XSLT. No pretende ser un tutorial completo; si quieres saber mucho más sobre el tema, consulta el tutorial de XSLT, donde se tratan de todos los medios necesarios para transformar XML. En este tutorial usaremos el Xalan 2.0, publicado en febrero del 2001, que incluye las últimas especificaciones en XSLT: un API para transformaciones sobre XML (TrAX), SAX 2, DOM level 2, y JAXP 1.0.

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

Hojas de estilo básicas

Para empezar, vamos a tratar de presentar una hoja XML que representa una encuesta (encuesta1-wap.xml): <?xml version="1.0" encoding='ISO-8859-1'?> <?xml-stylesheet href="encuesta1-wap.xsl" type="text/xsl"?> <?cocoon-process type="xslt"?> <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> </encuesta>

Para convertirlo en WML, usaremos la siguiente hoja de estilo (encuesta1-wap.xsl):

1 <?xml version="1.0" encoding='ISO-8859-1'?>
2 <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
3 <xsl:output doctype-public="-//WAPFORUM//DTD WML 1.1//EN" doctype-system="http://www.wapforum.org/DTD/wml_1.1.xml" /> 
5 <xsl:template match="encuesta">
7 <wml>
8   <template>
9   <do type="prev" name="prev" label="anterior">
10     <prev/>
11   </do>
12   <do type="accept" name="next" label="ultima">
13     <go href='#c2' />
14   </do>
15   </template>
16  <card id='titulo' title='Encuesta chuli'>
17 <p> <strong>Encuesta generada usando encuesta1-wap.xsl</strong><br />
18  <a href='#c1'>Comienzo encuesta</a>
19 </p>
20  </card>
21  <xsl:apply-templates />
22 </wml>
23 </xsl:template>
25 <xsl:template match="cuestion">
26  <xsl:param name='pos'><xsl:number /></xsl:param>
27  <card id='c{$pos}' title='pregunta{$pos}'>
28 <p>   <b> <xsl:value-of select="pregunta"/></b> </p>
29    	<xsl:apply-templates select='respuesta'/>
30  </card>
31 </xsl:template>
32 
33 <xsl:template match='respuesta'>
34         <p>*<xsl:value-of select="."/></p>
35 </xsl:template>
38 </xsl:stylesheet>

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 encuesta1-wap.xml -XSL encuesta1-wap.xsl -OUT encuesta1.wml.

[Encuesta1-wap en el DeckIt][Encuesta1-wap-2 en el DeckIt]

Esto debería dar como resultado algo como lo que hay en la imagen, usando el Deckit, un emulador de terminal WAP para Linux.

La primera hoja de estilo con la que nos enfrentamos es relativamente simple. El "esqueleto" es un documento WML normal, al cual tenemos que añadir "contenido dinámico", es decir, contenido generado a partir del XML original. 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 (encuesta), las etiquetas estén emparejadas correctamente y los atributos entre comillas.

Para empezar, se incluyen una serie de instrucciones, de la línea 1 a la 3: 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 (necesitamos que sea ISO-8859-1 para poder incluir acentos y demás caracteres idiosincráticos), la segunda es la etiqueta raíz de la hoja de estilo (cerrada en la última línea), mientras que la tercera el tipo de documento que necesitan los terminales WAP.

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. En este caso, el template que corresponde a la etiqueta raíz genera un esqueleto de baraja WML, con una tarjeta principal, que contiene un enlace a la primera de las tarjetas. La "orden" en la línea 21 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. Lo que se hará es incluir una tarjeta más por cada cuestion, que es lo que pretende el template que comienza en la línea 25.

En ese template, se declara una variable en XSL: un param, en la línea 26; y se le asigna un contador; xsl:number devuelve un número que aumenta de valor cada vez que se usa; lo usaremos para generar un número de pregunta, que luego servirá también para generar atributos de cada tarjeta. El valor de esa variable se usa en la línea 27, con el $ delante: $pos devuelve el valor de la variable, y para evaluarlo, tenemos dos opciones: si estamos dentro de alguna etiqueta, se rodea por llaves (como en la línea 27), si no, se puede usar xsl:value-of select='$variable', tal como en la línea 8. En general, xsl:value-of sirve para extraer el valor de un camino completo dentro del árbol que forma el documento XML, pero no nos vamos a preocupar de eso: en la línea 27 se usa para extraer el contenido de una etiqueta que desciende de la actual: pregunta.

En la línea 21 se aplica un template, pero de forma selectiva: solamente a aquellos que tengan la etiqueta respuesta. Según vayan apareciendo en el documento, se incluirán en el documento de salida la salida generada por este template, que empieza en la línea 33. En la línea 34 se incluye el valor de ., es decir, el valor del contenido de la etiqueta que se está procesando, en este caso respuesta

Ejercicios
1. A partir de una libreta de direcciones en XML, con nombre, dirección y teléfono, crear una baraja WML con sólo los nombres, encerrados dentro del tag <NAME>.

Contenido de esta sección
  • Atributos
  • xsl:choose/xsl:when
  • xsl:variable

Generando formularios

La encuesta que se ha usado anteriormente, en realidad, es poco operativa: habrá que incluir efectivamente los medios de que el usuario pueda contestar a la encuesta. Este manual no va a abarcar cómo se va a procesar la encuesta, si se quiere aprender cómo hacerlo, es mejor mirar tutorial de XSLT o bien el manual de CGIs desde WML. En este manual veremos solamente cómo generar los formularios a partir de una hoja XML que los especifique. En concreto, usaremos el siguiente fichero (encuesta2-wap.xml): <?xml version="1.0" encoding='ISO-8859-1'?> <?xml-stylesheet href="encuesta2.xsl" type="text/xsl"?> <?cocoon-process type="xslt"?> <encuesta>   <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='bool'>     <pregunta>¿Me quieres?    </pregunta>   </cuestion>   <cuestion tipo='rollete'>     <pregunta>¿Algo más que añadir?    </pregunta>   </cuestion> </encuesta>

Este fichero usa las mismas etiquetas que el anterior, salvo que en algunos casos, están cualificadas por el atributo "tipo": en las "multiples" se entiende que hay que elegir una respuesta entre varias; mientras que en las bool hay que contestar sí o no; finalmente, en las tipo rollete hay que escribir texto libre. Para cada uno de ellos usaremos diferentes tipos de elementos del formulario WML: para los dos primeros usaremos select, mientras que para la última usaremos input. Así se hace en la siguiente logicsheet encuesta2-wap.xsl: 14 <xsl:apply-templates select='cuestion'/> 15 </wml> 16 </xsl:template> 17 18 <xsl:template match="cuestion"> 19 <xsl:param name='pos'><xsl:number /></xsl:param> 20 <card id='c{$pos}' title='pregunta{$pos}'> 21 <small>-<xsl:value-of select='$pos' />. <b> <xsl:value-of select="pregunta"/></b> <br/> 22 <xsl:choose> 23 <xsl:when test='@tipo="multiple"'> 24 <select name="rc{$pos}" title="respuestas{$pos}"> 25 <xsl:apply-templates select='respuesta'/> 26 </select> 27 </xsl:when> 28 29 <xsl:when test='@tipo="rollete"'> 30 <input type='text' name='rr{$pos}'/> 31 </xsl:when> 32 <xsl:when test='@tipo="bool"'> 33 <select name="rb{$pos}" title="sino{$pos}"> 34 <option value="si">Si</option> 35 <option value="no">No</option> 36 </select> 37 </xsl:when> 38 </xsl:choose> 39 </small> 40 <xsl:variable name='siguiente'><xsl:value-of select='$pos+1' /></xsl:variable> 41 <do type='accept' label='siguiente'> 42 <go href='#c{$siguiente}' /> 43 </do>

[Encuesta2-wap en el YoSpace][Encuesta2-wap-2 en el Opera]

Esto debería dar como resultado el fichero encuesta2.wml, que se puede ver en las imágenes anteriores usando el YoSpace, un emulador de terminal WAP para Windows; el mejor en muchos aspectos.

El principio y el último template son exactamente igual que en la primera logicsheet; por eso lo eliminamos y dejamos solamente las líneas que incluyen estructuras nuevas. En concreto, a partir de la línea 22 se usa xsl:choose, una orden que permite incluir en la salida diferente texto dependiendo del resultado de los tests que le siguen, que usan xsl:when test. En este caso, lo que se prueba es el valor del atributo tipo: @tipo, para usar diferentes elementos de formulario tal como se ha visto anteriormente. Tal como lo requiere el estándar WML, cada elemento tiene su nombre, generado a partir del número de cuestion, generado como en el ejemplo anterior

.

Además, se usa xsl:variable en la línea 40, para generar el número de la siguiente tarjeta, y un enlace a la misma; las variables en XSLT son similares a los parámetros en cuanto a su uso, pero son las únicas que se pueden usar en el cuerpo de un template; los parámetros sólo se pueden declarar al principio de un template o de la logicsheet. En este caso se usa para generar el número de orden de la tarjeta siguiente, usando una operación en el atributo select de xsl:value-of.

Ejercicios
1. Crear una baraja-anillo, es decir, una baraja de cartas en la que cada una apunte a la siguiente, y la última a la primera. El contenido del fichero XML puede ser cualquiera.
2. Crear una baraja de cartas que apunten unas a las otras, a partir de un fichero XML de la forma siguiente: <baraja> <carta id='carta1'> <apunta>carta2 </apunta> <apunta>carta4 </apunta> </carta>. Como contenido de cada carta, aparte de los enlaces, se pondrá el "id" de la carta.

Contenido de esta sección
  • xsl:for-each
  • Un poco de XPath
  • xsl:sort

Tratando con datos tabulares

A veces la información contenida en el fichero XML se debe procesar de diferentes formas a la hora de presentarla; por ejemplo, es habitual en datos tabulares generar primero un índice de los datos, que aparecería como primera carta de la baraja, y luego cada uno de los elementos de la tabla. Por ejemplo, si tenemos la clasificación de fútbol en formato XML de la forma siguiente (futbol-wap.xml): <?xml version="1.0" encoding='ISO-8859-1'?>
<?xml-stylesheet href="encuesta2.xsl" type="text/xsl"?>
<?cocoon-process type="xslt"?>
<clasificacion>
  <equipo id='ba'>
    <nombre>Balompédica Alpedrete    </nombre>
     <gf>55     </gf>
     <gc>33     </gc>
     <puntos>28     </puntos>
  </equipo>
  <equipo id='sffc'>
    <nombre>Sant Feliù FC    </nombre>
     <gf>44     </gf>
     <gc>43     </gc>
     <puntos>22     </puntos>
  </equipo>
  <equipo id='rp'>
    <nombre>Real Polopos    </nombre>
     <gf>22     </gf>
     <gc>77     </gc>
     <puntos>3     </puntos>
  </equipo>
</clasificacion>

Que se va a procesar con la siguiente logicsheet (futbol-wap.xsl): 1 <?xml version="1.0" encoding='ISO-8859-1'?> 2 <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> 3 <xsl:output doctype-public="-//WAPFORUM//DTD WML 1.1//EN" doctype-system="http://www.wapforum.org/DTD/wml_1.1.xml" 4 encoding='utf-8'/> 5 <xsl:preserve-space elements='*' /> 6 <xsl:template match="clasificacion"> 7 <wml> 8 <template> 9 <do type="prev" name="prev" label="anterior"> 10 <prev/> 11 </do> 12 <do type="accept" name="next" label="menu"> 13 <go href='#principal' /> 14 </do> 15 </template> 16 <card id='principal' title='El Jurbo'> 17 <p><small> <strong>Clasificación división X</strong></small></p> 18 <xsl:for-each select='equipo'> 19 <p><a href='#{@id}'><small><xsl:value-of select='nombre' /></small> </a></p> 20 </xsl:for-each> 21 </card> 22 <xsl:apply-templates select='equipo'> 23 <xsl:sort select='puntos' data-type='number'/> 24 </xsl:apply-templates> 25 </wml> 26 </xsl:template> 27 28 <xsl:template match='equipo'> 29 <card id='{@id}' title='{nombre}'> 30 <p> GF <xsl:value-of select='gf' /> <br /> 31 GC <xsl:value-of select='gf' /> <br /> 32 <i>Puntos</i> <xsl:value-of select='puntos' /> </p> 33 </card> 34 </xsl:template> 35 36 </xsl:stylesheet>

[futbol en el deckit][otro futbol en el deckit]

Lo cual da un resultado como el de las imágenes (y el fichero futbol.wml).

Aparte de diferentes elementos de formateo, en esta logicsheet se ve por primera vez la orden for-each, que es lo más parecido a un bucle que hay en XSLT. En realidad, esta orden recorre uno por uno cada uno de los nodos del nodeset que hay en su atributo select. Un nodeset es un conjunto de nodos expresados por un XPath. La forma más simple es poner el nombre de una etiqueta, como en este caso, que dará como resultado todos los "hijos" de la etiqueta actual que tengan esa etiqueta; pero ese XPath se puede cualificar, por ejemplo, de la forma siguiente: equipo[0] sería el primer hijo, y el camino completo hasta el primer hijo se expresaría como clasificacion/equipo/puntos, por ejemplo. Para ver una introducción mucho más completa, consultar el tutorial de XPath de Víctor Rivas. En resumen, el cuerpo de ese bucle se va a repetir una vez para cada etiqueta de ese tipo, y . contendrá el texto de la etiqueta que se está procesando actualmente. En la línea 19 se usan además @id y nombre, que se referirán a características (atributos y nodos hijos) del nodo actual. En resumen, se imprimirá el nombre del equipo, rodeado por un enlace a la tarjeta correspondiente, cuyo ID se extrae del ID del equipo.

En cuanto al procesamiento de las tarjetas en sí, se hace a partir de la línea 22. La principal diferencia en este caso con respecto a la aplicación de templates que se había hecho anteriormente es que se clasifican los equipos según su puntuación: para ello, se usa xsl:sort; en el campo select se incluye el XPath que se va a usar para ordenar, en este caso la puntuación contenida en la etiqueta puntos; y en el atributo data-type se indica el tipo de clasificación que se va a hacer, numérica (number) o alfabética text). Si no se especifica nada más, se ordena de mayor a menor; pero se puede cambiar de la forma siguiente: <xsl:sort select='puntos' data-type='number' order='descending' />

[futbol-wap.xml procesado
con hoja alternativa]

En cuanto al siguiente template, que comienza en la línea 29, lo que hace es que incluye una carta nueva para cada equipo, usando como ID el ID del equipo, como título el nombre, y como contenido las tres etiquetas hijas del nodo (líneas 30-32). De hecho, esto se podría hacer de otra forma (futbol-wap-2.xsl): <p> <xsl:for-each select='gf|gc|puntos' > 31 <xsl:value-of select='name()' />: <xsl:value-of select='.' /> <br /> 32 </xsl:for-each> </p> En en este caso ponemos solamente las líneas que cambian. Lo que se incluye en el atributo de for-each son diferentes alternativas: el nodeset generado contiene a todas las etiquetas que coincidan con alguna de las puestas (el | correspondería a un "OR" lógico). En la linea 31, la función name() devuelve el nombre de la etiqueta que se está procesando, mientras que . devuelve el contenido. Así se obtiene lo visto en futbol2.wml, o en la imagen.

Ejercicios
1. A partir de una tabla HTML, crear una baraja que incluya en la primera carta la primera columna y una columna en cada una de las otras.

Bibliografía y enlaces relacionados con XSLT

Hay un par de artículos que apoyan el punto de vista de este tutorial, pero están en inglés:

No hay libros específicos sobre generación de WML a partir de XML, pero algunos libros dedican capítulos al tema: