.. highlight:: xslt .. _xslt: ****** |XSLT| ****** Introducción ************ ¿Qué es |XSLT|? =============== |XSLT| (en castellano, algo así como lenguaje para transformaciones basadas en hojas de estilos extensibles) es un lenguaje de programación basado en |XML| que permite traducir entre distintos dialectos |XML| o entre un dialecto |XML| y texto plano. Por lo general, la transformación se lleva a cabo hacia un formato que tenga definida una visualización (p.e. |HTML|). También puede generarse un documento |PDF|, pero en este caso la conversión que obra |XSLT| se lleva a cabo hacia |XSL-FO| y es desde este dialecto donde se hace una última transformación hacia |PDF|. |XSLT| es un dialecto |XML|, pero también un lenguaje de programación, puesto que se escribe para definir las reglas de transformación que deben aplicarse para realizar la conversión entre dos dialectos |XML| distintos. Así pues, una vez escrito, se toma este mismo documento |XSLT|, el documento |XML| a transformar y un procesador |XSLT|, que se encarga de hacer la transformación del |XML| según las reglas del |XSLT| para obtener la salida: .. image:: files/xslt-proceso.png Establecido que |XSLT| es un lenguaje de programación, queda reseñar cuáles son sus características: * Tiene una sintaxis |XML|, de manera que sus instrucciones (estructuras de flujo, funciones, etc.) son elementos |XML|. * Sigue, más o menos, el paradigma de la programación declarativa funcional, de modo que no hay variables y los bucles están muy restringidos. Sin embargo, carece de la propiedad fundamental de que las `funciones sean ciudadanos de primera clase `_. * Es un lenguaje basado en la coincidencia de patrones de texto, como `awk `_ por ejemplo. Dicho más extensamente, a diferencia de un lenguaje de programación imperativa, el flujo natural del programa no es un listado de instrucciones que se ejecuta de modo secuencial, sino que el programa se compone de bloques de código (elementos) que declaran cómo se debe transformar cada elemento del |XML| original. El procesador se encarga de comprobar qué bloque es aplicable a cada nodo |XML| del documento original. .. rubric:: Versiones De |XSLT| existen tres versiones: * La **1.0** publicada en 1999, que usa *XPath* 1.0 y es la más extendida y aún hoy se sigue utilizando. Para esta versión se definieron extensiones |EXSLT| que complementan el estándar y permiten hacer más transformaciones. En estos apuntes se tratará esta versión por cuanto es la que única que, dentro del *software* libre, soporta la librería libxslt_ en **C**. que es la base que utilicen muchísimos programas y lenguajes de programación, entre ellos :program:`xmlstarlet`. * La **2.0** que usa *XPath* 2.0 y fue publicada en 2007. * La **3.0**, que usa *XPath* 3.0 o 3.1 y fue publicada en junio de 2017. `Saxon `_, procesor desarrollado en `Java `_, implementa esta versión. El problema es que al no estar desarrollado en **C** no se presta a ser tomado como base por programas (no escritos en *Java*) y lenguajes de programación. .. caution:: El apéndice desarrolla la versión **1.0** del estándar |XSLT|, a pesar de que el resto de los apuntes introducen con cierta profundidad *XPath* 3.1. El autor no ha estudiado la versión **3.0**, aunque por lo que puede leer en `esta comparación entre XSLT 1.0 y XSLT 2.0 `_, no hay diferencias significativas de concepto, sino que: * Los principales cambios en el comportamiento se deben a la interpretación de las expresiones *XPath*, que ya hemos discutido en el :ref:`epígrafe dedicado a XPath `. Por tanto, sabiendo cómo interpreta *XPath*, adivinaremos cómo se comporta |XSLT|. * Las nuevas funciones añadidas a *XPath* cubren las necesidades por la que nacieron las :ref:`extensiones EXSLT ` para la versión *1.0*. Así pues, no son necesarias por más tiempo. * Presenta algunos añadidos propios de |XSLT|. En definitiva, dar el salto de la versión *1.0* a la versión *3.0* no entraña dificultad alguna si ya se conoce *XPath* 3.1. .. _xslt-ejemplo: Ejemplo introductorio ===================== Antes de entrar en harina es más didáctico plantear un ejemplo y ver cuál es el aspecto del código |XSLT|. Supongamos el siguiente documento |XML|: .. literalinclude:: files/discos.xml :language: xml El documento tiene incrustado el |DTD| que lo valida: .. code-block:: console $ xmlstarlet val -E discos.xml discos.xml - valid Queremos crear una transformación, de suerte que obtengamos una listado de discos en texto plano con el siguiente aspecto: .. code-block:: console $ xslstarlet tr discos.txt.xsl discos.xml LISTA DE DISCOS + clasica - Nueve sinfonías (Ludwing van Beethoven) - El Mesías (G.F. Haendel) + jazz - Kind of Blue (Miles Davis) - Ella and Louis (Ella Fitzgerald/Louis Armstrong) + rock - Metalmorfosis (Barón Rojo) - Más madera (Leño) Pues bien, una posible solución para lograr tal transformación es la siguiente: .. literalinclude:: files/discos.txt.xsl :language: xslt :linenos: Es aún prematura entender con perfección el código, pero es muy pertinente analizarlo superficialmente para entender cómo funciona el procesador, esto es, cómo lee las reglas para generar la transformación: #. Se define una plantilla para el nodo :code:``, o sea, el nodo *raíz* \ [#]_. #. Hay definida otra plantilla para los nodos "disco" #. El procesador empieza su labor viendo si existe una plantilla para "/". Existe y, por ella, se desencadena la transformación. Tal plantilla genera el título "LISTA DE DISCOS" y aplica la plantilla a los sucesivos nodos disco, pero ordenados por el atributo "@tipo". #. La plantilla para "disco" se encarga de generar los ítem:: - Título (Artista) pero cuando el disco es el primero de su tipo (o, leyendo literalmente, cuando no hay otro nodo hermano anterior que sea del mismo tipo que el tipo del nodo que estamos tratanto), además, se genera la etiqueta:: + tipo_de_musica .. note:: Aunque sea muy prematuro entenderlo, esta solución tiene un grave defecto: vuelve muy complicado hacer numerada la lista de tipos de música, en vez de despacharla con un "+". :ref:`Más adelante ` se ensaya otra solución que sí lo permite sin ser más complicada. Supongamos, en cambio, que nuestra intención es generar una tabla |HTML| (en realidad |XHTML| que es su equivalente |XML|) como la siguiente: .. image:: files/output-html.png La estrategia para la resolución es la: la plantilla del nodo "/" creará el envoltorio, esto es, los elementos ````, ````, ```` y ````; y la plantilla de cada elemento disco deberá crear cada fila ````. Además, al igual que en el caso anterior, cuando el disco sea el primero de tu tipo deberá generar la celda que indica el tipo de música con una altura igual al número de discos que haya de ese tipo: .. literalinclude:: files/discos.html.xsl :language: xslt :linenos: .. rubric:: Nota pedagógica Se pueden introducir el ejemplo que genera la lista de texto, creando sucesivamente los |XSLT| que generan los siguientes resultados: .. code-block:: console $ xmlstarlet tr discos.txtv1.xsl discos.xml LISTA DE DISCOS - clasica: Nueve sinfonías (Ludwing van Beethoven) - jazz: Kind of Blue (Miles Davis) - rock: Metalmorfosis (Barón Rojo) - rock: Más madera (Leño) - jazz: Ella and Louis (Ella Fitzgerald/Louis Armstrong) - clasica: El Mesías (G.F. Haendel) $ xmlstarlet tr discos.txtv1.xsl discos.xml LISTA DE DISCOS - clasica: Nueve sinfonías (Ludwing van Beethoven) - clasica: El Mesías (G.F. Haendel) - jazz: Kind of Blue (Miles Davis) - jazz: Ella and Louis (Ella Fitzgerald/Louis Armstrong) - rock: Metalmorfosis (Barón Rojo) - rock: Más madera (Leño) Pueden descargarse :download:`desde aquí ` estas transformaciones intermedias. .. seealso:: Para saber cómo realizar transformación |XSLT| usando :program:`xmlstartlet` consulte :ref:`el apéndice dedicado a ello `. .. _xslt-expresiones: Expresiones *********** Muchos de los elementos de |XSLT| permiten incluir atributos cuyo valor puede ser un literal o una expresión :ref:`XPath `, dentro de la cual, además de sus propias funciones, podrán usarse :ref:`funciones de XSLT ` y :ref:`funciones de EXSLT `. Usar estas expresiones dentro un atributo |XML| provoca que haya algunas particularidades a la hora de escribirlas. Fijemos nuestra vista en el elemento |value-of|, en cuyo atributo ``select`` se incluyen estas expresiones. La expresión de una cadena literal debe ir incluida entre comillas:: Ya es sabido que se debe rodear el valor de un atributo con comillas y que es indiferente que estas sean simples o dobles. Pues bien, las comillas que determinan que el valor es una cadena (simples en este caso) deben ser las que no se usan para rodear el valor (dobles en este caso). Para obtener el valor de un elemento o un atributo, basta con usar la expresión *XPath* apropiada:: Esto devuelve el valor del atributo tipo del elemento que se esté procesando. Cuando se realizan comparaciones, es importante reseñar que no se pueden usar los caracteres "<" y ">" directamente, sino que habrá que usar las entidades "<" y ">":: Recuérdese, además, que *XPath* dispone de sus propias funciones, que pueden usarse en todas estas expresiones. pero que libxml2_ (y, por tanto, :program:`xmlstarlet`) sólo soporta *XPath* **1.0**. Además, puede usarse la :ref:`notación de llaves ` con las expresiones *XPath* cuando se desea evaluarlas dentro de los atributos de los elementos del |XML| de salida. .. note:: Es preciso aquí, hacer un inciso. aunque gracias a :ref:`XPath ` sabemos qie una cadena se define incluyendo el valor entre comillas, y un número con un número a secas, definir un valor bopleano no es algo que se haya practicado hasta ahora, pero es posible que sea necesario a partir de ahora. Se definen mediante las funciones :code:`true()` y :code:`false()`:: Elementos ********* |XSLT| es un lenguaje de programación con sintaxis |XML| y, por tanto, estudiar la sintaxis de |XSLT| es repasar qué elementos admite y que atributos soporta cada elemento. Cabecera ======== Un documentos |XSLT| es un documento |XML|. Por tanto, la primera línea debe ser la de todo documento |XML|:: .. _xsl_stylesheet: O cualquier otra variante válida. Escrita esta, podemos abrir nuestro elemento contenedor que, para el caso es ````: .. code-block:: dtd El elemento puede tener un identificador, pero es opcional y no muy común. Lo que sí es obligatorio es incluir la versión del |XSLT| que se está usando: **1.0**, **2.0** o **3.0**. En nuestro caso, siempre será **1.0***. Además, como todo elemento |XML|, puede tener la definición de su propio espacio de nombres (que suele ser *xsl*). Así pues, una apertura típica es:: Cuando se usan extensiones |EXSLT|, es necesario incluir el atributo ``extension-element-prefixes`` (y el espacio de nombres correspondiente). Por ejemplo, si se usan las extensiones para fecha y matemáticas:: .. _xsl_import: .. _xsl_include: Abierto el elemento |stylesheet| lo primero que hay que hacer es importar otros |XSLT|, si es que se van a reaprovechar las definiciones que se hicieron en otros ficheros, con ````. También existe ```` que puede usarse en cualquier posición dentro de xsl:stylesheet: .. code-block:: dtd En ambos casos, el atributo *href* permite indicar la dirección del documento xsl importado. Como se ve se definen del mismo modo, pero no son elementos equivalentes. ```` puede usarse cuando la definiciones hechas es un documento externo queremos tenerlas disponibles exactamente del mismo modo que las tendríamos disponibles, si las hubiéramos hecho dentro del propio documento. Es útil cuando queremos despiezar nuestra transformación en varios ficheros, quizás porque sea muy compleja. ```` actúa de distinta forma y tiene otra utilidad: la de modificar estilos ya definidos sin tener que reescribir todo. En este caso, las definiciones de la hoja que se importa tienen menos prioridad que las de la hoja importadora. Así pues, si quisiéramos crear otra hoja xsl que sacara una salida idéntica a la del :ref:`primer ejemplo `, salvo por el hecho de que queremos que los signos '+' sean asteriscos ('*'), podríamos hacer lo siguiente:: * .. _xsl_output: O sea, importamos las definiciones de :file:`discos.txt.xsl` y redifinimos la plantilla con nombre *tipo*. Otro elemento importante al comenzar a escribir un documento |XSLT| es ````, el cual permite definir cómo será la salida de procesamiento, es decir, cómo será el resultado de nuestra transformación:: El primero de los atributos debe especificar cuál será el método de salida: "*xml*" para generar otro |XML|, "*text*" para generar un fichero de texto y |HTML| para generar un |HTML| (en su variante |SGML|). En la versión **1.0**, si se quiere generar un fichero |XHTML| hay que especificar el método "*xml*". El atributo version permite indicar la versión del |XML| o |HTML| de salida. También es interesante ``indent`` que permite sacar un documento |XML| o |html| con un sangrado que lo haga legible. Los :ref:`ejemplos expositorios ` muestran el uso de este elemento. |stylesheet| tiene otros hijos posibles, pero se introducirán adelante. Plantillas ========== Definición ---------- La definicion de las transformaciones consiste basicamente en describir cómo se transformara cada uno de los nodos del |XML| original, esto es, en crear plantillas para la transformacion de cada nodo. .. _xsl_template: Estas plantillas se crean dentro de |stylesheet| mediante el elemento ````, cuya definición en forma |DTD| podría ser más o menos así: .. code-block:: dtd Empecemos aclarando sus parametros: *match* permite indicar una expresion *XPath* que determina sobre qué elemento o elementos es aplicable la plantilla. Así, por ejemplo, :code:`match="disco"` indica que la plantilla es aplicable a los elementos "disco", se encuentren donde se encuentren, puesto que no se especifica ruta y en este punto no tiene sentido expresar una ruta relativsa ya que no tenemos ninguna referencia. En cambio, si la expresión hubiera sido :code:`match="disco[1]"`, la plantilla sólo se aplicaria al primer elemento "disco" y no al resto. El parámetro es obligatorio a menos que se incluya un parámetro *name*. *name* permite darle un nombre a la plantilla y puede aparecer con o sin el parámetro ``match``. Las plantillas con nombre pueden invocadarse apelando al nombre y no describen directamente la transformación de nodos del |XML| original como hacen aquellas con ``match``: simplemente, cuando se invocan, se ejecutan tomando como :ref:`nodo de procesamiento ` el nodo de procesamiento de la plantilla desde la que se invocan. Véase el :ref:`primer ejemplo introductorio ` en que hay definida una llamada "*tipo*". *mode* es un parámetro opcional y permite crear distintas plantillas para un mismo elemento. Cuál de las plantillas se use, dependerá entonces del atribute *mode* con el que se invoque (vease |apply-templates|). *priority* modifica la prioridad predeterminada de la plantilla, que se calcula atendiendo al valor de *match*. Esta prioridad predeterminada es: .. table:: :class: xslt-prioridad +-----------+-----------------------------------------+------------------------------+ | Prioridad | Descripción | Ejemplo | +===========+=========================================+==============================+ | -0.50 | Comodín o nombre genérico | | \* | | | | | @\* | | | | | node() | | | | | text() | +-----------+-----------------------------------------+------------------------------+ | -0.25 | Comodin con espacio de nombres | | ns:\* | | | | | @ns:\* | +-----------+-----------------------------------------+------------------------------+ | 0.00 | Nombre particular de un nodo | | disco | | | | | @tipo | +-----------+-----------------------------------------+------------------------------+ | 0.50 | Cualquier otra expresión más precisa | | /discoteca/disco | | | | | disco[@tipo='clasica'] | +-----------+-----------------------------------------+------------------------------+ Esto significa que si tuvieramos dos plantillas, una con :code:`match="disco"` y otra con :code:`match="disco[last()]"`, el último elemento "disco" cumple con ambas expresiones, pero como la segunda expresión tiene más prioridad que la primera, entonces la plantilla que se usará para este último disco es la segunda. Por supuesto, el atributo *priority* permite indicar una prioridad numérica que altere la prioridad calculada. El contenido de un elemento |template| es muy diverso y depende mucho de qué queramos hacer dentro de él: obsérvese que dentro de él tienen cabida tanto la mayoría de los elementos de |XSLT| como elementos del |XML| resultante: basta mirar :ref:`los dos ejemplos de arriba ` para comprobar lo que se afirma ahora. Así pues, ya sabremos ir rellenando este elemento según vayamos viendo cada elemento. .. _xslt-nodo-proces: Es fundamental tener claro el concepto de **nodo de procesamiento**. El :dfn:`nodo de procesamiento` es aquel nodo del |XML| original al que la plantilla está aplicando la transformación. Cuando se usan expresiones *XPath* relativas dentro de una plantilla, se sobreentiende que son relativas a su nodo de procesamiento. Si echa un vistazo al :ref:`primer ejemplo ` verá que en la plantilla para "/" se invoca para todos los hijos ```` de ```` la aplicación de su plantilla\ [#]_:: Esto supone que se aplique consecutivamente **6** veces la plantilla para ````, ya que seis son los elementos ```` del |XML| original\ [#]_. Durante la primera ejecución de la plantilla el nodo de procesamiento será el primer elemento ````; durante la segunda, el segundo; y así sucesivamente. Por este motivo, las expresiones ``@tipo``, ``nombre`` o ``autor`` funcionan, ya que se refieren al nodo de procesamiento, que es un elemento ````. .. _xsl_param: Ahora bien, aparte de todos estos innumerables elementos hay uno en particular con el que se puede abrir la definición de una plantilla: xsl:param. El nombre de este elemento no es caprichoso: *param* es apócope de *parámeter*, parámetro, y declara los parámetros que se pueden pasar a la plantilla cuando se la invoca, del mismo modo que en un lenguaje de programación más convencional se pueden pasar argumentos a las funciones. Es más, en este lenguaje los plantillas hacen el papel de funciones. La definición del elemento es esta: .. code-block:: dtd El atributo ``name`` es obligatorio y declara cómo se llama el parámetro. El atributo ``select``, sin embargo, es opcional e indica cuál será el valor predeterminado del parámetro cuando en la invocación a la plantilla no se indique valor alguno. Estos son ejemplos de declaraciones correctas de parámetros:: Sin embargo, el elemento |param| puede no estar vacío. En este caso, su contenido sirve para generar un valor que será el que se tome como predeterminado. Por ejemplo:: 2 .. note:: Por lo general, esta segunda forma se usa cuando el valor no es un valor fijo, sino que se genera de una forma más o menos complicada que requiere hacer varias operaciones. .. seealso:: El documento |XSLT| en sí también puede recibir parámetros externos y estos se definen con |param|. Consulte la :ref:`sección dedicada a las variables ` para más información. Para usar el valor de estos parámetros dentro del cuerpo de la plantilla, debe utlizarse la misma sintaxis que para las **constantes** definidas mediante |variable|. Invocación ---------- Hay dos modos de invocar una plantilla: .. _xsl_apply-templates: #. Basándose en el nodo con ````, para aquellas plantillas que tengan un atributo ``match``: .. code-block:: dtd #. Basándose en su nombre con ````, para aquellas plantillas que tengan un atributo ``name``: .. code-block:: dtd Ambos modos de invocación están ilustrados en los :ref:`ejemplos introductorios `. Por ejemplo, en la plantilla para el elemento "/" del primer ejemplo, se invoca la plantilla disco para que se aplique a los elementos de :file:`/discoteca/disco`:: La invocación también podría haber sido:: o incluso:: puesto que las tres expresiones *XPath* que dan valor al atributo ``select`` seleccionan todos los discos de la "discoteca". En consecuencia, la salida mostrará las transformaciones de cada elemento :code:`` y en el orden en que la expresión *XPath* los devuelve, esto es, el orden en que aparecen en el |XML| original. En cambio, si se hubiera escrito:: no se habría aplicado ninguna plantilla a los discos, ya que no hay ningún ```` hijo del nodo "/", que es el que se está procesando en el momento de usar este |apply-templates|. Si no se expresa el atributo ``select`` se aplican las plantillas de todos los nodos hijos del nodo de procesamiento. Además del atributo ``select``, puede incluirse opcionalmente un atributo ``mode`` en caso de que al definir la plantilla se usase tal atributo también. Como contenido pueden incluirse dos elementos: |sort|, que permite aplicar la plantilla a los nodos según un determinado orden (de lo contrario el orden vendrá determinado por la aparición del nodo en el documento); y |with-param| que permite pasar parámetros a la plantilla\ [#]_. .. _xsl_with-param: La definición pseudo-formal de ```` es exactamente la misma que para |param|: .. code-block:: dtd y podemos explicar exactamente lo mismo, de modo que no se repetirá. .. _xsl_sort: Por su parte, la definición de ```` es la siguiente: .. code-block:: dtd El atributo fundamental de |sort| es ``select`` que indica que expresión *XPath* se utilizará para definir la ordenación. El resto de los atributos permite modificar esta ordenación: ``lang`` especifica la lengua que se usará para ordenar; ``data-type`` si se ordenará suponiendo texto o suponiendo números; ``order`` si se quiere que la ordenación sea de menor a mayor o de mayor a menor; y ``case-order``, si se quiere que la mayúscula o la minúscula vaya primero. .. note:: En :ref:`los ejemplos ` se usa este elemento para ordenar los discos por su tipo. Para ordenar pueden usarse varios |sort|; en ese caso, se ordenará según el criterio del primer |sort|, y a igualdad de éste, según el segundo y así sucesivamente. Por ejemplo, en el :ref:`ejemplo introductorio sobre el claustro de profesores `, podríamos ordenar alfabéticamente del siguiente modo:: Un aspecto a tener en cuenta cuando se usa |sort|, es cómo se evalúa la :ref:`función position() de XPath `. Esta función devuelve la posición del nodo respecto al conjunto de nodos que se están evaluando; y no respecto a la posición que ocupa dentro del documento. Esto significa que si definimos esta simple transformación: .. literalinclude:: files/discos.simple.xsl :emphasize-lines: 13 la salida que se obtiene, puesto que no se especificó ningún orden en |apply-templates|, sigue el orden de aparición de los elementos disco en el documento: .. code-block:: none LISTA DE DISCOS 1. Nueve sinfonías 2. Kind of Blue 3. Metalmorfosis 4. Más madera 5. Ella and Louis 6. El Mesías lo cual es lo esperable. Si cambiamos la ordenación de los nodos para que, por ejemplo, se ordenen por nombre:: obtendremos esto otro: .. code-block:: none LISTA DE DISCOS 1. El Mesías 2. Ella and Louis 3. Kind of Blues 4. Más madera 5. Metalmorfosis 6. Nueve sinfonías Como puede observarse, la *posición* siempre es relativa a la ordenación que hayamos hecho, y no tiene nada que ver con la posición que ocupen dentro del documento. Si hubiéramos querido hacer referencia a la posición en él deberíamos haber usado el elemento |number|. .. _xsl_call-template: La otra forma de aplicar plantillas es invocarlas por su nombre con ````. En este caso, al invocarlas de este modo, no se le indica al procesador que aplique la plantilla adecuada a los nodos que se seleccionan con la expresión *XPath* incluida en ``match``, sino que se le insta a que aplique la plantilla cuyo nombre se refiere con ``name``. El procesador, pues, salta a otro lugar del código |XSLT| (la plantilla invocada), pero no salta de *nodo de procesamiento*: éste sigue siendo el mismo que procesaba. En consecuencia, crear una plantilla y aplicarla con |call-template| dentro de la invocante es equivalente a no hacerlo e incluir su código sin modificaciones dentro de la invocante en el punto en que se aplica con |call-template|. Las plantillas con nombre, sin embargo, nos permiten estructurar mejor el código. El uso de |call-template| con una plantilla con nombre se puede comprobar en el :ref:`primer ejemplo expositorio `::                          -                          +         Como puede verse la generación de la línea que indica el tipo de música se ha extraído fuera de la plantilla y se ha metido en otra con nombre ("tipo"). El nodo de procesamiento dentro de esta plantilla siempre es un elemento ````, ya que es invocada desde una plantilla en que es así. También podríamos, gracias a las plantilla con nombre, reorganizar el segundo ejemplo: .. literalinclude:: files/discos.htmlv2.xsl .. _xslt-default-template: .. rubric:: ¿Qué ocurre si se pide aplicar una plantilla que no se han definido? La especificaciones de |XSLT| `aclaran que existen definidas estas reglas implícitas `_:: Por tanto, si se pide aplicar con |apply-templates| a una plantilla que no se ha definido, se mostrará todo el texto que contenga y se aplicará recursivamente la misma regla a los elementos que contenga. .. note:: Obsérvese cómo la plantilla predeterminada para el nodo :code:`/` es aplicar la plantilla de su único nodo hijo (en el ejemplo, :code:``). Por ello, en la práctica podemos considerar que la primera plantilla que busca el procesador es la plantilla cuyo *match* coincide con el nombre del elemento contenedor (o sea, :code:``). .. rubric:: Ejercicio intermedio Tomando el |XML| del :ref:`ejemplo introductorio `, crear un único |XSLT| que devuelva la siguiente salida: .. code-block:: none LISTA DE DISCOS 1. clasica: Nueve sinfonías (Ludwing van Beethoven) 2. jazz: Kind of Blue (Miles Davis) 3. rock: Metalmorfosis (Barón Rojo) 4. rock: Más madera (Leño) 5. jazz: Ella and Louis (Ella Fitzgerald/Louis Armstrong) 6. clasica: El Mesías (G.F. Haendel) LISTA ALTERNATIVA DE DISCOS * [Ludwing van Beethoven] -- Nueve sinfonías (clasica) * [Miles Davis] -- Kind of Blue (jazz) * [Barón Rojo] -- Metalmorfosis (rock) * [Leño] -- Más madera (rock) * [Ella Fitzgerald/Louis Armstrong] -- Ella and Louis (jazz) * [G.F. Haendel] -- El Mesías (clasica) .. note:: La primera lista está ordenada por tipo de música. .. _xslt-variables: Variables ========= En |XSLT| no existen variables, sino exclusivamente constantes que pueden ser: .. _xsl_variable: - Definidas a través del elemento ```` que puede incluirse dentro de casi cualquier elemento (incluido |stylesheet|) y cuya sintaxis es equivalente a |param|: .. code-block:: dtd Por ejemplo, la condición de los :ref:`ejemplos introductorios `: .. code-block:: xml podría haber escrito también así:: .. note:: Percátese que para usar la (no-)variable debe anteponerse un dolar al nombre que se le asignó, tal como se hace en lenguajes como `sh `_ o `Perl `_. - Definidas como parámetro que se pasa a una plantilla a través de |param| y que se declaran dentro de la plantilla con |with-param|. El uso posterior del valor se hace exactamente igual que cuando se define la constante con |param|/|with-param|: anteponiendo un dólar. Cómo se usa |param| (y su contraparte |with-param|) ya está explicado bajo un epígrafe anterior. Pero es necesario añadir que al usar el procesador |XSLT| se pueden pasar parámetros tal como se pasan al invocar una plantilla; y que, así como en |template| se declaran los parámetros, es posible declarar con |param| dentro de |stylesheet| los parámetros que pasan al procesamiento:: lo cual permitiría pasar a la hoja un parámetro externo llamado *param1*. Con :program:`xmlstarlet`, podríamos hacerlo así: .. code-block:: console $ xmlstarlet tr hoja.xsl -p "param1='abcdef'" original.xml que propiciaría que la constante *$param1* tuviera como valor la cadena "abcdef". Tal como hemos hecho la definición, en caso de no pasar valor, su valor predeterminado sería la cadena "xxxx". Generación de la salida ======================= El objeto de escribir una transformación |XSLT| es generar un documento de salida: en principio, un fichero de texto o un fichero |XML| (o algo similar) como un |HTML|. Por tanto, el objetivo de las plantillas que creamos con |template| es transformar la información del |XML| original, bien en texto, bien en nuevos elementos |XML|. .. _xsl_text: El elemento más simple para esto es ````: .. code-block:: dtd cuyo uso puede conbsultarse en los :ref:`ejemplos introductorios `. Otro modo de volcar en la salida texto es el elemento ````: .. _xsl_value-of: .. code-block:: dtd El valor se expresa con el atributo ``select`` dentro del cual se podrá usar :ref:`una cadena o una expresión XPath `. Por lo general, cuando se pretende volcar un texto literal, se usa |text|; y, cuando se pretende volcar un valor obtenido a partir de una expresión *Xpath*, se usa |value-of|. Cuando la salida es un documento |XML|, entonces es necesario generar elementos. Una forma de hacerlo es incluirlos de forma explícita en el |XSLT| tal como se hace en la plantilla para "/" del segundo ejemplo:: Discos de mi fonoteca
Discos
TipoNombreAutor
.. note:: Esta es, precisamente, la razón por la que suelen incluirse los elementos propios de |XSLT| en un espacio de nombres aparte (*xsl*). .. _xsl_element: Sin embargo, los elementos también pueden generarse mediante ````: .. code-block:: dtd El atributo ``nombre`` sirve para indicar el nombre del elemento; mientras que ``use-attribute-sets`` permite añadir al elemento varios atributos en conjunción con el elemento |attribute-set|, que se verá a continuación. .. _xsl_attribute: Para añadir atributos al elemento puede usarse el elemento ````: .. code-block:: dtd Un ejemplo de lo expuesto son las líneas 30-35 del segundo :ref:`ejemplo introductorio `:: Nótese que se genera el elemento de salida ```` (una celda) a la que se quiere añadir el atributo ``rowspan``. El contenido de |attribute|, será el valor que adopte dicho atributo, y el resto de contenido de |element| el contenido de la celda. Alternativamente, lo anterior puede escribirse del siguiente modo: .. _xslt-notacion-parent: .. code-block:: xslt La :dfn:`notación de llaves` indica al procesador que evalúe la expresión *XPath* y escriba el resultado, y no que escriba la expresión literalmente. .. note:: Esta notación puede resultarnos útil no sólo cuando generamos una salida |XML|. Por ejemplo, en :ref:`un ejercicio intermedio anterior ` la podríamoa haber usado de este modo: .. code-block:: xslt xsl:text>i. 1. .. _xsl_attribute-set: Quedó pendiente la explicación del atributo ``use-attribute-sets`` de |element|. Dentro de este atributo, se pueden incluir nombres de conjuntos de atributos que se definieran con |attribute-set|. Puede tener utilidad en algunos casos. En `este enlace `_ se da cuenta de cómo se usa. .. _xsl_comment: Al generar como salida un |XML| también es posible que queramos generar un comentario, para lo cual existe el elemento ````: .. code-block:: dtd El contenido de este elemento sirve para generar el contenido del comentario. Por ejemplo, entre las líneas 14 y 15 del segundo ejemplo podríamos haber escrito lo siguiente:: Tabla generada a partir de discos.xml .. _xsl_processing-instruction: También es posible incluir instrucciones de procesamiento con ````: .. code-block:: dtd Un ejemplo de su uso puede ser el siguiente:: href="discos.css" type="text/css" .. _xsl_number: En ocasiones, es útil incluir números, formateados de algún modo determinado, dentro de la salida. Para ello existe el elemento ````: .. code-block:: dtd |number| actúa de dos formas: * si se le proporciona el atributo ``value`` se evalúa la expresión que se use como valor (la cual obviamente debe devolver un número). * si no se le proporciona, entonces obtiene el número basándose en la posición del nodo de procesamiento. En principio, lo que hace es contar el número de nodos del mismo tipo al del nodo de procesamiento, que hay encima de este (incluyendo éste). Ahora bien, este comportamiento puede alterarse con los atributos ``count`` y ``from``: * ``count`` selecciona los nodos que quieren contarse mediante el uso de una expresión *XPath*. * ``from`` selecciona con una expresión *XPath* el nodo desde el que se desea contar, en vez de hacerlo desde el primero que selecciona ``count``. El resto de atributos permite formatear el número. ``format`` permite indicar cómo se querrá qe aparezcan esos números: * "1" para una numeración normal: 1, 2, 3, 4, etc. * "01" para una numeración con igual número de cifras. * "a" para numerar con letras minúsculas del alfabeto. * "A" para numerar con letras mayúsuculas del alfabeto. * "i" para números romanos en minúsculas. * "I" para números romanos en mayúsculas. .. warning:: Los atributos ``count`` y ``from`` de |number| no permiten expresiones *XPath* relativas al nodo de procesamiento, sino que se comporta exactamente igual que el atributo ``match`` de |template|. En consecuencia, tampoco tiene sentido usar dentro él la función |current()|. En la especificación este tipo de atributos se notan como de tipo *pattern*. Además se pueden indicar formatos como "(1)" si se quiere el número abrazado por paréntesis. Por último, ``grouping-separator`` y ``grouping-size`` sirven para separar las cifras. Podemos usar este elemento para mejorar el listado del :ref:`primer ejemplo `: .. code-block:: none LISTA DE DISCOS + clasica 1. Nueve sinfonías (Ludwing van Beethoven) 2. El Mesías (G.F. Haendel) + jazz 1. Kind of Blue (Miles Davis) 2. Ella and Louis (Ella Fitzgerald/Louis Armstrong) + rock 1. Metalmorfosis (Barón Rojo) 2. Más madera (Leño) Que se obtiene con este otro |XSLT| en el que aprovechamos el |XSLT| ya escrito mediante el uso de |import|: .. literalinclude:: files/discos.txtv3.xsl .. _xsl_copy: .. _xsl_copy-of: Hay, por último, otro modo de generar contenido de salida: con los elementos `` y ````: .. code-block:: dtd Estos elementos permiten hacer la copia de un elemento del |XML| original. La diferencia entre ellos es que |copy| hace la copia sin copiar atributos y nodos hijos, mietras que |copy-of| sí los incluye. Por ejemplo, una estúpida hoja de estilos que transforma nuestro |XML| original :file:`discos.xml` en otro documento idéntico es la siguiente:: Sin embargo, cobra utilidad cuando lo que pretendemos es alterar parte del documento como aquí:: En esta traducción, la plantilla para los nodos elemento (plantilla "*") copia el elemento y aplica la plantilla correspondiente a todos los comentarios y todos sus nodos hijo. La plantilla para los nodos no-elemento (o sea, atributos, nodos de texto, comentarios e instrucciones de procesamiento) consiste en hacer una copia exacta del nodo. O sea, que, en principio, hacemos una copia exacta del |XML| original. Sin embargo, defimos una plantilla específica para los discos de jazz que consiste en... no traducir nada. La consecuencia es que en la salida obtendremos un |XML| como el original, pero sin los discos de *jazz*. Sentencias condicionales ======================== En |XSLT| hay dos elementos que permiten crear sentencias condicionales ```` y ````. El primero es una suerte de sentencia *if*, pero sin posibilidad de incluir un *else*, mientras que el segundo hace las veces de cláusula *switch*. .. _xsl_if: Elemento ```` --------------------- Permite generar una determinada salida si se cumple una condición: .. code-block:: dtd El atributo ``test`` sirve para indicar esta condición. Los :ref:`ejemplos expositorios ` tiene una muestra del uso de este elemento:: La condición en román paladino es: *si el disco que se está procesando, es el primer disco de su tipo*. .. _xsl_choose: Elemento ```` ------------------------- Este elemento permite generar distintas salidas dependiendo de la evaluación de distintas expresiones. Se ha dicho que equivale a una cláusula *switch*, pero más bien equivale a una serie de *if*: :code:`if... elif... elif... else ... fi`: .. code-block:: dtd De nuevo, ``test`` permite indicar la condición. En el caso de ````, no hay ninguna, porque se entra en este elemento cuando el resto de expresiones han sido falsas. Para ilustrar supongamos que queremos generar esta salida: .. code-block:: none LISTA DE DISCOS + clasica (para muy tradicionales) - Nueve sinfonías (Ludwing van Beethoven) - El Mesías (G.F. Haendel) + jazz (para tradicionales) - Kind of Blue (Miles Davis) - Ella and Louis (Ella Fitzgerald/Louis Armstrong) + rock (para modernillos) - Metalmorfosis (Barón Rojo) - Más madera (Leño) Es decir, añadir una frase al lado del tipo de música. Para ello podríamos hacer la siguiente transformación: .. literalinclude:: files/discos.txt-choose.xsl :emphasize-lines: 12-25 .. _xslt-ejerc-inter2: .. rubric:: Ejercicio intermedio Obtener el siguiente listado:: LISTA ALTERNATIVA DE DISCOS i) [Ludwing van Beethoven] -- Nueve sinfonías (clasica) 2) [Miles Davis] -- Kind of Blue (jazz) 3) [Barón Rojo] -- Metalmorfosis (rock) 4) [Leño] -- Más madera (rock) 5) [Ella Fitzgerald/Louis Armstrong] -- Ella and Louis (jazz) vi) [G.F. Haendel] -- El Mesías (clasica) en que los discos de música clásica se han numerado con números romanos. .. nota:: Si lo resuelve definiendo una variable para almacenar el formato, tendrá que usar la :ref:`notación de llaves `. .. _xsl_for-each: Bucles ====== |XSLT| implementa un tipo de bucle, ````\ [#]_, que nos permite recorrer una serie de elementos del |XML| original: .. code-block:: dtd El atributo ``select`` permite indicar los nodos que se quieren recorrer en el bucle. Al entrar en |for-each| el nodo de procesamiento pasa a ser el que se trate en cada iteración del bucle, de modo que las expresiones *XPath* relativas deberán estar referenciadas a él. Además es posible usar |sort| para ordenar de un determinado modo los nodos seleccionados, tal como se indicó para |apply-templates|. .. _xsl_ejemplo-intro-v2: Para ilustrar su uso, podemos reescribir el :ref:`primer ejemplo ` del siguiente modo: .. literalinclude:: files/discos.txt-for.xsl :emphasize-lines: 9-14 .. note:: La reescritura no ha consistido en una mera sustitución del |apply-templates| por el |for-each| (cosa que se podría haber hecho), sino que se ha cambiado significativamente el algoritmo. En la antigua solución se aplicaba la plantilla sobre todos los elementos "disco" y, dentro de la plantilla, se comprobaba si era el primer disco de un tipo para escribir la etiqueta que marcaba el tipo de música. Ahora, sin embargo, el |for-each| sólo recorre los primeros discos de cada tipo y, en cada iteración, se ejecuta la plantilla para todos los discos de un mismo tipo. Hay, por tanto, una profundidad más en la iteración, y eso nos permite numerar los tipos de música: .. code-block:: none LISTA DE DISCOS 1. clasica a) Nueve sinfonías (Ludwing van Beethoven) b) El Mesías (G.F. Haendel) 2. jazz a) Kind of Blue (Miles Davis) b) Ella and Louis (Ella Fitzgerald/Louis Armstrong) 3. rock a) Metalmorfosis (Barón Rojo) b) Más madera (Leño) .. note:: Por lo general, una solución con |for-each| puede ser escrita con |apply-templates| y viceversa. Como prueba, :download:`ésta es la solución con apply-templates ` del código |XSLT| que se acaba de proponer. Otros elementos =============== Además de los elementos ya descritos, |XSLT| dispone de algunos otros que permiten hacer cosas interesantes: .. _xsl_fall-back: ```` permite ejecutar un código alternativo cuando el procesador no tenga soporte para alguna instrucción que se haya intentado ejecutar. `Aquí `_ hay un ejemplo elocuente de cómo debe usarse. .. _xsl_message: ```` permite escribir en la salida de errores un mensaje durante el procesamiento. Es común que contenga texto, pero puede contener los mismos elementos que |template|, por ejemplo, |value-of|, lo cual permite mostrar el valor de nodos y variables durante una depuración del código. Permite, además, incluir el atributo *terminate* con valor *yes* para cancelar inmediatamente la ejecución de la transformación:: ERROR. Valor incorrecto: . .. _xsl_preserve-space: .. _xsl_strip-space: ````/```` permite indicar una lista de elementos del |XML| original para los que se quiere en la transformación preservar o eliminar los espacios en blanco adicionales. Deben incluirse dentro de |stylesheet|. .. _xsl_key: ```` genera una colección de elementos clasificados según un determinado criterio: .. code-block:: dtd El atributo ``name`` sirve para darle nombre a la clave, ``match`` para indicar los elementos que formarán parte de la colección; y ``use`` el criterio que se seguirá para clasificarlos. Por ejemplo, dentro del elemento |stylesheet| podemos hacer la siguiente definición:: que clasifica los discos por su tipo, de manera que si usamos la función |key()|, la expresión :code:`key(tipo_musica, 'clasica')` devuelve el conjunto de elementos disco de música clásica. .. _xsl_decimal-format: ```` especifica los caracteres que se usarán para el formateo de números que se lleve a cabo con la función |format-number()|: .. code-block:: dtd Debe incluirse como hijo de |stylesheet|. .. _xslt-func: Funciones ********* |XSLT| **1.0** tiene unas pocas funciones que pueden usarse para hacer más poderosas las expresiones *XPath*. De hecho, |EXSLT| consisten básicamente en aumentar este número de funciones disponibles. .. _xsl_func-current: ``current()`` Devuelve el nodo actual de procesamiento. En principio, parace un poco inútil porque:: es equivalente a:: Sin embargo, cuando la expresión *XPath* es más complicada y contiene un predicado dejan de devolver el mismo nodo, ya que *current()* siempre devuelve el nodo de procesamiento. Un ejemplo sería una variante de la plantilla disco del :ref:`primer ejemplo `:: Obsérvese que para tomar los discos cuyo tipo es igual al disco que se procesa, hubo que guardar primero el tipo en una variable, puesto que en la expresión :code:`test="not(preceding-sibling::disco[@tipo = ./@tipo])"` el punto representa el propio disco del que se comprueba el tipo en el predicado; y, por tanto, tal predicado es una `tautología `_. En cambio:: hace exactamente lo que queremos sin necesidad de definir la variable. .. warning:: Ha de hacerse una puntualización: no debe usarse la función |current()| dentro de atributos que la normativa indica que son *pattern*, tales como el atributo ``match`` de |template| o el atributo ``count`` de |number|. .. _xsl_func-document: ``document()`` Obtiene el árbol de nodos de un documento |XML|, para poder acceder a su información. Para ilustrar la función, supongamos que tenemos otro documento |XML| en que están apuntados los discos que hemos dejado prestados: .. literalinclude:: files/prestamos.xml y que queremos sacar una lista en que se distinga convenientemente qué discos tenemos prestados: .. literalinclude:: files/prestamos.txt.xsl :emphasize-lines: 9 Esta transformación genera la siguiente salida: .. code-block:: none LISTA DE DISCOS + clasica # Nueve sinfonías (Ludwing van Beethoven) - El Mesías (G.F. Haendel) + jazz - Kind of Blue (Miles Davis) - Ella and Louis (Ella Fitzgerald/Louis Armstrong) + rock - Metalmorfosis (Barón Rojo) # Más madera (Leño) .. _xsl_func-element-available: ``element-available()`` La función permite comprobar si un elemento |XSLT| está disponible en el procesador. Por ejemplo:: Comentario interesantísimo ¡Atención! La salida se queda sin el comentario interesantísimo .. _xsl_func-format-number: ``format-number()`` La función sirve para convertir un número a cadena según un determinado formato. El número se indica como primer argumento y la cadena como segundo siguiendo estas reglas: - Un "0" indica que se mostrará siempre un dígito. - Una "#" indica que se mostrará el dígito sólo si este realmente existe. - Un "." indica la posición del punto decimal - Un "%" indica que quiere mostrarse el número como porcentaje. Así, por ejemplo: - "#.00" mostrará el número siempre con dos decimales. - "#%" expresará el número como un porcentaje Por ejemplo:: muestra "23.34%". La función admite un tercer parámetro: el nombre de un formato definido con el elemento |decimal-format|. .. _xsl-func-available: ``function-available()`` Equivalente a |element-available()|, pero para comprobar funciones en vez de elementos. .. _xsl_func-generate-id: ``generate-id()`` Genera un identificar único para el nodo especificado en el argumento. Si no especifica ninguno, se sobreentiende el nodo actual. .. _xsl_func-key: ``key()`` Función complementaria al elemento |key|. Para ilustrarla, podemos cambiar el :ref:`primer ejemplo ` por este otro: .. literalinclude:: files/discos.txt-key.xsl :emphasize-lines: 7, 12, 14 Esta transformación genera la siguiente lista: .. code-block:: none LISTA DE DISCOS 1. clasica a) Nueve sinfonías (Ludwing van Beethoven) b) El Mesías (G.F. Haendel) 2. jazz a) Kind of Blue (Miles Davis) b) Ella and Louis (Ella Fitzgerald/Louis Armstrong) 3. rock a) Metalmorfosis (Barón Rojo) b) Más madera (Leño) ``system-property()`` Permite obtener información sobre el procesador que se está usando para la transformación. El único argumento es una cadena que puede ser: * '*xsl:version*', la versión de |XSLT| que implementa el procesador. * '*xsl:vendor*', el vendedor del procesador o, por mejor decir, el nombre del software procesador. * '*xsl:vendor-url*', la |URL| del software procesador. ``unparsed-entity-uri()`` Devuelve la |URI| de la entidad no procesable que se haya indicado como argumento. Esta entidad debe estar definida en el |DTD|. .. seealso:: Consulte el :ref:`concepto de entidad no procesable ` en la sección dedicada a |DTD|. .. rubric:: Ejercicio intermedio Modifique la propuesta con la que se presentó |func:document|, para que la salida sea: .. code-block:: none LISTA DE DISCOS + clasica # [Periquito] Nueve sinfonías (Ludwing van Beethoven) - El Mesías (G.F. Haendel) + jazz - Kind of Blue (Miles Davis) - Ella and Louis (Ella Fitzgerald/Louis Armstrong) + rock - Metalmorfosis (Barón Rojo) # [Mariquita] Más madera (Leño) .. _xslt-extensiones: Extensiones *********** Además de las funciones que define las especificación para |XSLT| **1.0**, algunos procesadores que soportan esta versión, pero no la **2.0** han implementado los elementos y funciones definidos por la `comunidad EXSLT`_. Por otro lado, es también posible crear extensiones propias. .. _e-xslt: Extensiones estándar ==================== Como los procesadores no tienen por qué implementar todas, lo recomentables es conocer cuáles, que en nuestro caso se hace: .. code-block:: console $ xmlstarlet tr --show-ext Extenderse aquí en explicar una por una las funciones es labor prolija y además ya está realizada en la propia página de la `comunidad EXSLT`_. Lo que sí podemos hacer es ilustrar con algunos ejemplos cómo se utilizan. .. _exslt_set_distinct: A partir de 2.0, *XPath* tiene una utilísima función llamada `distinct-values() `_, de la que carece la versión **1.0** y que permite eliminar los nodos repetidos en un conjunto\ [#]_. Ahora bien, |EXSLT| dispone de la función |set:distinct| precisamente para realizar esa misma labor. Con ella, podríamos resolver el :ref:`primer ejemplo introductorio ` del siguiente modo: .. literalinclude:: files/discos.txt-distinct.xsl :emphasize-lines: 4, 5, 11 cuya salida es: .. code-block:: none LISTA DE DISCOS 1. clasica a) Nueve sinfonías (Ludwing van Beethoven) b) El Mesías (G.F. Haendel) 2. jazz a) Kind of Blue (Miles Davis) b) Ella and Louis (Ella Fitzgerald/Louis Armstrong) 3. rock a) Metalmorfosis (Barón Rojo) b) Más madera (Leño) .. _exslt_func_func: Otra aplicación de ls extensdiones es esta: imaginemos que queremos mostrar un conjunto de elementos de un |XML|, pero de manera desordenada; esto es, que cada vez que generemos la salida el orden de esos elementos varíe de modo aleatorio. Piénsese en un |XML| que almacene preguntas de un examen: puede resultarnos útil sacar unos cuantos modelos en que esas preguntas estén desordenadas con el propósito de dificultar que los alumnos se copien. Así que nuestro propósito es generar una función de manera que al hacer:: dentro de |for-each| o |apply-templates| la ordenación de los nodos sea imprevisible y distinta cada vez. Lo primero es analizar lo que pretendemos: #. Queremos crear una función nueva, para lo cual existe la extensión ````. #. La funciones debemos meterla en un espacio de nombres propio. Puede ser *my*, por ejemplo\ [#]_. #. La llamamos *random*, por eso de que genera un número aleatorio. #. Debe devolvernos un número pseudo-aleatorio que es el que servirá para (des)ordenar el conjunto de elementos. #. Es conveniente crearla en fichero aparte, pues puede sernos útil en muchas transformaciones. Pues bien, fijado esto, podemos crearnos el siguiente fichero :file:`random.xsl`: .. literalinclude:: files/random.xsl No es tan importante el modo en que se obtiene el número pseudo-aleatorio, sino sólo saber que para obtenerlo es necesaria una semilla que se consigue a partir del identificador generado del nodo y la hora puesta en segundos según el tiempo *UNIX*. El primer dato se consigue a través de la función |generate-id()|. Para el segundo es necesario la función `date:seconds() `_, que es una función de |EXSLT|. Ahora, si queremos usar esta nueva función, para generar un listado desordenado de nombres de discos, podemos hacer lo siguiente: .. literalinclude:: files/discos.txt-random.xsl :emphasize-lines: 4, 5, 7, 15 Extensiones propias =================== Se han presentado ya los principios de |XSLT| **1.0** y también las extensiones estándar definidas por la `comunidad EXSLT`_ para éste, implementadas muchas de forma nativa en los propios procesadores. Sin embargo, realizar una tarea algo particular y compleja con estas herramientas puede resultar una tarea titánica, cuando no poco menos que imposible. Para suplir estas carencias de manera que podamos escribir nosotros mismos nuestras propias funciones de extensión en otros lenguajes (|func:func| permite escribirlas, pero en |XSLT|) hay algunas posibilidades: * La extensión ```` de |EXSLT| que permite definir el código de la función en otro lenguaje. Desgraciadamente los procesadores no suelen tener soporte para ella. * Usar libxslt_ dentro de un lenguaje de programación de propósito general y definir nuevas funciones de transformación. Es este segundo método el que se ilustrará, usando como lenguaje de programación Python_. .. todo:: Escribir este apartado basándose en `lo expuesto aquí `_. Ejercicios propuestos ===================== .. include:: /99.ejercicios/41.xslt.rst :start-line: 6 .. rubric:: Notas al pie .. [#] No es exactamente así y, de hecho, podría definir una plantilla para el nodo :code:`/`, dentro del cual se encuentra :code:``. A efectos, prácticos es lo mismo, y ya responderemos por qué cuando veamos :ref:`cuál es la plantilla predeterminada `. .. [#] Simplificamos la invocación eliminado |sort|, porque ahora mismo no aporta nada a la explicación del concepto su inclusión. .. [#] Obsérvese que esto, conceptualmente, es la ejecución de un bucle en un lenguaje de programación estructurada. .. [#] Obviamente en la definición deberá haber un elemento |param| correspondiente .. [#] Bucle a medias porque la especificación no asegura el orden en que se recorrerán los nodos. .. [#] Si disponemos de :program:`xidel`, que sí soporta *XPath* **2.0**, podemos probar la función así: .. code-block:: console $ xidel -se 'distinct-values(//disco/@tipo)' discos.xml clasica jazz rock .. [#] Podemos también ahorrarnos el espacio de nombres metiendo la función dentro del espacio ``xsl``, pero ciertamente no es algo muy pulcro. .. _libxslt: http://xmlsoft.org/libxslt/ .. _libxml2: http://xmlsoft.org/libxml2/ .. _Python: http://python.org .. _comunidad EXSLT: http://exslt.org .. |XSLT| replace:: :abbr:`XSLT (eXtensible Stylesheet Language Transformations)` .. |XSL-FO| replace:: :abbr:`XSL-FO (XSL Formatting Object)` .. |PDF| replace:: :abbr:`PDF (Portable Document Format)` .. |URI| replace:: :abbr:`URI (Uniform Resource Identifier)` .. |EXSLT| replace:: :ref:`EXSLT ` .. |stylesheet| replace:: :ref:` ` .. |text| replace:: :ref:` ` .. |value-of| replace:: :ref:` ` .. |template| replace:: :ref:` ` .. |apply-templates| replace:: :ref:` ` .. |call-template| replace:: :ref:` ` .. |import| replace:: :ref:` ` .. |include| replace:: :ref:` ` .. |output| replace:: :ref:` ` .. |param| replace:: :ref:` ` .. |variable| replace:: :ref:` ` .. |with-param| replace:: :ref:` ` .. |sort| replace:: :ref:` ` .. |number| replace:: :ref:` ` .. |element| replace:: :ref:` ` .. |attribute| replace:: :ref:` ` .. |attribute-set| replace:: :ref:` ` .. |copy| replace:: :ref:` ` .. |copy-of| replace:: :ref:` ` .. |if| replace:: :ref:` ` .. |choose| replace:: :ref:` ` .. |for-each| replace:: :ref:` ` .. |decimal-format| replace:: :ref:` ` .. |key| replace:: :ref:` ` .. |key()| replace:: :ref:`key() ` .. |generate-id()| replace:: :ref:`generate-id() ` .. |format-number()| replace:: :ref:`format-number() ` .. |current()| replace:: :ref:`current() ` .. |element-available()| replace:: :ref:`element-available() ` .. |func:func| replace:: :ref:` ` .. |set:distinct| replace:: :ref:`set:distinct() ` .. |func:document| replace:: :ref:`document() ` .. |DTD| replace:: :abbr:`DTD (Document Type Definition)` .. |XHTML| replace:: :abbr:`XHTML (eXtensible HyperText Markup Language)` .. |SGML| replace:: :abbr:`SGML (Standard Generalized Markup Language)`