 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
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
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
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).
|
|
|