El API JAXP
Introducción
El API Java para Proceso de XML (JAXP) hace fácil el proceso de datos XML
con aplicaciones escritas en el lenguaje Java. JAXP contiene los
analizadores estándars SAX (Simple API for XML Parsing) y
DOM (Document Object Model) para que podamos elegir entre analizar
nuestros datos como streams de eventos y construir una representación de objetos con ellos. La
versión 1.1 de JAXP también soporta el estándar XSLT
(XML Stylesheet Language Transformations), dándonos control sobre la representación de los
datos y permitiéndonos convertir los datos a otros documentos XML o a otros formatos, como a
HTML. JAXP también proporciona soporte para espacios de nombres,
permitiéndonos trabajar con DTDs que de otra forma tendrían conflictos de nombrado.
Diseñado para ser flexible, JAXP nos permite usar cualquier
analizador compatible XML desde dentro de nuestra aplicación. Esto lo hace con algo
llamado capa de conectividad, que nos permite enchufar una implementación de los APIs
SAX o DOM. La capa de conectividad también nos
permite enchufar un procesador XSL, lo que nos permite controlar la forma en que se muestran
los datos. La Implementación de Referencia 1.1 de JAXP (disponible en
http://java.sun.com/xml) proporciona
el procesador de XSLT Xalan y el analizador Crimson, ámbos desarrollados
conjuntamente entre Sun y la Fundación de Software Apache, que proporciona software
open-source.
El API SAX
SAX define un API para un analizador basado en eventos. Estar
"basado en eventos" significa que el analizador lee un documento XML desde el principio hasta
el final, y cada vez que reconoce una síntaxis de construcción, se lo notifica a la
aplicación que lo está ejecutando. El analizador SAX notifica a la
aplicación llamando a los métodos del interface
ContentHandler. Por ejemplo, cuando el analizador encuentra
un símbolo ("<"), llama al método startElement; cuando
encuentra caracteres de datos, llama al método characters;
y cuando encuentra un símbolo ("</"), llama al método
endElement, etc. Para ilustrar, echemos un vistazo al
documento XML del ejemplo de la primera sección y veamos que hace el analizador en cada
línea. (Por simplicidad, no se han incluido las llamadas al método
ignorableWhiteSpace.)
<priceList> [el analizador llama a startElement]
<coffee> [el analizador llama a startElement]
<name>Mocha Java</name> [El analizador llama a startElement, characters, y endElement]
<price>11.95</price> [el analizador llama a startElement, characters, y a endElement]
</coffee> [el analizador llama a endElement]
Las implementaciones por defecto de los métodos que llama el analizador no hacen nada,
necesitamos escribir un subclase que implemente los métodos apropiados para obtener la
funcionalidad que queremos. Por ejemplo, supongamos que queremos obtener el precio por
libra del café "Mocha". Escribiriamos una clase extendiendo
DefaultHandler (la implementación por defecto de
ContentHandler) en la que escribiríamos nuestras propias
implementaciones de los métodos startElement y
characters.
Primero necesitamos crear un objeto SAXParser desde un
objeto SAXParserFactory. Llamaríamos al método
parse sobre él, pasándole la lista de precios y un ejemplar
de nuestra nueva clase handler (con sus nuevas
implementaciones de los métodos startElement
y characters). En este ejemplo, la lista de precios es un
fichero, pero el método parse también puede aceptar una gran
variedad de fuentes de entrada, incluyendo objetos InputStream,
URL, y InputSource.
SAXParserFactory factory = SAXParserFactory.newInstance();
SAXParser SAXParser = factory.newSAXParser();
SAXParser.parse("priceList.xml", handler);
El resultado de llamar al método parse depende, por supuesto,
de como estén implementados los métodos en handler. El analizador
SAX atravesará el fichero priceList.xml línea a línea,
llamando a los métodos apropiados. Además de los métodos ya mencionados, el analizador llamará
a otros métodos como startDocument,
endDocument, ignorableWhiteSpace,
y processingInstructions, pero estos métodos también tienen
sus implementaciones por defecto que no hacen nada.
Las siguientes definiciones de métodos muestran una forma de implementar los métodos
characters y startElement
para que puedan encontrar e imprimir el precio del café Mocha Java. A causa de la forma
en que trabaja el analizador SAX estos métodos trabajan juntos
para buscar en el elemento name, los caracteres "Mocha
Java", y el elemento price que sigue inmediantemente a
"Mocha Java". Estos métodos usan tres banderas para seguir la pista de las condiciones que
han encontrado. Observa que el analizador SAX tendrá que llamar a
estos métodos más de una vez antes de se alcancen las condiciones para imprimir el precio:
public void startElement(..., String elementName, ...){
if(elementName.equals("name")){
inName = true;
} else if(elementName.equals("price") && inMochaJava ){
inPrice = true;
inName = false;
}
}
public void characters(char [] buf, int offset, int len) {
String s = new String(buf, offset, len);
if (inName && s.equals("Mocha Java")) {
inMochaJava = true;
inName = false;
} else if (inPrice) {
System.out.println("The price of Mocha Java is: " + s);
inMochaJava = false;
inPrice = false;
}
}
Una vez que el analizador ha encontrado el elemento coffee
"Mocha Java", aquí tenemos el estado después de la siguientes llamadas a métodos:
- siguiente llamada a startElement -- inName es true
- siguiente llamada a characters -- inMochaJava es true
- siguiente llamada a startElement -- inPrice es true
- siguiente llamada a characters -- imprime el precio
El analizador SAX puede realizar validación mientras analiza los datos XML,
lo que significa que chequea si los datos siguen las reglas especificadas en el DTD de los
documentos XML. Una analizador SAX será con validación si fue crado mediante
un objeto SAXParserFactory con la validación activada. Esto se
hace para la factoría de objetos SAXParserFactory en la siguiente
línea de código:
factory.setValidating(true);
Para que el analizador sepa qué DTD utilizar para la validación, el documento XML debe referenciar
el DTD en su declaración DOCTYPE. Esa declaración debería ser
similar a esta:
<!DOCTYPE PriceList SYSTEM "priceList.DTD">
El API DOM
El API "Document Object Model" (DOM), definido por el grupo de trabajo
DOM de la W3C, es un conjunto de interfaces para construir una
representación de objeto, en forma de árbol, de un documento XML analizado. Una vez que
hemos construido el DOM, podemos manipularlo con métodos
DOM como insert y
remove, igual que manipularíamos cualquier otra estructura de datos
en forma de árbol. Así, al contrario que un analizador SAX, un analizador
DOM permite acceso aleatorio a piezas de datos particulares de un
documento XML. Otra diferencia es que con un analizador SAX, sólo podemos
leer un documento XML, mientras que con un analizador DOM, podemos
construir una representación objeto del documento y manipularlo en memoria, añadiendo un nuevo
elemento o eliminando uno existente.
En el ejemplo anterior, usamos un analizador SAX para buscar sólo un dato
en un documento. Usando un analizador DOM hubieramos tenido que tener el
modelo del objeto del documento completo en memoria, los que generalmente es menos eficiente para
búsquedas que implican unos pocos ítems, especialmente si el documento es largo. En el siguiente
ejemplo, añadimos un nuevo café a la lista de precios usando un analizador
DOM. No podemos usar una analizador SAX para modificar la
lista de precios porque sólo permite la lectuda de datos.
Supongamos que queremos añadir el café "Kona" a la lista de precios. Leeremos el fichero XML de la
lista de precios en un DOM y luego insertamos el nuevo elemento
coffee, con su nombre y su precio. El siguiente fragmento de código
crea un objeto DocumentBuilderFactory, que luego es usado
para crear el objeto DocumentBuilder. Luego el código llama al método
parse sobre el builder, pasándole el
fichero "priceList.xml".
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.parse("priceList.xml");
En este punto, el documento es una representación DOM de la lista de precios
situada en la memoria. El siguiente fragmento de código añade un nuevo café (con el nombre "Kona" y
el precio de 13.50) al documento de la lista de precios. Como queremos añadir el nuevo café justo
antes del café cuyo nombre es "Mocha Java", el primer paso es obtener una lista de elementos
name e iterar a través de la lista para encontrar "Mocha Java".
Usando el interface Node incluido en el paquete
org.w3c.DOM, el código crea un objeto
Node para el nuevo elemento coffee
y también nuevos nodos para los elementos name y
price. Estos dos elementos contienen los datos, por eso el código
crea un objeto TextNode para cada uno de ellos y añade los nodos
de texto a los nodos que representan los elementos name y
price.
NodeList list = document.getElementsByTagName("name");
Node thisNode = list.getItem("name");
// loop through list
Node thisChild = thisNode.getChildNode();
if(thisNode.getFirstChild() instanceof org.w3c.DOM.TextNode) {
String data = thisNode.getFirstChild().getData();
}
if (data.equals("Mocha Java")) { // new node will be inserted before Mocha Java
Node newNode = document.createElement("coffee");
Node nameNode = document.createElement("name");
TextNode textNode = document.createTextNode("Kona");
nameNode.appendChild(textNode);
Node priceNode = document.createElement("price");
TextNode tpNode = document.createTextNode("13.50");
priceNode.appendChild(tpNode);
newNode.appendChild(nameNode);
newNode.appendChild(priceNode);
thisNode.insertBefore(newNode, thisNode);
}
Obtenemos una analizador DOM que tiene validación de la misa forma que un
analizador SAX validante: llamamos a
setValidating(true) sobre una factoría de analizadores
DOM antes de usarla para crear nuestro analizador
DOM, y nos aseguramos de que el documento XML referencia su DTD
en la declaración DOCTYPE.
Espacios de Nombres XML
Todos los nombres en un DTD son únicos, así se evita la ambiguedad. Sin embargo, si un
documento XML particular referencia uno o más DTDs, hay una posibilidad de que dos o más DTDs
contengan el mismo nombre. Por lo tanto, el documento necesita especificar un espacio de
nombres para cada DTD para que el analizador sepa qué definición usar cuando analice un
ejemplar de un DTD particular.
Aquí está la notación estándard para declarar espacios de nombres XML, lo que normalmente se
hace en el elemento raíz de un documento XML. En el siguiente ejemplo de declaración de
espacios de nombres, la notación xmlns identifica a
nsName y se configura con la URL del espacio de nombres actual:
<priceList xmlns:nsName="myDTD.dtd"
xmlns:otherNsName="myOtherDTD.dtd">
...
</priceList>
Dentro del documento, podemos especificar a qué espacio de nombres pertenece un elemento de
esta forma:
<nsName:price> ...
Para hacer que nuestros analizador SAX o DOM pueda
reconocer los espacios de nombres, llamamos al método
setNamespaceAware(true) sobre nuestro ejemplar de
ParserFactory. Después de esta llamada a este método, cualquier
parser que cree la factoría de parsers
tendrá cuidado con los espacios de nombres.
El API XSLT
XSLT (XSL Transformations), definido por el grupo de trabajo
XSL de la W3C, describe un lenguaje para transformar documentos XML
en otros documentos XML o en otros formatos. Para realizar la transformación, normalmente
necesitamos suministrar una hoja de estilo, que está escrita en "XML Stylesheet Language"
(XSL). La hoja de estilo XSL específica como se mostrarrán los datos XML.
XSLT usa las instrucciones de formateo de la hoja de estilo para realizar
la transformación. El documento convertido puede ser otro documento XML o un documento en
otro formato, como HTML.
JAXP soporta XSLT con el paquete
javax.xml.transform, que nos permite enchufar un
transformer XSLT para realizar las transformaciones.
Los subpaquetes tienen APIs de streams espeficicos, de SAX-, y de
DOM-, que nos permiten realizar transformaciones directamente desde árboles
DOM y eventos SAX. Los dos siguientes ejemplos muestran
como crear un documento XML desde un árbol DOM y como transfomar el documento
XML resultante en HTML usando una hoja de estilo XSL.
Transformiar un Árbol DOM en un Documento XML
Para transformar el árbol DOM creado en la sección anterior en un
documento XML, el siguiente código primero crea un objeto
Transformer que realizará la transformación:
TransformerFactory transFactory = TransformerFactory.newInstance();
Transformer transformer = transFactory.newTransformer();
Usando el nodo raíz del árbol DOM, la siguiente línea de código construye
un objeto DOMSource como fuente de la transformación:
DOMSource source = new DOMSource(document);
El siguiente fragmento de código crea un objeto StreamResult
para tomar el resultado de la transformación y transforma el árbol en XML:
File newXML = new File("newXML.xml");
FileOutputStream os = new FileOutputStream(newXML);
StreamResult result = new StreamResult(os);
transformer.transform(source, result);
Transformar un Documento XML en un Documento HTML
También podemos usar XSLT para convertir el nuevo documento XML,
"newXML.xml", a HTML usando una hoja de estilo. Cuando escribimos una hoja de estilo usamos
espacios de nombres XML para referenciar el XSL construido. Por ejemplo, cada hoja de
estilo tiene un elemento raíz identificando el lenguaje de la hoja de estilo, como se
muestra en la siguiente línea de código:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
Cuando referenciamos un constructor particular en el lenguaje de la hoja de estilos, usamos
el prefijo del espacio de nombres seguido por dos puntos y el constructor particular a
aplicar. Por ejemplo, la siguiente parte de una hoja de estilo indica que el dato
name debe insertase en una fila de una tabla HTML:
<xsl:template match="name">
<tr><td>
<xsl:apply-templates/>
</td></tr>
</xsl:template>
La siguiente hoja de estilo especifica que el dato XML es convertido a HTML y que las
entradas de cafés se insertan en las filas de una tabla:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="priceList">
<html><head>Coffee Prices</head>
<body>
<table>
<xsl:apply-templates />
</table>
</body>
</html>
</xsl:template>
<xsl:template match="name">
<tr><td>
<xsl:apply-templates />
</td></tr>
</xsl:template>
<xsl:template match="price">
<tr><td>
<xsl:apply-templates />
</td></tr>
</xsl:template>
</xsl:stylesheet>
Para realizar la transformación necesitamos obtener un transformer
XSLT y usarlo para aplicar la hoja de estilos a los datos XML. El
siguiente fragmento de código obtiene un transformer
ejemplarizando un objeto TransformerFactory, lee los ficheros
de la hoja de estilos y del XML, crea un fichero para la salida HTML, y finalmente obtiene
el objeto Transformer desde la factoría de objetos
TransformerFactory llamada tFactory.
TransformerFactory tFactory = TransformerFactory.newInstance();
String stylesheet = "prices.xsl";
String sourceId = "newXML.xml";
File pricesHTML = new File("pricesHTML.html");
FileOutputStream os = new FileOutputStream(pricesHTML);
Transformer transformer = tFactory.newTransformer(new StreamSource(stylesheet));
La transformación se consigue llamando al método transform,
pasándole los datos y el stream de salida:
transformer.transform(new StreamSource(sourceId), new StreamResult(os));