Programación en castellano
Inicio > Tutoriales > Java y XML > El API JAXP
-Tutoriales

El API JAXP


Mostrar un Fichero XML con el Analizador SAX

En la vida real, vamos a tener poca necesidad de mostrar un fichero XML con un analizador SAX. Normalmente queremos procesar los datos de alguna forma para hacer algo útil con ellos. (Si queremos mostrarlo, es más fácil construir un árbol DOM y usar sus funciones de impresión internas. Pero mostrar una estructura XML es una gran forma de ver el analizador SAX en acción. En este ejercicio, configuraremos los eventos de "echo" del analizador SAX a System.out.

Consideremos esta versión "Hello World" de un programa de proceso XML. Muestra cómo usar el analizador SAX para obtener el dato y mostrar lo que hemos conseguido.

Nota:

El código explicado en esta sección está en Echo01.java. El fichero opera sobre slideSample01.xml.

. Crear el Skeleton

Empezamos creando un fichero llamado Echo.java e introducimos el esqueleto para la aplicación:

public class Echo extends HandlerBase
{
    public static void main (String argv[])

    {

    }

}

Esta clase extiende HandlerBase, que implementa todos los interfaces que explicamos en Una Introducción a los APIs XML de Java. Que nos permite sobreescribir los métodos que nos interesan y dejar por defecto el resto.

Como lo vamos a ejecutar en solitario, necesitamos un método main. Y necesitamos argumentos de la línea de comandos para que podamos decirle a la aplicación qué fichero debe mostrar.

. Importar las Clases que Necesitamos

Luego añadimos las sentencias import para las clases que usará la aplicación:

import java.io.*;
import org.xml.sax.*;
import javax.xml.parsers.SAXParserFactory;  
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;

public class Echo extends HandlerBase
{
  ...

Por supuesto, se necesitan las clases de java.io para hacer la salida. El paquete org.xml.sax define todos los interfaces que usaremos para el analizador SAX, la clase SAXParserFactory crea el ejemplar que usaremos. Lazan una ParserConfigurationException si no puede producir un analizador que corresponda con la configuración de opciones especificada. Finalmente, el SAXParser es lo que la factoría devuelve para analizar.

. Configurar la I/O

La primera orden de negocio es procesar el argumento de la línea de comandos, obtener el nombre del fichero a mostrar, y configurar el canal de salida. Añadimos el texto en negrita de abajo para realizar estas tareas y para hacer una limpieza adicional.

    public static void main (String argv [])

    {
        if (argv.length != 1) {
            System.err.println ("Usage: cmd filename");
            System.exit (1);
        }
        try {
            // Set up output stream
            out = new OutputStreamWriter (System.out, "UTF8");

        } catch (Throwable t) {
            t.printStackTrace ();
        }
        System.exit (0);
    }
    static private Writer out;

Cuando creamos el canal de salida, estamos seleccionando la codificación de caracteres UTF-8. Podríamos haber elegido US-ASCII, o UTF-16, que también son soportados por la plataforma Java. Para más información sobre estos conjuntos de caracteres, puedes ver Esquemas de Codificación en Java.

. Configurar el Analizador

Ahora (por último) estamos preparados para configurar el analizador. Añadimos el texto en negrita de abajo para configurarlo y arrancarlo.

    public static void main (String argv [])

    {
        if (argv.length != 1) {
            System.err.println ("Usage: cmd filename");
            System.exit (1);
        }

        // Use the default (non-validating) parser
        SAXParserFactory factory = SAXParserFactory.newInstance();
        try {
            // Set up output stream
            out = new OutputStreamWriter (System.out, "UTF8");

            // Parse the input 
            SAXParser saxParser = factory.newSAXParser();
            saxParser.parse( new File(argv [0]), new Echo() );

        } catch (Throwable t) {
            t.printStackTrace ();
        }
        System.exit (0);
    }

Con estas líneas de código, creamos un ejemplar SAXParserFactory, según lo determina la propiedad del sistema javax.xml.parsers.SAXParserFactory. Entonces obtenemos un analizador de la factoría y le damos al analizador un ejemplar de esta clase para manejar los eventos, diciéndole qué fichero debe procesar.

Nota:

La clase javax.xml.parsers.SAXParser es una envoltura que define un número de métodos de conveniencia. Envuelve el objeto org.xml.sax.Parser. Si se necesita, podemos obtener el analizador usando el método getParser().

Por ahora, simplemente estamos capturando cualquier excepción que el analizador pueda lanzar. Aprenderemos más sobre el precesamiento de errores en la sección Manejar Errores con el Analizador sin Validación.

Nota:

El método parse que opera sobre objetos File es un método de conveniencia. Debajo de la cubierta, crea un objeto org.xml.sax.InputSource para que el analizador SAX opere sobre él. Para hacer esto, usa un método estático en la clase com.sun.xml.parser.Resolver para crear un InputSource desde un objeto java.io.File. También podríamos hacer esto nosotros mismos, pero el método de conveniencia lo hace más sencillo.

. Implementar el Interface DocumentHandler

El interface más importante para nuestro proceso actual es DocumentHandler. Este interface requiere un número de métodos que invoca el analizador SAX en respuesta a diferentes eventos de análixis. Por ahora, sólo nos conciernen cinco de ellos: startDocument, endDocument, startElement, endElement, y characters. Introducimos el código en negrita de abajo para configurar los métodos que manejan dichos eventos.

    ...   
    static private Writer	out;
    
    public void startDocument ()
    throws SAXException
    {
    }

    public void endDocument ()
    throws SAXException
    {
    }

    public void startElement (String name, AttributeList attrs)
    throws SAXException
    {
    }

    public void endElement (String name)
    throws SAXException
    {
    }

    public void characters (char buf [], int offset, int len)
    throws SAXException
    {
    }
     ...

Cada uno de estos métodos debe lanzar una SAXException. Esta excepción es devuelta al analizador, quién la reenvía al código que llamó al analizador. En el programa actual, esto significa que vuelve al manejador de la excepción Throwable que hay en la parte inferior del método main.

Cuando se encuentra una etiqueta de inicio o de final, el nombre de la etiqueta se pasa como un String a los métodos startElement o endElement, según corresponda. Cuando se encuentra una etiqueta de inicio, cualquier atributo que defina también son pasados en un AttributeList. Los caracteres encontrados dentro del elemento se pasa como un array de caracteres, junto con el número de caracteres (longitud) y un desplazamiento dentro del array que apunta al primer caracter.

. Escribir la Salida

Los métodos DocumentHandler lanzan SAXExceptions pero no IOExceptions, que pueden ocurrir durante la escritura. La SAXException puede envolver cualquier otra excepción, por eso tiene sentido hacer la salida en un método que tiene cuidado de los detalles de manejo de excepciones. Añadimos el código en negrita de abajo para definir un método emit que hace eso.

public void characters (char buf [], int offset, int Len)
throws SAXException
{
}


private void emit (String s)
throws SAXException
{
    try {
        out.write (s);
        out.flush ();
    } catch (IOException e) {
        throw new SAXException ("I/O error", e);
    }
}
...

Cuando se llama a emit, cualquier error de I/O es envuelto en una SAXException junto con un mensaje que lo identifica. Esta excepción entonces es lanzada de vuelta al analizador SAX. Por ahora tengamos en mente que emit es un pequeño método que maneja el stream de salida.

. Espaciar la Salida

Hay un poco más de infraestructura que necesitamos hacer antes de realizar cualquier proceso real. Añadimos el código en negrita de abajo para definir el método nl que escribe el tipo de caracter de final de línea usado por el sistema actual.

    private void emit (String s)

    ...
        }
  
    private void nl ()
    throws SAXException
    {
        String lineEnd =  System.getProperty("line.separator");
        try {
            out.write (lineEnd);
       
        } catch (IOException e) {
            throw new SAXException ("I/O error", e);
        }
    }
Nota:

Aunque puede parecer un poco aburrido, llamaremos a nl() muchas veces dentro de nuestro código. Definirlo ahora simplificará el código posterior. También proporciona un lugar para indentar la salida cuando a esta sección del tutorial.

. Manejar Eventos de Documento

Finalmente, escribiremos algo de código que realmente procesa los eventos DocumentHandler para los métodos añadidos. Añadimos el código en negrita de abajo para manejar los eventos start-document y end-document.

    public void startDocument ()
    throws SAXException
    {
        emit ("<?xml version='1.0' encoding='UTF-8'?>");
        nl();
    }

    public void endDocument ()
    throws SAXException
    {
        try {
            nl();
            out.flush ();
        } catch (IOException e) {
            throw new SAXException ("I/O error", e);
        }
    }

Aquí hemos mostrado una declaración XML cuando el analizador encuentra el inicio del documento. Como hemos configurado OutputStreamWriter para usar la codificación UTF-8, incluimos ésta especificación como parte de la declaración.

Nota:

Sin embargo, las clases IO no entienden los nombres de codificaciones con guiones, por eso debemos especificar "UTF8" en vez de "UTF-8".

Al final del documento, simplemente poner una nueva línea y vaciamos el stream de salida. Añadimos el código en negrita de abajo para procesar los eventos start-element y end-element.

    public void startElement (String name, AttributeList attrs)
    throws SAXException
    {
        emit ("<"+name);
        if (attrs != null) {
            for (int i = 0; i < attrs.getLength (); i++) {             
                emit (" ");
                emit (attrs.getName(i)+"=\""+attrs.getValue (i)+"\"");
            }
        }
        emit (">");
    }

    public void endElement (String name)
    throws SAXException
    {
        emit ("</"+name+">");
    }

Con este código, mostramos las etiquetas de elementos, incluyen cualquier atributo definido en la etiqueta de inicio. Para finalizar esta versión del programa, añadimos el código en negrita de abajo para mostrar los caracteres que ve el analizador.

    
public void characters (char buf [], int offset, int len)
    throws SAXException
    {
        String s = new String(buf, offset, len);
        emit (s);
    }

¡Felicidades! Hemos escrito un aplicación analizador SAX. El siguiente paso es compilarlo y ejecutarlo.

Nota:

Para estar seguros, el manejador de caracteres debería escanear el buffer para buscar caracteres ampersand ('&') y ángulos a la izquierda ('<') y reemplazarlos con los strings "&amp;" o "&lt;", según sea apropiado. Encontraremos más sobre estos tipos de procesamiento cuando expliquemos las referencias a entidades en Sustituir e Insertar Texto.

. Compilar el Programa

Para compilar el programa que hemos creado, ejecutaremos el comando apropiado para nuestro sistema (o usaremos los scripts de comandos mencionados abajo).

Windows:

javac -classpath %XML_HOME%\jaxp.jar;%XML_HOME%\parser.jar Echo.java 

Unix:

javac -classpath ${XML_HOME}/jaxp.jar:${XML_HOME}/parser.jar Echo.java

donde:

  • XML_HOME es donde instalamos las librerías JAXP y Project X.
  • jaxp.jar contiene los APIs especificos JAXP
  • parser.jar contiene los interfaces y clases que componen los APIs SAX y DOM, así como la implementación de referencia de Sun, Project X.
Nota:

Si estamos usando la versión 1.1 de la plataforma también necesitamos añadir %JAVA_HOME%\lib\classes.zip tanto para el script de compilación como el de ejecución, donde JAVA_HOME es la localización de la plataforma Java.

. Ejecutar el Programa

Para ejecutar el programa, de nuevo ejecutamos los comandos apropiados para nuestro sistema.

Windows:

java -classpath .;%XML_HOME%\jaxp.jar;%XML_HOME%\parser.jar 
Echo slideSample.xml

Unix:

java -classpath .:${XML_HOME}/jaxp.jar:${XML_HOME}/parser.jar 
Echo slideSample.xml 

. Scripts de Comandos

Para hacernos la vida más fácil, aquí tenemos algunos scripts de comandos que podemos usar para compilar y ejecutar nuestras aplicaciones mientras trabajemos con este tutorial.

  Unix Windows
Scripts constuir, ejecutar build.bat, run.bat
Netscape Pulsa, elige File-->Save As Pulsa con el botón derecho, elige
Save Link As
.
Internet
Explorer
-/- Pulsa con el botón derecho, elige Save Target As.

. Chequear la Salida

La salida del programa es almacenada en Echo01-01.log. Aquí tenemos una parte de él, mostrando algo de su espaciado.

...
<slideshow title="Sample Slide Show" date="Date of publication" 
author="Yours Truly">


    <slide type="all">
      <title>Wake up to WonderWidgets!</title>
    </slide>
    ...

Mirando esta salida, nos surgen un buen número de preguntas. ¿De dónde vienen los espacios verticales extras? y ¿por qué estos elementos están identados apropiadamente, cuando el código no lo está? Bien, responderemos a estas preguntas en un momento. Primero hay unos cuantos puntos a observar sobre la salida.

  • El comentario definido en la parte superior del fichero
<!-- A SAMPLE set of slides -->

No aparece en el lista. Los comentarios son ignorados por definición, a menos que implementemos un LexicalEventListener en lugar de un DocumentHandler.

  • Los atributos del elemento se listan todos juntos en una sóla linea.

  • La etiqueta del elemento vacío que definimos en (<item/>) es tratada exactamente igual que un elemento vacío de dos etiquetas (<item></item>). Para todos los propósitos son idénticos, (sólo que es más fácil de teclear y consume menos espacio).

. Identificar los Eventos

Esta versión del programa echo podría ser útil para mostrar un fichero XML, pero no nos dice mucho sobre hacía donde va el analizador. El siguiente paso es modificar el programa para que podamos ver de dónde vienen los espacios y las líneas verticales.

Nota:

El código descrito en esta sección está en Echo02.java. La salida que produce está contenida en Echo02-01.log.

Haremos los cambios en negrita que hay abajo para identificar los eventos cuando ocurran.

    public void startDocument ()
    throws SAXException
    {
        nl();
        nl(); 
        emit ("START DOCUMENT");
        nl(); 
        emit ("<?xml version='1.0' encoding='UTF-8'?>");
        nl();
    }

    public void endDocument ()
    throws SAXException
    {
        nl(); emit ("END DOCUMENT");
        try {
         ...
    }

    public void startElement (String name, AttributeList attrs)
    throws SAXException
    {
        nl(); emit ("ELEMENT: ");
        emit ("<"+name);
        if (attrs != null) {
            for (int i = 0; i < attrs.getLength (); i++) {
                emit (" ");
                emit (attrs.getName(i)+"=\""+attrs.getValue (i)+"\"");
                nl(); 
                emit("   ATTR: ");
                emit (attrs.getName (i));
                emit ("\t\"");
                emit (attrs.getValue (i));
                emit ("\"");
            }
        }
        if (attrs.getLength() > 0) nl();
        emit (">");
    }

    public void endElement (String name)
    throws SAXException
    {
        nl(); 
        emit ("END_ELM: ");
        emit ("</"+name+">");
    }

    public void characters (char buf [], int offset, int len)
    throws SAXException
    {   
        nl(); emit ("CHARS: |");     
        String s = new String(buf, offset, len);
        emit (s);
        emit ("|");
    }

Compilamos y ejecutamos esta versión del programa para producir una salida más informativa. Los atributos se muestran uno por línea, que es más bonito. Pero, más importante, las líneas de salida se parecen a esta.

CHARS: |



    |

vemos que el método characters es responsable de mostrar tanto los espacios que crean la identación y las múltiples nuevas líneas que separan los atributos.

Nota:

La especificación XML requiere que todos los separadores de líneas de entrada estén normalizados a una simple nueva línea. El carácter de nueva línea se especifica como \n en Java, C, y en sistemas Unix, pero tiene el alias "linefeed" en sistemas Windows.

. Comprimir la Salida

Para hacer la salida más leíble, modificamos el programa para que sólo muestre los caracteres que tienen algo distinto de los espacios en blanco.

Nota:

El código explicado en está sección está en Echo03.java.

Haremos los cambios mostrados abajo para suprimir de la salida los caracteres que son espacios en blanco.

    public void characters (char buf [], int offset, int len)
    throws SAXException
    {
        nl(); emit ("CHARS: |");
        nl(); emit ("CHARS:   ");
        String s = new String(buf, offset, len);
        emit (s);
        emit ("|");
        if (!s.trim().equals("")) emit (s);
    }

Si ejecutamos el programa ahora, veremos que hemos eliminado también toda la identación, porque el espacio de identación forma parte de los espacios en blanco que preceden el inicio del elemento. Añadimos el código en negrita de abajo para manejar la identación.

    static private Writer	out;
    
    private String indentString = "    "; // Amount to indent
    private int indentLevel = 0;

    ...
        public void startElement (String name, AttributeList attrs)
    throws SAXException
    {
        indentLevel++;
        nl(); emit ("ELEMENT: ");
        ...
    }

    public void endElement (String name)
    throws SAXException
    {
        nl(); 
        emit ("END_ELM: ");
        emit ("</"+name+">");
        indentLevel--;
    }
    ...
    private void nl ()
    throws SAXException
    {
        ...
        try {
            out.write (lineEnd);
            for (int i=0; i < indentLevel; i++) out.write(indentString);
          
        } catch (IOException e) {
        ...
            }

Este código configura un string de identación, sigue la pista del nivel de identación actual, y saca el string de identación siempre que se llame al método nl.

. Inspeccionar la Salida

La salida completa para esta versión del programa está contenida en Echo03-01.log.

    ELEMENT: <slideshow
    ...
    CHARS:   
    CHARS:   
        ELEMENT: <slide
        ...  
        END_ELM: </slide>
    CHARS:   
    CHARS:   

Observa que el método characters fue invocado dos veces en una fila. Inspeccionando el fichero fuente slideSample01.xml veremos que hay un comentario antes de la primera diapositiva. La primera llamada a characters viene antes de este comentario. La segunda llamada viene después.

Observa, también, que el método characters es llamado después del primer elemento slide, así como antes de él. Cuando pensamos en términos de una estructura de datos del tipo árbol, parace obvio. Después de todo, queremos que el elemento slideshow contenga elementos slide, no texto.

En ausencia de un DTD, el analizador debe asumir que cualquier elemento que vea contiene texto como el del primer elemento del slide.

<item>Why <em>WonderWidgets</em> are great</item>

Aquí , la estructura de árbol se parece a esto.

ELEMENT: <item>
CHARS:   Why 
    ELEMENT: <em>
    CHARS:   WonderWidgets
    END_ELM: </em>
CHARS:    are great
END_ELM: </item>

. Documentos y Datos

En este ejemplo, está claro que hay una mezcla de caracteres con la estructura de los elementos. El hecho de que el texto pueda rodear los elementos nos ayuda a explicar porque algunas veces oímos hablar sobre "datos XML" y otras veces oímos hablar sobre "documentos XML". XML maneja confortablemente tanto estructuras de datos como documentos de texto que pueden incluir marcas. La única diferencia entre los dos es si se permite o no texto entre los elementos.

Nota:

En una futura sección de este tutorial, trabajeremos con el método ignorableWhitespace del interface DocumentHandler. Este método sólo puede invocarse cuando está presente un DTD. Si un DTD especifica que slideshow no contiene texto, entonces todos los espacios en blanco que rodean a los elementos slide son ignorables por definición. Por otro lado, si slideshow puede contener texto (lo que se debe asumir como verdadero en ausencia de un DTD), el analizador debe asumir que los espacios y líneas que ve entre los elementos slide son parte importante del documento.

 
Patrocinados
 

Copyright © 1999-2007 Programación en castellano. Todos los derechos reservados.
Formulario de Contacto - Datos legales - Publicidad

Hospedaje web y servidores dedicados linux por Ferca Network

red internet: musica mp3 | logos y melodias | hospedaje web linux | registro de dominios | servidores dedicados
más internet: comprar | recursos gratis | posicionamiento en buscadores | tienda virtual | gifs animados