La tecnología DOM (Document
Object Model) es una interfaz de programación que permite analizar y
manipular dinámicamente y de manera global el contenido, el estilo y la
estructura de un documento. Tiene su origen en el Consorcio World Wide Web (W3C). Esta norma está definida en tres
niveles: Nivel 1, que describe la funcionalidad básica de la interfaz DOM, así
como el modo de navegar por el modelo de un documento general; Nivel 2, que
estudia tipos de documentos específicos (XML, HTML, CSS); y Nivel 3, que amplía
las funcionalidades de la interfaz para trabajar con tipos de documentos
específicos.
Para trabajar con un documento XML primero se almacena en
memoria en forma de árbol con nodos padre, nodos hijo y nodos finales que son
aquellos que no tienen descendientes. En este modelo todas las estructuras de
datos del documento XML se transforman en algún tipo de nodo, y luego esos
nodos se organizan jerárquicamente en forma de árbol para representar la
estructura descrita por el XML.
Una vez creada en memoria esta estrucura, los métodos de DOM
permiten recorrer los diferentes nodos del árbol y analizar a qué tipo
particular pertenecen. En función del tipo de nodo, la interfaz ofrece una
serie de funcionalidades u otras para poder trabajar con la información que
contienen.
El siguiente documento XML muestra una serie de libros. Cada
uno de ellos está definido por un atributo publicado_en,
un texto que indica el año de publicación del libro y por dos elementos hijo: Título y Autor.
<?xml version="1.0"
encoding="UTF-8"?>
<Libros
xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
xsi:noNamespaceSchemaLocation='LibrosEsquema.xsd'>
<Libro publicado_en="1840">
<Titulo>El Capote</Titulo>
<Autor>Nikolai
Gogol</Autor>
</Libro>
<Libro publicado_en="2008">
<Titulo>El Sanador de
Caballos</Titulo>
<Autor>Gonzalo
Giner</Autor>
</Libro>
<Libro publicado_en="1981">
<Titulo>El Nombre de la
Rosa</Titulo>
<Autor>Umberto Eco</Autor>
</Libro>
</Libros>
Un esquema del árbol DOM que representaría internamente este
documento es mostrado en la siguiente imagen. El árbol DOM se ha creado en base
al documento XML anterior. Sin embargo, para no enturbiar la claridad del
gráfico solo se ha desarrollado hasta el final uno de los elementos Libro, dejando los otros dos sin
detallar.
La generación del árbol DOM a partir de un documento XML se
hace de la siguiente manera:
1. Aunque
el documento XML tiene asociado un esquema XML, la librería de DOM, a no ser
que se le diga lo contrario, no necesita validar el documento con respecto al
esquema para poder generar el árbol. Solo tiene en cuenta que el documento esté
bien formado.
2. El
primer nodo que se crea es el nodo Libros
que representa el elemento <Libros>
3. De
<Libros> cuelgan en el documento tres hijos <Libro> de tipo
elemento, por tanto el árbol crea tres nodos Libro descendiente de Libro.
4. Cada
elemento <Libro> en el documento tiene asociado un atributo publicado_en. En DOM, los atributos son
listas con el nombre del atributo y el valor. La lista contiene tantas tuplas
(nombre, valor) como atributos haya en el elemento. En este caso solo hay un
atributo en el elemento <Libro>.
5. Cada
<Libro> tiene dos elementos que descienden de él y que son <Titulo>
y <Autor>. Al ser elementos, estos son representados en DOM como nodos
descendientes de Libro, al igual que
se ha hecho con Libro al descender de
Libros.
6. Cada
elemento <Titulo> y <Autor> tiene un valor que es de tipo cadena de
texto. Los valores de los elementos son representados en DOM como nodos #text.
Sin duda esta es la parte más importante del modelo DOM. Los valores de los
elementos son nodos también, a los que internamente DOM llama #text y que
descienden del nodo que representa el elemento que contiene ese valor. DOM
ofrece funciones para recuperar el valor de los nodos #text. Un error muy común
cuando se empieza por primera vez a trabajar con árboles DOM es pensar que, por
ejemplo, el valor del nodo Título es
el texto que contiene el elemento <Titulo> en el documento XML. Sin
embargo, eso no es así. Si se quiere recuperar el valor de un elemento, es
necesario acceder al nodo #text que desciende de ese nodo y de él recuperar su
valor.
7. Hay
que tener en cuenta que cuando se edita un documento XML, al ser este de tipo
texto, es posible que, por legibilidad, se coloque cada uno de los elementos en
líneas diferentes o incluso que se utilicen espacios en blanco para separar los
elementos y ganar en claridad. Eso mismo se ha hecho con el documento XML
anterior. El documento queda mucho más legible así que si se pone todo en una
única línea sin espacios entre las etiquetas.
DOM no distingue
cuándo un espacio en blanco o un salto de línea (\n) se hace porque es un texo
asociado a un elemento XML o es algo que se hace por “estética”. Por eso, DOM
trata todo lo que es texto de la misma manera, creando un nodo de tipo #text y
poniéndole el valor dentro de ese nodo. Eso mismo es lo que ha ocurrido en el
ejemplo de la imagen. El nodo #text que desciendo de Libro y que tiene como valor “\n” (salto de línea) es creado por
DOM ya que, por estética, se ha hecho un salto de línea dentro del documento
XML, para diferenciar claramente que la etiqueta <Titulo> es descendiente
de <Libro>. Sin duda, en el documento XML hay muchos más “saltos de
línea” que se han empleado para dar claridad al documento, sin embargo, en el
ejemplo del modelo DOM no se han incluido ya que tantos nodos con “saltos de línea”
dejarían el esquema ilegible.
8.
Por último, un documento XML tiene muchas más
“cosas” que las mostradas en el ejemplo anterior, por ejemplo: comentarios,
encabezados, espacios en blanco, entidades, etc. son algunas de ellas. Cuando
se trabaja con DOM, rara vez esos elementos son necesarios para el programador,
por lo que la librería DOM ofrece funciones que omiten estos elementos antes de
crear el árbol, agilizando así el acceso y modificación del árbol DOM.
DOM y Java
DOM ofrece una manera de acceder a documentos XML tanto para
ser leído como para ser modificado. Su único inconveniente es que en el árbol
DOM se crea todo en memoria principal, por lo que si el documento XML es muy
grande, la creación y manipulación de DOM será intratable.
DOM, al ser una propuesta W3C, fue muy apoyado desde el
principio, y eso ocasionó que aparecieran un gran número de librerías (parsers) que permitían el acceso a DOM
desde la mayoría de lenguajes de programación. Esta sección se centra en Java
como lenguaje para manipular DOM, pero dejando claro que otros lenguajes
también tienen sus librerías (muy similares) para gestionarlo, por ejemplo
Microsoft .NET.
Java y DOM han evolucionado mucho en el último lustro.
Muchas han sid las propiestas para trabajar desde Java con la gran cantidad de parsers DOM que existen. Sin embargo,
para resumir, actualmente la propuesta principal se reduce al uso de JAXP (Java API for XML Processing). A través
de JAXP los diversos parsers
garantizan la interoperabilidad de Java. JAXP es incluido en todo JDK (superior
a 1.4) diseñado para Java. La siguiente imagen muestra una estructura de
bloques de una aplicación que accede a DOM. Una aplicación que desea manipular
documentos XML accede a la interfaz JAXP porque le ofrece una manera
transparente de utilizar los diferentes parsers
que hay para manejar XML. Estos parsers
son para DOM (XT, Xalan, Xerces, etc.) pero también pueden ser parsers para SAX (otra tecnología
alternativa a DOM, que se verá en la siguiente sección).
En los ejemplos mostrados en las siguientes secciones se
utilizará la librería Xerces para procesar representaciones en memoria de un
documento XML considerado un árbol DOM. Entre los paquetes concretos que se
usarán destacan:
è javax.xml.parsers.*, en concreto javax.xml.parsers.DocumentBuilder y javax.xml.parsers.DocumentBuilderFactory,
para el acceso desde JAXP.
è org.w3c.dom.* que representa el modelo
DOM según la W3C (objetos Node, NodeList,
Document, etc. y los métodos para manejarlos). Por lo tanto, todas las
especificaciones de los diferentes niveles y módulos que componen DOM están
definidas en este paquete.
El siguiente esquema relaciona JAXP con el acceso a DOM.
Desde la interfaz JAXP se crea un DocumentBuilderFactory.
A partir de esa factoría se crea un DocumentBuilder
que permitirá cargar en él la estructura del árbol DOM (Árbol de Nodos) desde
un fichero XML (Entrada XML).
La clase DocumentBuilderFactory
tiene métodos importantes para indicar qué interesa y qué no del fichero XML
para ser incluido en el árbol DOM, o si se desea validar el XML con respecto a
un esquema. Algunos de estos métodos son los siguientes:
è
setIgnoringComments(boolean
ignore): Sirve para ignorar los comentarios que tenga el fichero XML.
è
setIgnoringElementContentWhitespace(boolean
ignore): Es útil para eliminar los espacios en blanco que no tienen
significado.
è
setNamespaceAware(boolean
aware): Usado para interpretar el documento usando el espacio de nombres.
è
setValidating(boolean
validate): Valida el documento XML según el esquema definido.
Con respecto a los métodos que sirven para manipular el
árbol DOM, que se encuentran en el paquete org.w3c.dom,
destacan los siguientes étodos asociados a la clase Node:
è Todos
los nodos contienen los métodos getFirstChild()
y getNextSibling() que permiten
obtener uno a uno los nodos descendientes de un nodo y sus hermanos.
è El
método getNodeType() devuelve una
constante para poder distinguir entre los diferentes tipos de nodos: nodo de
tipo Elemento (ELEMENT_NODE), nodo de tipo #text (TEXT_NODE), etc. Este método
y las constantes asociadas son especialmente importantes a la hora de recorrer
el árbol ya que permiten ignorar aquellos tipos de nodos que no interesan (por
ejemplo, los #text que tengan saltos de línea).
è El
método getAttributes() devuelve un
objeto NamedNodeMap() (una lista con
sus atributos) si el nodo es del tipo Elemento.
è Los
métodos getNodeName() y getNodeValue() devuelven el nombre y el
valor de un nodo de forma que se pueda hacer una búsqueda selectiva de un nodo
concreto. El error típico es creer que el método getNodeValue() aplicado a un nodo de tipo Elemento (por ejemplo,
<Titulo>) devuelve el texto que contiene. En realidad, es el nodo de tipo
#text (descendiente de un nodo tipo Elemento) el que tiene el texto que
representa el título del libro y es sobre él sobre el que hay que aplicar el
método getNodeValue() para obtener el
título.
Abrir DOM desde Java
Para abrir un documento XML desde Java y crear un árbol DOM
con él se utilizan las clases DocumentBuilderFactory
y DocumentBuilder, que pertenecen al
paquete javax.xml.parsers, y Document, que representa un documento en
DOM y pertenece al paquete org.w3c.dom.
Aunque existen otras posibilidades, en el ejemplo mostrado seguidamente se usa
un objeto de la clase File para
indicar la localización del archivo XML.
public int
abrir_XML_DOM(File fichero){
doc = null; // doc es de tipo Document y
representa al árbol DOM
try{
// Se crea un objeto
DocumentBuilderFactory
DocumentBuilderFactory factory =
DocumentBuilderFactory.newInstance();
// Indica que el modelo DOM no debe
contemplar los comentarios que tenga el XML
factory.setIgnoringComments(true);
// Ignora los espacios en blanco que
tenga el documento
factory.setIgnoringElementContentWhitespace(true);
// Se crea un objeto DocumentBuilder
para cargar en él la estructura del árbol DOM a partir del XML seleccionado
DocumentBuilder builder =
factory.newDocumentBuilder();
// Interpreta (parsea) el documento
XML (file) y genera el DOM equivalente
doc = builder.parse(fichero);
// Ahora doc apunta al árbol DOM listo
para ser recorrido
return 0;
}
catch(Exception e){
e.printStackTrace();
return -1;
}
}
Como se puede entender siguiendo los comentarios del código,
primeramente se crea Factory y se
prepara el parser para interpretar un
fichero XML en el cual ni los espacios en blanco ni los comentarios serán
tenidos en cuenta a la hora de generar el DOM. El método parse() de DocumentBuilder
(creado a partir de un DocumentBuilderFactory)
recibe como entrada un File con la
ruta del fichero XML que se desea abrir y devuelve un objeto de tipo Document. Este objeto (doc) es el árbol DOM cargado en memoria,
listo para ser tratado.
Recorrer un árbol DOM
Para recorrer un árbol DOM se utilizan las clases Node y NodeList que pertenecen al paquete org.w3c.dom. En el ejemplo de esta sección se ha recorrido el árbol
DOM creado del documento XML. El resultado de procesar dicho doumento origina
la siguiente salida:
Publicado en: 1840
El autor es: Nikolai
Gogol
El título es: El
Capote
-------------------
Publicado en: 2008
El autor es: Gonzalo
Giner
El título es: El
Sanador de Caballos
-------------------
Publicado en: 1981
El autor es: Umberto
Eco
El título es: El
Nombre de la Rosa
-------------------
El siguiente código recorrer el árbol DOM para dar la salida
anterior con los nombres de los elementos que contiene cada <Libro>
(<Titulo>, <Autor>), sus valores y el valor y nombre del atributo
de <Libro> (publicado_en).
public String
recorrerDOMyMostrar(Document doc){
String datos_nodo[] = null;
String salida = "";
Node node;
// Obtiene el primer nodo del DOM (primer
hijo)
Node raiz = doc.getFirstChild();
// Obtiene una lsita de nodos con todos los
nodos hijo del raíz
NodeList nodeList = raiz.getChildNodes();
// Procesa los nodos hijo
for (int i = 0; i <
nodeList.getLength(); i++){
node = nodeList.item(i);
if (node.getNodeType() ==
Node.ELEMENT_NODE){
// Es un nodo libro
datos_nodo =
procesarLibro(node);
salida = salida + "\n
" + "Publicado en: " + datos_nodo[0];
salida = salida + "\n
" + "El autor es: " + datos_nodo[2];
salida = salida + "\n
" + "El título es: " + datos_nodo[1];
salida = salida + "\n
--------------------";
}
}
return salida;
}
El código anterior, partiendo del objeto tipo Document (doc) que contiene el árbol
DOM, recorre dicho árbol para sacar los valores del atributo de <Libro> y
de los elementos <Titulo> y <Autor>. Es una práctica común al
trabajar con DOM comprobar el tipo del nodo que se está tratanto en cada
momento ya que, como se ha comentado antes, un DOM tiene muchos tipos de nodos
que no siempre tienen información que merezca la pena explorar. En el código,
solo se presta atención a los nodos de tipo Elemento (ELEMENT_NODE) y de tipo
#text (TEXT_NODE).
Por último queda ver el código del método ProcesarLibro.
protected String[]
procesarLibro(Node n){
String datos[] = new String[3];
Node ntemp = null;
int contador = 1;
// Obtiene el valor del primer atributo del nodo (solo uno en
este ejemplo)
datos[0] = n.getAttributes().item(0).getNodeValue();
// Obtiene los hijos del Libro (título y autor)
NodeList nodos = n.getChildNodes();
for (int i = 0; i <nodos.getLength(); i++){
ntemp = nodos.item(i);
if(ntemp.getNodeType() == Node.ELEMENT_NODE){
/* Importante: para obtener el texto con el título y
autor se accede al nodo TEXT hijo de ntemp y se saca su valor */
datos[contador] =
ntemp.getChildNodes().item(0).getNodeValue();
contador++;
}
}
return datos;
}
En el código anterior lo más destacable es:
è Que
el programador sabe que el elemento <Libro> solo tiene un atributo, por
lo que accede directamente a su valor (n.getAttributes().item(0).getNodeValue()).
è Una
vez detectado que se está en el nodo de tipo Elemento (que puede ser el título
o el autor) entonces se obtiene el hijo de este (tipo #text) y se consulta su
valor (ntemp.getChildNodes().item(0).getNodeValue()).
Modificar y serializar
Ademñas de recorrer en modo “solo lectura” un árbol DOM,
este también puede ser modificado y guardado en un fichero para hacerlo
persistente. En esta sección se muestra un código para añadir un nuevo elemento
a un árbol DOM y luego guardar todo el árbol en un documento XML. Es importante
destacar lo fácil que es realizar modificaciones con la librería DOM. Si esto
mismo se quisiera hacer accediendo a XML como si fuera un fichero de texto
“normal” (usando FileWriter por
ejemplo), el código necesario sería mucho mayor y el rendimiento en ejecución
bastante más bajo.
En el siguiente código añade un nuevo libro al árbol DOM
(doc) con los valores de publicado_en,
titulo y autor, pasados como parámetros.
public int
annadirDOM(Document doc, String titulo, String autor, String anno){
try{
// Se crea un nodo tipo Element con nombre 'titulo'
(<Titulo>)
Node ntitulo = doc.createElement("Titulo");
// Se crea un nodo tipo texto con el títlo del libro
Node ntitulo_text =
doc.createTextNode(titulo);
// Se añade el nodo de texto con el título como hijo del
elemento Titulo
ntitulo.appendChild(ntitulo_text);
// Se hace lo mismo que con titulo a autor (<Autor>)
Node nautor = doc.createElement("Autor");
Node nautor_text = doc.createTextNode(autor);
nautor.appendChild(nautor_text);
// Se crea un nodo de tipo elemento (<libro>)
Node nlibro = doc.createElement("Libro");
// Al nuevo nodo
libro se le añade un atributo publicado_en
((Element)nlibro).setAttribute("publicado_en",
anno);
// Se añade a libro el nodo autor y titulo creados antes
nlibro.appendChild(ntitulo);
nlibro.appendChild(nautor);
/* Finalmente, se obtiene el primer nodo del documento y a
él se le
añade como hijo el nodo libro que ya tiene colgando todos
sus
hijos y atributos creados antes. */
Node raiz = doc.getChildNodes().item(0);
raiz.appendChild(nlibro);
return 0;
}
catch(Exception e){
e.printStackTrace();
return -1;
}
}
Revisando el código anterior se puede apreciar que para
añadir nuevos nodos a un árbol DOM hay que conocer bien cómo de diferente es el
modelo DOM con respecto al documento XML original. La prueba es la necesidad de
crear nodos de texto que sean descendientes de los nodos de tipo Elemento para
almacenar los valores XML.
Una vez se ha modificado en memoria un árbol DOM, éste puede
ser llevado a fichero para lograr la persistencia de los datos. Esto se puede
hacer de muchas maneras. Una alternativa se recoge en el siguiente código.
En el ejemplo se usa las clases XMLSerializer y OutputFormat
que están definidas en los paquetes com.sun.org.apache.xmo.internal.serialize.OutputFormat
y com.sun.org.apache.xml.internal.serialize.XMLSerialize.
Estas clases realizan la labor de serializar un documento XML.
Serializar (en
inglés marshalling) es el proceso de
convertir el estado de un objeto (DOM en nuestro caso) en un formato que se
pueda almacenar. Serializar es, por ejemplo, llevar el estado de un objeto en
Java a un fichero o, como en nuestro caso, llevar los objetos que componen un
árbol DOM a un fichero XML. El proceso contrario es decir, pasar el contenido
de un fichero a una estructura de objetos se conoce por unmarshalling.
La clase XMLSerializer
se encarga de serializar un árbol DOM y llevarlo a fichero en formato XML bien
formado. En este proceso se utiliza la clase OutputFormat porque permite asignar diferentes propiedades de
formato al fichero resultado, por ejemplo que el fichero esté indentado
(anglicismo, indent) es decir, que los elementos hijos de otro elemento
aparezcan con más tabulación a la derecha para así mejorar la legibilidad del
fichero XML.
public int
guardarDOMcomoFILE(){
try{
// Crea un fichero llamado salida.xml
File archivo_xml = new File("salida.xml");
// Especifica el formato de salida
OutputFormat format = new OutputFormat(doc);
// Especifica que la salida esté indentada
format.setIndenting(true);
// Escribe el contenido en el FILE
XMLSerializer serializer = new XMLSerializer(new
FileOutputStream(archivo_xml), format);
serializer.serialize(doc);
return 0;
}
catch(Exception e){
return -1;
}
}
Según el código anterior, para serializar un árbol DOM se
necesita:
è
Un objeto File
que representa al fichero resultado (en el ejemplo será salida.xml).
è
Un objeto OutputFormat
que permite indicar pautas de formato para la salida
è
Un objeto XMLSerializer
que se construye con el File de
salida y el formato definido con un OutputFormat.
En el ejemplo utiliza un objeto FileOutputStream
para representar el flujo de salida.
è
Un método serialize()
de XMLSerializer que recibe como
parámetro el Document (doc) que
quiere llevar a fichero y lo escribe.
Crear documentos XML desde cero
Para realizar todo lo anterior, necesitaremos tener un
documento XML ya creado. Pero, ¿cómo podemos generarlo en Java desde cero?
Hay dos métodos.
Método 1. Modificar
el método guardarDOMcomoFILE para que acepte un argumento de tipo cadena, el
nombre del fichero XML destino.
package GestionDOM;
import
javax.xml.parsers.DocumentBuilder;
import
javax.xml.parsers.DocumentBuilderFactory;
import
org.w3c.dom.DOMImplementation;
public class
PruebaGestionarDOM{
public static void main(String[] args){
GestionarDOM ObjetoDOM = new
GestionarDOM();
ObjetDOM.doc = null;
try{
DocumentBuilderFactory factory =
DocumentBuilderFactory.newInstance();
DocumentBuilder builder =
factory.newDocumentBuilder();
DOMImplementation implementation
= builder.getDOMImplementation();
ObjetDOM.doc =
implementation.createDocument(null, "Libros", null);
ObjetDOM.doc.setXMLVersion("1.0");
ObjetDOM.annadirDOM("Desarrollo
de Interfaces", "Pablo Martinez", "2010");
ObjetDOM.annadirDOM("Acceso
a datos", "Alberto Carrera", "2011");
ObjetDOM.annadirDOM("Formación
y orientación laboral", "Belén Carrera", "2012");
ObjetDOM.guardarDOMcomoFILE("D:\\profesores.XML");
}
catch(Exception e){
e.printStackTrace();
}
}
}
Método 2. XMLSerializer
es una API propietaria y puede desaparecer en un futuro, así que otro forma de
generarlo es con la clase Transformer.
package GestionDOM;
import
javax.xml.parsers.DocumentBuilder;
import
javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Result;
import
javax.xml.transform.Source;
import
javax.xml.transform.Transformer;
import
javax.xml.transform.TransformerFactory;
import
javax.xml.transform.stream.*;
import
javax.xml.transform.dom.DOMSource;
import
org.w3c.dom.DOMImplementation;
public class
PruebaGestionarDOM_1{
public static void main(String[] args){
GestionarDOM ObjetoDOM = new
GestionarDOM();
ObjetDOM.doc = null;
try{
DocumentBuilderFactory factory =
DocumentBuilderFactory.newInstance();
DocumentBuilder builder =
factory.newDocumentBuilder();
DOMImplementation implementation
= builder.getDOMImplementation();
ObjetDOM.doc =
implementation.createDocument(null, "Libros", null);
ObjetDOM.doc.setXMLVersion("1.0");
ObjetDOM.annadirDOM("Desarrollo
de Interfaces", "Pablo Martinez", "2010");
ObjetDOM.annadirDOM("Acceso
a datos", "Alberto Carrera", "2011");
ObjetDOM.annadirDOM("Formación
y orientación laboral", "Belén Carrera", "2012");
/* A partir de aquí cambio
respecto a la versión anterior.
Lo podría haber puesto como
segundo método de guardar de ObjetoDOM y llamarlo */
Soruce source = new
DOMSource(ObjetDOM.doc);
Result result = new
StreamResult(new java.io.File("D:\profesores.XML"));
Transformer transformer =
TransformerFactory.newInstance().newTransformer();
transformer.transform(source,
result);
}
catch(Exception e){
e.printStackTrace();
}
}
}
Me ha resultado de gran ayuda leer este artículo para comprender el acceso a archivos XML utilizando las librería DOM. Muy completo y muy bien explicado. Además en español que también ayuda. Gracias por compartir esta información de forma desinteresada.
ResponderEliminarGracias a ti por comentar. Un saludo :)
EliminarMuy buena guía para explicar como funcionan en general las clases y el proceso 👍👍
ResponderEliminarGracias!
EliminarGenial encontrar contenido así para entender la materia cuando no te facilitan estas explicaciones en clase! Mil gracias
ResponderEliminarMe alegro serte de ayuda. Gracias por visitar el blog!
EliminarExcelente aporte muchas gracias
ResponderEliminar