Zona HTML Zona Java Zona PHP Zona ASP Zona Bases de datos

ORIGEN:

Fecha

3-2-2006

------------------------------------------------------

Inicio > Tutoriales > Internet > XML > Apuntes de XML
-Tutoriales

Apuntes de XML


Manipulación de XML desde programas Java

. Analizar XML con SAX

. Introducción a SAX

  • Hasta ahora hemos trabajado sólo con el lenguaje XML y algunos de sus lenguajes relacionados (DTD, Esquema XML ó XSLT). Siendo estrictos, todavía no hemos programado con XML, es decir, no hemos realizado programas que trabajen con XML.
  • La API SAX (ó API simple para XML) es el primer punto de unión del mundo de XML con el mundo de la programación en general, y en particular con Java.
  • Esta API consta de una serie de clases, con sus correspondientes métodos, que nos permiten trabajar con un documento XML desde un programa escrito en Java, pudiendo acceder a los datos, comprobar si está bien formado, si es válido, etc.
  • La principal característica de SAX es que el documento se lee secuencialmente de principio a fin, sin cargar todo el documento en memoria, lo cual tiene consecuencias positivas y negativas:
    • La ventaja es la eficiencia en cuanto al tiempo y la memoria empleados en el análisis.
    • La desventaja es que con SAX no disponemos de la estructura en árbol de los documentos, luego no podemos modificar ni crear documentos XML, ni recorrerlos jerárquicamente, solamente analizarlos secuencialmente.

. El analizador SAX

  • Para poder trabajar con documentos XML mediante SAX necesitamos un analizador SAX. El analizador realiza el trabajo sucio (detectar cuándo empieza y termina un elemento, gestionar los espacios de nombres, comprobar que el documento está bien formado, etc.), de forma que podemos concentrarnos en los aspectos específicos de nuestra aplicación.
  • Existen muchos analizadores en el mercado, pero no todos se pueden utilizar desde Java. Para los ejemplos de este curso utilizaremos el analizador Xerces, del proyecto colaborativo de código abierto Apache.
  • Para utilizar SAX desde un programa escrito en Java necesitamos conseguir las clases que componen el analizador y asegurarnos de incluir estas clases en la ruta de clases.

. Un primer ejemplo

  • Veamos un primer ejemplo muy sencillo que simplemente analiza un documento XML e imprime un mensaje si el documento está bien formado:
    import java.io.IOException;
    import org.xml.sax.SAXException;
    import org.xml.sax.XMLReader;
    import org.apache.xerces.parsers.SAXParser;
    
    public class SAX {
                    
      public static void main(String[] args) {
        if (args.length != 1) {
                System.out.println("Debe haber un parámetro (el fichero)");
          }
        else {
          String fich = args[0];
          System.out.println("Analizando: " + fich + "\n");
          try {
            XMLReader parser = new SAXParser();
            parser.parse(fich);
            System.out.println("Fin del análisis. Todo bien\n");
          } catch (IOException e) {
              System.out.println(e);
          } catch (SAXException e) {
              System.out.println(e);
          }
        }
      }
    }
  • Creamos una instancia del analizador (clase SAXParser), que vamos a manejar a través de la interfaz XMLReader.
  • Para analizar el documento llamamos al método parse, pasándole el fichero que sea.
  • Si hay algún problema con el análisis se genera una excepción SAXException y el análisis se detiene.

. El manejador de contenido

  • Para que nuestro programa pueda hacer algo útil con los datos XML según se analizan, debemos usar un manejador de contenido.
  • Un manejador es una clase con una serie de métodos. Estos métodos se ejecutan cuando el analizador detecta determinados eventos que se producen al leer un documento (empieza el documento, se abre un elemento, se cierra, se encuentra una instrucción de proceso, etc.).
  • Añadamos al ejemplo un esqueleto de manejador. Los métodos se rellenarán con el código apropiado para el programa que estemos realizando en cada momento:
    class ManejadorPrueba implements ContentHandler {
     
        private Locator loc;
     
        public void setDocumentLocator(Locator loc) {
        }
        
        public void startDocument()  {
        }
    
        public void endDocument() {
        }
        
        public void processingInstruction(String destino, String datos) {
        }
        
        public void startPrefixMapping(String prefijo, String uri) {
        }
    
        public void endPrefixMapping(String prefijo) {
        }
        
        public void startElement(String espacio, String nomLocal,
                                 String nomCompleto, Attributes atrs) {
        }
        
        public void endElement(String espacio, String nomLocal, 
                               String nomCompleto) {
        }
    
        public void characters(char[] ch, int comienzo, int fin) {
        }
    
        public void ignorableWhitespace(char[] ch, int comienzo, int fin) {
        }
    
        public void skippedEntity(String nombre) {
        }
    }
  • Es necesario añadir algunas líneas al código anterior para utilizar el manejador:
    import java.io.IOException;
    import org.xml.sax.SAXException;
    import org.xml.sax.XMLReader;
    import org.xml.sax.Attributes;
    import org.xml.sax.ContentHandler;
    import org.xml.sax.Locator;
    import org.apache.xerces.parsers.SAXParser;
    
    public class SAX {
    
        ...                
            XMLReader parser = new SAXParser();
            ManejadorPrueba man = new ManejadorPrueba();
            parser.setContentHandler(man);
            parser.parse(fich);
            System.out.println("Fin del análisis. Todo bien\n");
        ...
    }

. Los métodos del manejador

  • Ahora añadiremos algo de código a los métodos del manejador para ilustrar su funcionamiento. Simplemente escribiremos mensajes por la consola cuando se detecten los eventos.
  • Es importante notar que estamos analizando sin validar. Más adelante realizaremos análisis con validación.
  • En primer lugar rellenaremos el método setDocumentLocator (que se ejecuta al principio del análisis) guardando en una variable de nuestra clase el localizador que nos llega como parámetro. Así podremos saber en qué lugar (línea y columna) del fichero ocurren los eventos.
    public void setDocumentLocator(Locator l) {
      loc = l;
      System.out.println ("Localizador guardado");
    }
  • El comienzo y el final de un documento implican llamadas a los métodos startDocument y endDocument:
    public void startDocument()  {
      System.out.println ("Ahora empieza el análisis...");
    }
    
    public void endDocument() {
      System.out.println ("...Ahora termina el análisis");
    }
  • El comienzo y el final de cada elemento invocan a los métodos startElement y endElement, dentro de los cuales tenemos información sobre el nombre del elemento, sus atributos y el espacio de nombres al que pertenece:
    public void startElement(String espacio, String nomLocal,
                             String nomCompleto, Attributes atrs) {
      System.out.print("(Línea " + loc.getLineNumber() + ") ");
      System.out.print("Se abre elemento: " + nomLocal);
      if (!espacio.equals("")) {
        System.out.println(" en espacio " + espacio + 
        " (" + nomCompleto + ")");
      } else {
        System.out.println(" sin espacio asociado");
      }
      for (int i=0; i<atrs.getLength(); i++) {
        System.out.println("  Atributo: " + atrs.getLocalName(i) +
        "=" + atrs.getValue(i));          
      }
    }
    
    public void endElement(String espacio, String nomLocal, 
                           String nomCompleto) {
      System.out.println("Se cierra elemento: " + nomLocal + "\n");               
    }
  • La lectura del texto contenido en cada elemento genera una llamada al método characters, dentro del cual disponemos del texto en un vector de caracteres acotado con una posición de comienzo y otra de fin:
    public void characters(char[] ch, int comienzo, int fin) {
      String s = new String(ch, comienzo, fin);
      System.out.println("Contenido: " + s);
    }
  • Algunas puntualizaciones sobre este método:
    • No se puede contar con tener siempre todo el texto de un elemento en una única llamada a este método (grandes porciones de texto, texto y elementos hijos se entremezclan, caracteres especiales, distintos analizadores, etc.)
    • Puede recoger los espacios en blanco al analizar sin validación (justo lo que estamos haciendo).
  • Las instrucciones de proceso se controlan mediante el método processingInstruction:
    public void processingInstruction(String destino, String datos) {
      System.out.println("PI: Destino:" + destino + ", Datos:" + datos);
    }
  • Los dos métodos que sirven para controlar las declaraciones de espacios de nombres son startPrefixMapping y endPrefixMapping. El primero se ejecuta al declarar un espacio de nombres mediante el atributo xmlns, mientras que el segundo se ejecuta al cerrar el elemento en el que se declaró el espacio (normalmente el elemento raíz):
    public void startPrefixMapping(String prefijo, String uri) {
      System.out.println("Comienza el uso del prefijo " + 
                (prefijo.equals("")? "(vacío)": prefijo) +  
                " correspondiente a la URI " + uri);
    }
    
    public void endPrefixMapping(String prefijo) {
      System.out.println("Termina el uso del prefijo " + 
                (prefijo.equals("")? "(vacío)": prefijo));  
    }
  • El método ignorableWhitespace lleva control de los espacios en blanco, aunque con Xerces y la mayoría de analizadores no funciona bien si no estamos validando el documento:
    public void ignorableWhitespace(char[] ch, int comienzo, int fin) {
    }
  • Por último, el método skippedEntity tiene lugar en algunos analizadores que se saltan las entidades cuando no están validando, pero es difícil encontrar casos así:
    public void skippedEntity(String nombre) {
    }

. Código portable

  • Hay una forma mejor de instanciar el analizador, utilizando la API JAXP, con la que podemos conseguir fácilmente que nuestros programas que usan SAX funcionen con cualquier analizador sin tener que realizar cambios en el código, consiguiendo así una completa portabilidad:
    import java.io.IOException;
    import org.xml.sax.SAXException;
    import org.xml.sax.Attributes;
    import org.xml.sax.Locator;
    
    import javax.xml.parsers.SAXParser;
    import javax.xml.parsers.SAXParserFactory;
    import javax.xml.parsers.ParserConfigurationException;
    
    import org.xml.sax.helpers.DefaultHandler;
    
    public class SAXconJAXP {
                    
      public static void main(String[] args) {
        if (args.length != 1) {
                System.out.println("Debe haber un parámetro (el fichero)");
            }
        else {
          String fich = args[0];
          System.out.println("Analizando: " + fich + "\n");
          try {
            SAXParser parser;
            SAXParserFactory factoria = SAXParserFactory.newInstance();
            factoria.setNamespaceAware(true);
            parser = factoria.newSAXParser();
            ManejadorPrueba man = new ManejadorPrueba();
            parser.parse(fich, man);
            System.out.println("Fin del análisis. Todo bien\n");
          } catch (IOException e) {
              System.out.println(e);
          } catch (SAXException e) {
              System.out.println(e);
          } catch (ParserConfigurationException e) {
              System.out.println(e);
          }      
        }
        }
    }
    
    
    class ManejadorPrueba extends DefaultHandler {
     
      private Locator loc;
    
      public void setDocumentLocator(Locator l) {
        loc = l;
        System.out.println ("Localizador guardado");
      }
    
      public void startDocument() {
        System.out.println ("Ahora empieza el análisis...");
      }
    
      public void endDocument() {
        System.out.println ("...Ahora termina el análisis");
      }
    
      public void processingInstruction(String destino, String datos) {
        System.out.println("PI: Destino:" + destino + ", Datos:" + datos);
      }
    
      public void startPrefixMapping(String prefijo, String uri) {
        System.out.println("Comienza el uso del prefijo " + 
                  (prefijo.equals("")? "(vacío)": prefijo) +  
                  " correspondiente a la URI " + uri);
      }
    
      public void endPrefixMapping(String prefijo) {
        System.out.println("Termina el uso del prefijo " + 
                  (prefijo.equals("")? "(vacío)": prefijo));  
      }
    
      public void startElement(String espacio, String nomLocal,
                               String nomCompleto, Attributes atrs) {
    
        System.out.print("(Línea " + loc.getLineNumber() + ") ");
        System.out.print("Se abre elemento: " + nomLocal);
        if (!espacio.equals("")) {
          System.out.println(" en espacio " + espacio + 
          " (" + nomCompleto + ")");
        } else {
          System.out.println(" sin espacio asociado");
        }
        for (int i=0; i<atrs.getLength(); i++) {
          System.out.println("  Atributo: " + atrs.getLocalName(i) +
          "=" + atrs.getValue(i));          
        }
    
      }
    
      public void endElement(String espacio, String nomLocal, 
                             String nomCompleto) {
        System.out.println("Se cierra elemento: " + nomLocal);               
      }
    
      public void characters(char[] ch, int comienzo, int fin) {
        String s = new String(ch, comienzo, fin);
        System.out.println("Contenido: " + s);
      }
    
    }
  • Observar que no se hace ninguna referencia al analizador a utilizar. La independencia se consigue a través de las clases SAXParser y SAXParserFactory, debiéndose capturar una ParserConfigurationException.
  • El método setNamespaceAware activa el soporte para espacios de nombres.
  • Con esta solución, el manejador tiene que ser derivado a partir de la clase DefaultHandler, que es una clase de conveniencia en la que se sobreescriben sólo los métodos deseados (sin tener que implementar todos como ocurría con ContentHandler). Observar que la forma de pasar el manejador al analizador cambia.

. Validación

  • Falta completar el código para que nuestro programa sea capaz de validar un documento XML a la vez que lo analiza:
    ...
    import org.xml.sax.helpers.DefaultHandler;
    import org.xml.sax.SAXParseException;
    
      ...
          try {
            SAXParser parser;
            SAXParserFactory factoria = SAXParserFactory.newInstance();
            factoria.setNamespaceAware(true);
            factoria.setValidating(true);
            parser = factoria.newSAXParser();
            ManejadorPrueba man = new ManejadorPrueba();
            parser.parse(fich, man);
      ...
    
    class ManejadorPrueba extends DefaultHandler {
     
    ...
    
      public void error(SAXParseException exc) 
        throws SAXException {
              
          System.out.println("**Error**\n" +
                             "  Línea:    " + 
                                exc.getLineNumber() + "\n" +
                             "  URI:     " + 
                                exc.getSystemId() + "\n" +
                             "  Mensaje: " + 
                                exc.getMessage());
        throw new SAXException("Se encontró error");
                    
      }
    
      public void fatalError(SAXParseException exc)
          throws SAXException {
              
          System.out.println("**Error Fatal**\n" +
                             "  Línea:    " + 
                                exc.getLineNumber() + "\n" +
                             "  URI:     " + 
                                exc.getSystemId() + "\n" +
                             "  Mensaje: " + 
                                exc.getMessage());        
          throw new SAXException("Se encontró error fatal");
      }
    
    }
    
  • Para informar de los errores que se detecten al validar necesitamos añadir nuevos métodos al manejador.
  • Los errores referentes a la validación provocan una llamada al método error, mientras que los errores que afectan a la sintaxis XML invocan al método fatalError.
  • Los errores de validación no tienen por qué detener el análisis, sólo lo hacen si lanzamos una excepción desde el método error. Los errores de sintaxis siempre detienen el análisis, lancemos o no una excepción.
  • La validación se puede realizar mediante una DTD o un esquema XML, según lo que indique el documento XML (no hace falta cambiar nada en el código Java).

. Otro ejemplo

  • Para terminar, veamos el código de un manejador para el ejemplo del libro que imprime por pantalla el título del libro, las líneas del fichero en que empieza cada capítulo, el número de capítulos y las materias de las que trata el libro:
    class ManejadorLibro extends DefaultHandler {
     
       private Locator l;
      private boolean titulo;
      private int numCaps;
      private HashSet materias;
    
      public void setDocumentLocator(Locator l) {
        this.l = l;
      }
    
      public void startDocument() {
        numCaps = 0;
        materias = new HashSet();
      }
    
      public void startElement(String espacio, String nomLocal,
                               String nomCompleto, Attributes atrs) {
        if (nomLocal.equals("Titulo")) {
          titulo = true;
        }
        else if (nomLocal.equals("Capitulo")) {
          numCaps++;
          System.out.println("Capítulo en línea: " +l.getLineNumber());
          materias.add(atrs.getValue(0));
        }
      }
    
      public void endElement(String espacio, String nomLocal, 
                             String nomCompleto) {
           if (nomLocal.equals("Titulo")) {
             titulo = false;
           }               
      }
    
      public void characters(char[] ch, int comienzo, int fin) {
        if (titulo) {
          String cad = new String(ch, comienzo, fin);
          System.out.println("El título es: " + cad);
        }
      }
    
      public void endDocument() {
        System.out.println("Número de capítulos: " + numCaps);
        System.out.print("Materias: ");
        Iterator i = materias.iterator();
        while (i.hasNext()) {
          System.out.print(i.next() + " ");
        }
        System.out.println();
      }
    
    }

. Manipular XML con DOM

. Introducción a DOM

  • La API DOM (ó Modelo de Objetos de Documento) constituye un paso más allá en el uso de XML desde un lenguaje de programación, y en particular desde Java.
  • Esta API también consta de una serie de clases y métodos, que nos permiten trabajar con los documentos XML desde nuestros programas escritos en Java.
  • La principal característica de DOM es que el documento con el que se trabaja se carga entero en memoria, en una estructura de árbol, por lo que la forma de trabajar con DOM es muy distinta a la de SAX:
    • La ventaja es que con DOM, al disponer de la estructura del documento, podemos acceder a los datos en función de la jerarquía de elementos, así como modificar el contenido de los documentos e incluso crearlos desde cero.
    • La desventaja es el coste en tiempo y memoria que conlleva construir el árbol para un documento, sobre todo si tiene cierto tamaño.

. El analizador DOM

  • Para poder trabajar con DOM necesitamos un analizador DOM. El analizador DOM lee un documento XML y genera una estructura en árbol para él, para lo cual se suele apoyar en un analizador SAX.
  • Para los ejemplos de DOM utilizaremos también el analizador Xerces (dado que ya tenemos sus clases y hemos establecido también la ruta de clases sólo tendremos que importar las clases oportunas para trabajar con DOM)

. Un primer ejemplo

  • Veamos un primer ejemplo sencillo que lee un documento XML y genera un árbol para él:
    import org.w3c.dom.Document;
    import java.io.IOException;
    import org.xml.sax.SAXException;
    import org.apache.xerces.parsers.DOMParser;
    
    public class DOM {
    
      public static void main(String[] args) {
        if (args.length != 1) {
                System.out.println("Debe haber un parámetro (el fichero)");
        }
        else {
          String fich = args[0];
          System.out.println("Generando árbol para: " + fich + "\n");
          try {
            DOMParser parser = new DOMParser();
            parser.parse(fich);
            Document doc = parser.getDocument();
            System.out.println("Fin de la carga. Todo bien\n");
          }
          catch (IOException e) {
            System.out.println(e);
          }            
          catch (SAXException e) {
            System.out.println(e);
           }
        }
      }
    }
  • Necesitamos una instancia del analizador (clase DOMParser). Veremos más adelante cómo se consigue independencia del analizador por medio de la API JAXP.
  • Para generar el árbol llamamos al método parse, pasándole el fichero que sea.
  • Después, con el método getDocument obtenemos un objeto que referencia al árbol creado, y que podemos manipular de múltiples formas.
  • Si hay algún problema con la sintaxis del documento XML se genera una excepción SAXException y el proceso se detiene.

. Manipulación del árbol

  • Para ilustrar el manejo de DOM vamos a imprimir el documento XML que se nos pase como entrada.
  • Para ello necesitamos realizar un procesamiento recursivo: procesamos cada nodo del árbol y después procesamos sus nodos hijos recursivamente.
  • Añadimos un método tratarNodo que va a ser llamado recursivamente:
    import org.w3c.dom.Document;
    import org.w3c.dom.Node;
    import java.io.IOException;
    ...
    
    public class DOM {
    
      public static void tratarNodo(Node nodo) {
        switch (nodo.getNodeType()) {
            case Node.DOCUMENT_NODE:
                break;
    
            case Node.ELEMENT_NODE:
                break;
    
           case Node.ATTRIBUTE_NODE:
              break;
       
            case Node.TEXT_NODE:
              break;
    
            case Node.CDATA_SECTION_NODE:
                break;
    
            case Node.PROCESSING_INSTRUCTION_NODE:
                break;
    
            case Node.ENTITY_REFERENCE_NODE:
                break;
    
            case Node.DOCUMENT_TYPE_NODE:
                break;
        }
      }
    
      public static void main(String[] args) {
        ...
            Document doc = parser.getDocument();
            tratarNodo(doc);
            System.out.println("Fin de la carga. Todo bien\n");
          ...
      }
    }
  • Dentro de este método se realizarán tres tareas:
    • Determinar el tipo del nodo en curso.
    • Imprimir por pantalla el contenido del nodo.
    • Tratar recursivamente los nodos hijos del nodo en curso.
  • La primera tarea se realiza con el método getNodeType, como puede verse en el esquema de código. Ahora completaremos cada caso para realizar las otras dos.
  • El primer nodo que nos encontramos representa todo el documento (tipo DOCUMENT_NODE).
  • Aquí imprimiremos la declaración XML. Después con getDocumentElement accedemos al nodo que representa al elemento raíz, y lo pasamos recursivamente al método tratarNodo:
    case Node.DOCUMENT_NODE:
      System.out.println("<xml version=\"1.0\">");
      Document doc = (Document)nodo;
      tratarNodo(doc.getDocumentElement());
      break;
  • Los elementos del documento están representados por un nodo ELEMENT_NODE.
  • Obtenemos el nombre de cada elemento con getNodeName.
  • Con getChildNodes obtenemos los hijos en una NodeList, que hay que recorrer para tratar recursivamente cada hijo.
    import org.w3c.dom.Document;
    import org.w3c.dom.Node;
    import org.w3c.dom.NodeList;
    import java.io.IOException;
    ...
    
    case Node.ELEMENT_NODE:
       String nombre = nodo.getNodeName();
         System.out.print("<" + nombre);
         System.out.println(">");
         NodeList hijos = nodo.getChildNodes();
         if (hijos != null) {
             for (int i=0; i<hijos.getLength(); i++) {
                 tratarNodo(hijos.item(i));
             }
         }
         System.out.println("</" + nombre + ">");
          break;
  • Para obtener los atributos de un elemento tenemos el método getAttributes, que devuelve un NamedNodeMap, el cual procesamos para tratar los atributos recursivamente.
  • Cada atributo es un nodo ATTRIBUTE_NODE, cuyo nombre y valor recuperamos con getNodeName y getNodeValue:
    import org.w3c.dom.Document;
    import org.w3c.dom.Node;
    import org.w3c.dom.NodeList;
    import org.w3c.dom.NamedNodeMap;
    import java.io.IOException;
    ...
    
    case Node.ELEMENT_NODE:
          String nombre = nodo.getNodeName();
          System.out.print("<" + nombre);
          NamedNodeMap ats = nodo.getAttributes();
          for (int i=0; i<ats.getLength(); i++) {
              tratarNodo(ats.item(i));
          }
          System.out.println(">");
          NodeList hijos = nodo.getChildNodes();
         ...
     break;
    
      case Node.ATTRIBUTE_NODE:
        System.out.print(" " + nodo.getNodeName() +
                         "=\"" + nodo.getNodeValue() +
                         "\"");
  • Para que la salida sea más fácil de leer colocamos un sangrado que vamos aumentando recursivamente:
    public static void tratarNodo(Node nodo, String ind) {
        switch (nodo.getNodeType()) {
    case Node.DOCUMENT_NODE:
      System.out.println("<xml version=\"1.0\">");
      Document doc = (Document)nodo;
      tratarNodo(doc.getDocumentElement(), "");
      break;
    
    case Node.ELEMENT_NODE:
      String nombre = nodo.getNodeName();
      System.out.print(ind + "<" + nombre);
      NamedNodeMap ats = nodo.getAttributes();
      for (int i=0; i<ats.getLength(); i++) {
          tratarNodo(ats.item(i), "");
      }
      System.out.println(">");
      NodeList hijos = nodo.getChildNodes();
      if (hijos != null) {
          for (int i=0; i<hijos.getLength(); i++) {
              tratarNodo(hijos.item(i), ind + "   ");
          }
      }
      System.out.println(ind + "</" + nombre + ">");
      break;
    ...
    
      public static void main(String[] args) {
        ...
            Document doc = parser.getDocument();
            tratarNodo(doc, "");
            System.out.println("Fin de la carga. Todo bien\n");
          ...
    }
  • El texto de los elementos viene recogido en nodos TEXT_NODE o CDATA_SECTION_NODE, en los cuales sólo tenemos que hacer una llamada a getNodeValue para obtener el texto:
    case Node.TEXT_NODE:
      String texto = nodo.getNodeValue().trim();
      if (!texto.equals("")) {
        System.out.println(ind + texto);
      }
      break;
    
    case Node.CDATA_SECTION_NODE:
      System.out.println(nodo.getNodeValue());
      break;
  • Sólo nos faltan unos últimos detalles: el código para procesar las instrucciones de proceso, la declaración DTD y las referencias a entidades:
    import org.w3c.dom.Document;
    import org.w3c.dom.Node;
    import org.w3c.dom.NodeList;
    import org.w3c.dom.NamedNodeMap;
    import org.w3c.dom.DocumentType;
    import java.io.IOException;
    ...
    
    case Node.PROCESSING_INSTRUCTION_NODE:
      System.out.println("<?" + nodo.getNodeName() +
                         " " + nodo.getNodeValue() +
                         "?>");
      break;      
    
    case Node.DOCUMENT_TYPE_NODE:
      DocumentType dt = (DocumentType)nodo;
      System.out.println("<!DOCTYPE " + dt.getName() +
                " SYSTEM \"" + dt.getSystemId() + "\">");
      break;
    
    case Node.ENTITY_REFERENCE_NODE:
      System.out.println(ind + "&" + nodo.getNodeName() + ";");
      break;
  • Y modificar el tratamiento del nodo del documento para que trate todos sus nodos hijos, ya que las instrucciones de proceso y la declaración DTD lo son:
    case Node.DOCUMENT_NODE:
      System.out.println("<xml version=\"1.0\">");
      Document doc = (Document)nodo;
      tratarNodo(doc.getDocumentElement(), "");
      NodeList nodos = nodo.getChildNodes();
      if (nodos != null) {
          for (int i=0; i<nodos.getLength(); i++) {
              tratarNodo(nodos.item(i), "");
          }
      }
      break;

. Código portable

  • Podemos utilizar la API JAXP para conseguir independencia del analizador, de una forma análoga a como lo hicimos con SAX:
    ...
    
    import javax.xml.parsers.DocumentBuilder;
    import javax.xml.parsers.DocumentBuilderFactory;
    import javax.xml.parsers.ParserConfigurationException;
    
    public class DOMconJAXP {
    
      ...
    
      public static void main(String[] args) {
        if (args.length != 1) {
                System.out.println("Debe haber un parámetro (el fichero)");
        }
        else {
          String fich = args[0];
          System.out.println("Generando árbol para: " + fich + "\n");
          try {
            DocumentBuilder parser;
            DocumentBuilderFactory factoria = 
              DocumentBuilderFactory.newInstance();
            parser = factoria.newDocumentBuilder();
            Document doc = parser.parse(fich);
            tratarNodo(doc, "");
            System.out.println("Fin de la carga. Todo bien\n");
                  
          }
          catch (IOException e) {
              System.out.println(e);
          }            
          catch (SAXException e) {
              System.out.println(e);
          } 
          catch (ParserConfigurationException e) {
              System.out.println(e);
          }      
          
        }
       }
    }

. Otro ejemplo

  • Ejemplo DOM para el documento del libro. Imprime por pantalla el título del libro, el número de capítulos y las materias de las que trata el libro:
    import org.w3c.dom.Document;
    import org.w3c.dom.Node;
    import org.w3c.dom.NodeList;
    import org.w3c.dom.NamedNodeMap;
    import java.io.IOException;
    import org.xml.sax.SAXException;
    
    import javax.xml.parsers.DocumentBuilder;
    import javax.xml.parsers.DocumentBuilderFactory;
    import javax.xml.parsers.ParserConfigurationException;
    
    import java.util.HashSet;
    import java.util.Iterator;
    
    public class EjemploDOM {
    
      static int numCaps = 0;  
      static HashSet materias = new HashSet();
    
      public static void procesar(Node nodo) {
    
        String nombre = nodo.getNodeName();
        if (nombre.equals("Libro")) {
          NodeList hijos = nodo.getChildNodes();      
            for (int i=0; i<hijos.getLength(); i++) {
                procesar(hijos.item(i));
            }
          System.out.println("Num Capítulos: " + numCaps);    
          System.out.print("Materias: ");
          Iterator i = materias.iterator();
          while (i.hasNext()) {
            System.out.print(i.next() + " ");
          }
          System.out.println();                  
        }
        else if (nombre.equals("Contenido")) {
          NodeList hijos = nodo.getChildNodes();      
          for (int i=0; i<hijos.getLength(); i++) {
              procesar(hijos.item(i));
          }                    
        }
        else if (nombre.equals("Titulo")) {
          NodeList hijos = nodo.getChildNodes();      
            System.out.println("Título: " + hijos.item(0).getNodeValue());        
        }
        else if (nombre.equals("Capitulo")) {
          numCaps++;
          NamedNodeMap ats = nodo.getAttributes();
          materias.add(ats.item(0).getNodeValue());
            
        }
      }
                    
      public static void main(String[] args) {
        if (args.length != 1) {
              System.out.println("Debe haber un parámetro (el fichero)");
        }
        else {
          String fich = args[0];
          System.out.println("Generando árbol para: " + fich + "\n");
          try {
            DocumentBuilder parser;
            DocumentBuilderFactory factoria = 
              DocumentBuilderFactory.newInstance();
            parser = factoria.newDocumentBuilder();
            Document doc = parser.parse(fich);
            procesar(doc.getDocumentElement());
            System.out.println("Fin. Todo bien\n");
                  
          }
          catch (IOException e) {
              System.out.println(e);
          }            
          catch (SAXException e) {
              System.out.println(e);
          } 
          catch (ParserConfigurationException e) {
              System.out.println(e);
          }          
        }
      }
    }

. Más formas de manipular el árbol

  • Casi todos los métodos que hemos visto para trabajar con DOM están definidos en la interfaz Node.
  • Existen un par de métodos muy útiles definidos en la interfaz Element, que hereda de Node:
    • Para recuperar una serie de elementos por su nombre, no necesariamente hijos directos, tenemos getElementsByTagName, que devuelve una NodeList. Este método también está definido en la interfaz Document.
    • Para recuperar el valor de un atributo a partir de su nombre, tenemos getAttribute.
  • Más métodos útiles, éstos otra vez en la interfaz Node:
    • Para extraer el primer hijo de un nodo, getFirstChild. Es muy útil para conseguir el texto de un elemento.
    • Para extraer el padre de un nodo, getParentNode.
  • Ilustraremos estos métodos con un pequeño programa ejemplo que muestra por pantalla todas las secciones de un libro, con su número de apartados y capítulo al que pertenecen:
    import org.w3c.dom.Document;
    import org.w3c.dom.NodeList;
    import org.w3c.dom.Node;
    import org.w3c.dom.Element;
    import java.io.IOException;
    import org.xml.sax.SAXException;
    import javax.xml.parsers.DocumentBuilder;
    import javax.xml.parsers.DocumentBuilderFactory;
    import javax.xml.parsers.ParserConfigurationException;
    
    public class PeqEjemploDOM {
                   
      public static void main(String[] args) {
        if (args.length != 1) {
                System.out.println("Debe haber un parámetro (el fichero)");
        }
        else {
          String fich = args[0];
          System.out.println("Generando árbol para: " + fich + "\n");
          try {
            DocumentBuilder parser;
            DocumentBuilderFactory factoria = 
              DocumentBuilderFactory.newInstance();
            parser = factoria.newDocumentBuilder();
            Document doc = parser.parse(fich);
            NodeList secciones = doc.getElementsByTagName("Seccion");
            for (int i=0; i<secciones.getLength(); i++) {
              Element sec = (Element)secciones.item(i);
                System.out.print(sec.getFirstChild().getNodeValue());
              System.out.print(" (" + sec.getAttribute("apartados") + ", ");
              Element padre = (Element)sec.getParentNode();
              Node tema = padre.getElementsByTagName("Tema").item(0);
              System.out.println(tema.getFirstChild().getNodeValue() + ")");
            }        
            
          }
          catch (IOException e) {
              System.out.println(e);
          }            
          catch (SAXException e) {
              System.out.println(e);
          }      
          catch (ParserConfigurationException e) {
              System.out.println(e);
          }      
        }
       }
    }
  • Observar que realizar el mismo programa mediante SAX sería un poco engorroso.

. Crear documentos

  • Nos queda pendiente crear un documento desde cero con DOM.
  • Al no estar estandarizada esta tarea, los detalles concretos dependen del analizador que se utilice.
  • Primeramente necesitamos instanciar una
    clase concreta
    que implemente el interfaz Document. En el caso de Xerces es DocumentImpl.
  • Los métodos más importantes para ir componiendo el documento son:
    • createElement crea un nodo para un elemento (Interfaz Document).
    • setAttribute crea un atributo para un elemento (Interfaz Element).
    • createTextNode crea un nodo de tipo texto (Interfaz Document).
    • appendChild sirve para agregar un nodo hijo a un nodo padre, al final de su lista de nodos hijos (Interfaz Node).
    • Existen otros métodos definidos en Document que crean otros tipos de nodos, como instrucciones de proceso, secciones CDATA o referencias a entidad.

. Imprimir documentos

  • Una vez que hemos creado el documento, podemos imprimirlo en un flujo (por pantalla, a un fichero, etc.).
  • Al no estar estandarizada esta tarea, los detalles concretos dependen del analizador que se utilice.
  • En el caso de Xerces, necesitamos instanciar la clase XMLSerializer, especificando el flujo por el que vamos a escribir y un formato. Después pasamos nuestro documento al método serialize.

. Un ejemplo

  • En el siguiente ejemplo crearemos un pequeño documento con los datos de una persona y lo imprimiremos por pantalla y a un fichero.
    import  java.io.IOException;
    import  java.io.StringWriter;
    import  java.io.FileWriter;
    import  org.w3c.dom.Document;
    import  org.w3c.dom.Element;
    import  org.w3c.dom.ProcessingInstruction;
    import  org.apache.xerces.dom.DocumentImpl;
    import  org.apache.xml.serialize.OutputFormat;
    import  org.apache.xml.serialize.Serializer;
    import  org.apache.xml.serialize.SerializerFactory;
    import  org.apache.xml.serialize.XMLSerializer;
    
    public class CrearDOM {
      public static void main( String[] argv ) {
        try {
          Document doc= new DocumentImpl();
          Element raiz = doc.createElement("Persona");
          raiz.setAttribute("DNI", "1234567");
          Element elem = doc.createElement("Nombre");
          elem.appendChild(doc.createTextNode("Juan Pérez"));
          raiz.appendChild(elem);
          elem = doc.createElement("Edad");
          elem.appendChild(doc.createTextNode("28"));
          raiz.appendChild(elem);
          elem = doc.createElement("Altura");
          elem.appendChild(doc.createTextNode("1.75"));
          raiz.appendChild(elem);
          elem = doc.createElement("Dirección");
          Element elemHijo = doc.createElement("Calle");
          elemHijo.appendChild(doc.createTextNode("Castellana"));
          elem.appendChild(elemHijo);
          elemHijo = doc.createElement("Número");
          elemHijo.appendChild(doc.createTextNode("155"));
          elem.appendChild(elemHijo);      
          raiz.appendChild(elem);
          ProcessingInstruction pi = 
            doc.createProcessingInstruction("miPI", "atr=\"prueba\"");
          doc.appendChild(pi);
          doc.appendChild(raiz);
    
          OutputFormat formato  = new OutputFormat(doc, "UTF-8", true);
          StringWriter s = new StringWriter();
          XMLSerializer ser = new XMLSerializer(s, formato);
          ser.serialize(doc);
          System.out.println(s.toString());
    
          FileWriter f = new FileWriter("persona.xml");
          ser = new XMLSerializer(f, formato);
          ser.serialize(doc);
    
        } catch (IOException e) {
          System.out.println(e);
        }
      }
    }

. Modificar documentos

  • También podemos modificar un documento DOM por medio de los siguientes métodos definidos en Node:
    • insertBefore agrega un nodo hijo a otro nodo, permitiendo especificar la posición concreta en la lista de hijos (el hijo que va a quedar detrás del nuevo).
    • removeChild elimina un nodo hijo.
    • replaceChild reemplaza un nodo hijo por otro.
    • removeAttribute elimina un atributo de un nodo (este método se define en Element).
 
Patrocinados
 

Copyright © 1999-2005 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