XSLT florece con Java

¿Alguna vez se ha sentido perplejo por un problema difícil de transformación XML que no pudo resolver con XSLT (Transformación del lenguaje de hoja de estilo extensible) solo? Tomemos, por ejemplo, una hoja de estilo de filtro simple que selecciona solo los nodos con fecha anterior a cinco días. Ha escuchado que XSLT puede filtrar documentos XML, por lo que piensa que resolverá este problema en poco tiempo. La primera tarea es obtener la fecha de hoy desde una hoja de estilo, siempre que la información no esté incluida en el documento XML original. Desafortunadamente, no puede completar esta tarea solo con XSLT. En una situación como esta, puede simplificar su código XSLT y resolver el problema más rápido con una extensión de Java.

Muchos procesadores XSLT permiten algún tipo de mecanismo de extensión; la especificación requiere que lo hagan. En el mundo de Java y XML, el procesador XSLT más utilizado es el procesador Apache Xalan de código abierto. Escrito en Java, Xalan permite extensiones en Java. Muchos desarrolladores encuentran poderosa la extensibilidad de Xalan porque les permite utilizar sus habilidades de Java desde el contexto de la hoja de estilo. Considere la forma en que las JSP (JavaServer Pages), los scriptlets y las etiquetas personalizadas agregan potencia al HTML. Las extensiones de Xalan agregan poder a las hojas de estilo de la misma manera: permitiendo a los desarrolladores de Java acceder a su herramienta favorita, Java.

En este artículo, demostraré cómo puede usar Java desde una hoja de estilo XSLT. Primero, usaremos la extensibilidad de Xalan para instanciar y usar clases existentes dentro del JDK. Más adelante, le mostraré cómo escribir una función de extensión XSLT que toma un Stringargumento y devuelve un fragmento DOM (Modelo de objetos de documento) al procesador de hojas de estilo.

XSLT es importante para los desarrolladores de J2EE (Java 2 Platform, Enterprise Edition) porque diseñar documentos XML se ha convertido en una operación del lado del servidor. Además, JAXP (la API de Java para procesamiento XML), que incluye soporte para motores XSLT, se ha convertido en parte de la especificación J2EE (J2EE 2.6.11). En su infancia, XSLT tenía la intención de diseñar XML en el cliente; sin embargo, la mayoría de las aplicaciones diseñan el XML antes de enviarlo al cliente. Para los desarrolladores de J2EE, esto significa que el procesador XSLT probablemente se ejecutará dentro del servidor de aplicaciones.

Antes de continuar con este artículo, tenga en cuenta que el uso de extensiones Java en sus hojas de estilo XSLT reducirá su portabilidad. Si bien las extensiones son parte de la especificación XSLT, la forma en que se implementan no lo es. Si sus hojas de estilo se ejecutarán en procesadores que no sean Xalan, como el motor de hojas de estilo de Internet Explorer, debe evitar el uso de extensiones a toda costa.

Debilidades XSLT

Debido a que XSLT tiene algunos puntos débiles, las extensiones XSLT resultan bastante útiles. No estoy diciendo que XSLT sea malo; sin embargo, simplemente no ofrece la mejor herramienta para procesar todo en un documento XML. Considere esta sección de XML:

 XSLT no es tan fácil de usar como algunos creen que tú ...   

Suponga que su jefe le pide que modifique una hoja de estilo para que convierta todas las instancias de "no es" en "no es" y localice etiquetas comunes. Ciertamente, XSLT proporciona un mecanismo para hacer algo en este sentido, ¿verdad? Incorrecto. XSLT no proporciona una forma sencilla de reemplazar la aparición de una palabra o patrón dentro de una cadena. Lo mismo ocurre con la localización. Eso no quiere decir que no se pueda hacer con la sintaxis XSLT estándar. Hay formas, pero no son tan fáciles como nos gustaría. Si realmente desea escribir funciones de manipulación de texto utilizando plantillas recursivas, sea mi invitado.

La principal debilidad de XSLT es el procesamiento de texto, que parece razonable ya que su propósito es renderizar XML. Sin embargo, debido a que el contenido XML es completamente texto, XSLT necesita un manejo de texto más sólido. No hace falta decir que los diseñadores de hojas de estilo requieren cierta extensibilidad de vez en cuando. Con Xalan, Java proporciona esta extensibilidad.

Usar clases JDK dentro de XSLT

Es posible que le complazca saber que no tiene que escribir ningún código Java para aprovechar la extensibilidad de Xalan. Cuando usa Xalan, puede crear e invocar métodos en casi cualquier objeto Java. Antes de utilizar una clase Java, debe proporcionar un espacio de nombres XSLT para ella. Este ejemplo declara "java"como un espacio de nombres para todo en o bajo el paquete Java (es decir, todo el JDK):


  

Ahora necesitamos algo que hacer. Comencemos con un pequeño documento XML:

 Java puede ser una moda J. Burke 30/11/97  

Se le ha pedido que modifique el estilo de este XML para que el título aparezca en mayúsculas. Un desarrollador nuevo en XSLT simplemente abriría una referencia XSLT para buscar la toUpper()función; sin embargo, se sentiría decepcionada al descubrir que la referencia carece de uno. El translate()método es la mejor opción, pero no tengo un método aún mejor: java.lang.String.toUpperCase(). Para utilizar este método, debe crear una instancia de un Stringobjeto con el contenido del título. Así es como puede crear una nueva Stringinstancia con el contenido del elemento de título:


  

El nameatributo especifica el identificador de su nueva Stringinstancia. Invoca al constructor especificando primero el espacio de nombres junto con la ruta restante a la Stringclase. Como habrás notado, Stringcarece de new()método. Se utiliza new()para la construcción de un objeto Java en Xalan; corresponde a la newpalabra clave de Java . Los argumentos dados para new()determinar la versión del constructor que se llamará. Ahora que tiene el contenido del título dentro de un Stringobjeto Java , puede usar el toUpperCase()método, así:


  

Esto puede parecerle extraño al principio. Cuando se utilizan métodos Java en una instancia en particular, el primer argumento es la instancia en la que desea que se invoque el método. Obviamente, Xalan usa la introspección para proporcionar esta capacidad.

A continuación encontrará otro truco. Así es como puede emitir la fecha y la hora en cualquier lugar dentro de su hoja de estilo usando java.lang.Date:


  

Aquí hay algo que alegrará el día de cualquiera que necesite localizar una hoja de estilo genérica entre dos o más idiomas. Puede utilizar java.util.ResourceBundlepara localizar texto literal dentro de una hoja de estilo. Dado que su XML tiene una etiqueta de autor, es posible que desee imprimir "Author:"junto al nombre de la persona.

Una opción es crear una hoja de estilo separada para cada configuración regional, es decir, una para el inglés, otra para el chino, etc. Los problemas inherentes a este enfoque deberían ser evidentes. Mantener la coherencia de varias versiones de hojas de estilo lleva mucho tiempo. También necesita modificar su aplicación para que elija la hoja de estilo correcta según la configuración regional del usuario.

En lugar de duplicar la hoja de estilo para cada idioma, puede aprovechar las funciones de localización de Java. La localización con la ayuda de un ResourceBundledemuestra un mejor enfoque. Dentro de XSLT, cargue el ResourceBundleal principio de sus hojas de estilo, así:


  

La ResourceBundleclase espera encontrar un archivo llamado General.propertiesen su CLASSPATH. Una vez que se crea el paquete, se puede reutilizar en toda la hoja de estilo. Este ejemplo recupera el authorrecurso:


  

Note nuevamente la extraña firma del método. Normalmente, ResourceBundle.getString()toma solo un argumento; sin embargo, dentro de XSLT también debe especificar el objeto mediante el cual desea invocar el método.

Escribe tus propias extensiones

En algunas situaciones excepcionales, es posible que deba escribir su propia extensión XSLT, en forma de función de extensión o elemento de extensión. Hablaré de la creación de una función de extensión, un concepto bastante fácil de comprender. Cualquier función de extensión de Xalan puede tomar cadenas como entrada y devolver cadenas al procesador XSLT. Sus extensiones también pueden tomar NodeLists o Nodes como argumentos y devolver estos tipos al procesador XSLT. Usar Nodes o NodeLists significa que puede agregar al documento XML original con una función de extensión, que es lo que haremos.

Un tipo de elemento de texto que se encuentra con frecuencia es una fecha; brinda una gran oportunidad para una nueva extensión XSLT. Nuestra tarea es diseñar un elemento de artículo para que la fecha se imprima en el siguiente formato:

Viernes 30 de noviembre de 200

Can standard XSLT complete the date above? XSLT can finish most of the task. Determining the actual day is the difficult part. One way to quickly solve that problem is to use the java.text.SimpleDate format class within an extension function to return a string formatted as we wish. But wait: notice that the day appears in bold text. This returns us to the initial problem. The reason we are even considering an extension function is because the original XML document failed to structure the date as a group of nodes. If our extension function returns a string, we will still find it difficult to style the day field differently than the rest of the date string. Here's a more useful format, at least from the perspective of an XSLT designer:

  11 30 2001  

We now create an XSLT extension function, taking a string as an argument and returning an XML node in this format:

  November 30 Friday 2001  

The class hosting our extension function doesn't implement or extend anything; we will call the class DateFormatter:

public class DateFormatter { public static Node format (String date) {} 

Wow, too easy, huh? There are absolutely no requirements placed on the type or interface of a Xalan extension function. Generally, most extension functions will take a String as an argument and return another String. Other common patterns are to send or receive org.w3c.dom.NodeLists or individual Nodes from an extension function, as we will do. See the Xalan documentation for details on how Java types convert to XSLT types.

In the code fragment above, the format() method's logic breaks into two parts. First, we need to parse the date string from the original XML document. Then we use some DOM programming techniques to create a Node and return it to the XSLT processor. The body of our format() method implementation reads:

 Document doc = DocumentBuilderFactory.newInstance(). newDocumentBuilder().newDocument(); Element dateNode = doc.createElement("formatted-date"); SimpleDateFormat df = (SimpleDateFormat) DateFormat.getDateInstance(DateFormat.SHORT, locale); df.setLenient(true); Date d = df.parse(date); df.applyPattern("MMMM"); addChild(dateNode, "month", df.format(d)); df.applyPattern("EEEE"); addChild(dateNode, "day-of-week", df.format(d)); df.applyPattern("yyyy"); dateNode.setAttribute("year", df.format(d)); return dateNode; 

dateNode will contain our formatted date values that we return to the stylesheet. Notice that we've utilized java.text.SimpleDateFormat() to parse the date. This allows us to take full advantage of Java's date support, including its localization features. SimpleDateFormat handles the numeric date conversion and returns month and day names that match the locale of the VM running our application.

Remember: the primary purpose of an extension function is simply to allow us access to existing Java functionality; write as little code as possible. An extension function, like any Java method, can use other methods within the same class. To simplify the format() implementation, I moved repetitive code into a small utility method:

private void addChild (Node parent, String name, String text) { Element child = parent.getOwnerDocument().createElement(name); child.appendChild(parent.getOwnerDocument().createTextNode(text)); parent.appendChild(child); } 

Use DateFormatter within a stylesheet

Now that we have implemented an extension function, we can call it from within a stylesheet. Just as before, we need to declare a namespace for our extension function:


  

Esta vez, calificamos completamente la ruta a la clase que aloja la función de extensión. Esto es opcional y depende de si usará otras clases dentro del mismo paquete o solo un único objeto de extensión. Puede declarar el completo CLASSPATHcomo espacio de nombres o usar un paquete y especificar la clase donde se invoca la función de extensión. Al especificar el completo CLASSPATH, escribimos menos cuando llamamos a la función.

Para utilizar la función, simplemente llámala desde dentro de una selectetiqueta, así: