DOM (Document Object Model) es una especificación de un API, independiente del lenguaje, para acceder y modificar documentos XML (y también HTML). Dicho API representa documentos XML y HTML como objetos que pueden ser accedidos por aplicaciones tales como navegadores Web, buscadores de documentos, y lenguajes de scripts. Mediante el uso de DOM, dichos programas pueden crear, navegar, manipular, y modificar los documentos XML (y HTML).
Como hemos visto, SAX permite realizar programas (analizadores) que reaccionen ante los datos contenidos en un documento XML. Sin embargo si queremos acceder a esta información de forma no-secuencial (no siguiendo el orden marcado por el analizador) o queremos alterar o manipular esta información (por ejemplo mover una sección o parte del documento) entonces necesitamos algo más flexible.
La idea es proporcionar algún mecanismo para crear un modelo de un documento XML como un conjunto de objetos. A partir de este modelo ya podemos navegar por la estructura del documento y añadir, quitar o modificar elementos y su contenido. Dicho modelo se llama DOM (Document Object Model).
Otro objetivo importante de DOM es el proporcionar una interfaz de programación estándar de forma que pueda usarse en una amplia variedad de entornos y aplicaciones. DOM se ha diseñado para poder ser usado con cualquier lenguaje de programación.
DOM está siendo definido por el W3C (World Wide Web Consortium) en varias etapas o "niveles" (levels). Las especificaciones de DOM se pueden consultar en http://www.w3c.org.
La especificación DOM Level 1 comenzó a finales del año 1997 (http://www.w3.org/TR/REC-DOM-LEVEL-1) siendo la actualización más reciente de 2000. Este primer nivel se limita a aquellos métodos necesarios para representar y manipular la estructura del documento y su contenido. DOM Level 2 incluye facilidades relacionadas con eventos, así como para la manipulación de hojas de estilo. La actualización más reciente de DOM Level 2 es de Noviembre de 2000 (http://www.w3.org/TR/REC-DOM-Level-2-Core). Actualmente se dispone de un borrador de la especificación del nivel 3 (de Octubre de 2002, (http://www.w3.org/TR/REC-DOM-Level-3-Val), que proporciona facilidades para mover nodos entre documentos, ordenación de nodos, así como un API para cargar ficheros XML y convertirlos a una representación DOM y al revés.
Las aplicaciones que utilizan DOM típicamente comienzan analizando algún
documento XML y obteniendo la representación DOM asociada (objeto Document
que representa al documento). Cómo se realiza esto no está especificado
en el nivel 1, y el nivel 2 proporciona solamente mejoras limitadas: existe
una clase que proporciona acceso a los métodos para crear objetos Document
,
pero no hay forma de acceder a un constructor de lectura/análisis de
un documento XML de forma independiente de la implementación.
Una vez que se tiene el modelo proporcionado por DOM, se puede acceder a las
distintas partes del documento XML asociado a través de las propiedades
y métodos del objeto Document
correspondiente. Dichas propiedades y métodos se definen en la especificación
DOM. La especificación proporcionada por el W3C define el API de DOM
para Java, ECMAScript, y OMG IDL.
DOM especifica un conjunto de interfaces para manejar documentos XML o HTML. El objetivo es obtener un modelo del documento en el que se represente su estructura.
Tanto los documentos XML, como HTML, tienen una estructura jerárquica,
por eso el modelo lógico resultante obtenido por DOM de dichos documentos
se asemeja a un árbol (o bosque). Sin embargo, es importante tener en
cuenta que la especificación de DOM no indica que los docuementos obtenidos
deben implementarse como un árbol (o bosque), ni especifica las relaciones
entre los objetos a implementar. DOM es un modelo lógico, que puede implementarse
de la manera que se crea más conveniente. Otra propiedad importante de
los modelos obtenidos por DOM es su isomorfismo estructural: si se usa dos implementaciones
distintas de DOM para obtener una representación del mismo documento,
se creará el mismo modelo de estructura, con los mismos objetos y relaciones
entre ellos.
Supongamos el siguiente texto, extraído de un documento XML:
<zoo> <mamiferos> <animal> Gato </animal> <animal> Perro </animal> </mamiferos> <reptiles> <animal> Boa </animal> <animal> Lagarto </animal> </reptiles> </zoo>
una implementación de DOM obtendría el siguiente modelo (ver Figura 3.1):
Figura 3.1. Modelo DOM asociado a un documento XML.
Los documentos se modelan usando objetos, y el modelo obtenido incluye no sólo la estructura de un documento, sino también el comportamiento de los objetos por los que está formado dicho documento. En otras palabras, los nodos en el diagrama anterior no representan una estructura de datos, sino objetos, los cuales tienen unas propiedades y un comportamiento. DOM identifica:
Las interfaces que pueden usarse para gestionar documentos XML (y HTML) son abstracciones (similares a las clases abstractas de Java), es decir, constituyen la manera de especificar la forma de acceder y manipular una representación de un documento de una aplicación. Las interfaces no implican una implementación concreta particular. Cada aplicación DOM es libre de mantener cualquier representación para los documentos que considere conveniente.
DOM está formado por dos partes: DOM Core (conjunto de interfaces para acceder a documentos XML) y el DOM HTML (extensión con interfaces específicas para acceder a documentos HTML).
DOM presenta los documentos como una jerarquía de objetos Node
,
que también implementan otras interfaces más especializadas. La
interfaz Node es el tipo de datos primario de un documento DOM, representando
un nodo del árbol. Algunos tipos de nodos pueden tener nodos hijo de
varios tipos, y otros son nodos hoja, que representan el nivel más inferior
en la estructura del documento. Los tipos de nodo, y qué tipos de nodos
pueden tener como hijos son los siguientes:
Tipos Node | Qué representa | Posibles nodos hijo | Atributos - valor |
Document | Representa todo el documento XML o HTML | Element (<=1), ProcessingInstruction, Comment, DocumentType |
name - #document value - null |
DocumentFragment | Representa una porción de un documento | Element, ProcessingInstruction, Comment, Text, CDATASection, EntityReference |
name - value - null |
DocumentType | Cada documento tiene un atributo "doctype" cuyo valor es nulo o bien un objeto DocumentType |
--
|
name - nombre del tipo de documento value - null |
EntityReference | Referencias a entidades | Element, ProcessingInstruction, Comment, Text, CDATASection, EntityReference |
name - nombre de la entidad referenciada value - null |
Element | Representa un elemento | Element, Text, Comment, ProcessingInstruction, CDATASection, EntityReference |
name - nombre value - null |
Attr | Atributo de un objeto Element | Text, EntityReference |
name - nombre del atributo value - valor atributo |
ProcessingInstruction | Instrucción de procesamiento |
--
|
name - destino value - contenido |
Comment | Comentarios |
--
|
name - #comment value - contenido del comentario |
Text | Texto |
--
|
name - #text value - texto del nodo |
CDATASection | Secciones de datos |
--
|
name - value - texto sección |
Entity | Entidad de un documento XML o HTML | Element, ProcessingInstruction, Comment, Text, CDATASection, EntityReference |
name - value - null |
Notation | Notación declarada en el DTD |
--
|
name- value - null |
DOM especifica una interfaz NodeList
para manejar listas
ordenadas de elementos Node (como por ejemplo hijos de un objeto Node
,
o los elementos devueltos por el método Element.getElementsByTagName
).
Otra interfaz importante es NamedNodeMap
, utilizada para
manejar conjuntos de nodos no ordenados, referenciados por su atributo name
(como por ejemplo los atributos de un objeto Element
).
Tanto NodeList
como NamedNodeMap
reflejan los cambios en la estructura del documento subyacente, de forma que
si un usuario utiliza un objeto NodeList
que contiene
el hijo de un objeto Element
, y posteriormente
el usuario añade más hijos a dicho elemento (o los elimina o modifica),
dichos cambios se reflejan automáticamente en el objeto NodeList
,
sin que el usuario tenga que hacerlo explícitamente. De igual forma,
los cambios en un elemento Node
de un árbol
se reflejan en todas las referencias a ese Node
en las NodeLists
y NamedNodeMaps
.
Otras interfaces consideradas fundamentales (y por lo tanto deben ser implementadas en cualquier implementación de DOM), son las siguientes:
DOMException
, para manejar situaciones
excepcionales,DOMImplementation
, proporciona
métodos para crear objetos Document
y
DocumentType
, así como el método
hasFeature()
para averiguar qué características
soporta una determinada implementación.DOM Level 2 (DOM2) está dividido en catorce módulos distribuídos en ocho paquetes diferentes (DOM1 se corresponde con los módulos Core y XML):
org.w3c.dom
): interfaces
básicas para representar un documentoorg.w3c.dom
): interfaces para manipular documentos XML
org.w3c.dom.html
): interfaces para manipular documentos
HTML org.w3c.dom.views
): interfaces para manejar vistas un
documento org.w3c.dom.stylesheets
): interfaces para representar
hojas de estiloorg.w3c.dom.css
): interfaces representar hojas de estilo
CSS org.w3c.dom.css
): clase que proporciona métodos para
fijar propiedades de las hojas de estiloorg.w3c.dom.events
): interfaces para asociar manejadores
de eventos a nodosorg.w3c.dom.events
): interfaces para manejar señales
para nodos que representan alguna forma de interfaz gráfica de usuario(GUI)org.w3c.dom.events
): interfaces manejar señales
provocadas por el uso del ratón org.w3c.dom.events
): interfaces manejar la
adición, modificación o borrado de nodosorg.w3c.dom.events
): Usa la interfaz DOMEvent para
informar de eventos específicos de los navegadores web org.w3c.dom.traversal
): Clases con utilidades para
realizar operaciones para "navegar" por la estructura de árbol org.w3c.dom.ranges
): Módulo opcional que extiende DOM
para cubrir secciones de documentos que no se corresponden exactamente con
elementos definidos. Por ejemplo, podría ser interesante indicar la sección
de texto que el usuario ha seleccionado con el ratón. DOM está definido casi en su totalidad por interfaces, en lugar de clases. Diferentes analizadores proporcionan su propia implementación de las interfaces estándar. DOM no está tan ampliamente soportado como SAX, pero la mayoría de los analizadores Java lo proporcionan, incluyendo Crimson, Serces, XML for Java, Oracle XML Parser for Java, y GNU JAXP.
DOM no está completo en sí mismo. Casi todos los programas DOM necesitan usar algunas clases específicas del analizador. No resulta demasiado difícil cambiar un analizador por otro, pero normalmente se requiere recompilar el programa. DOM2 no especifica como analizar un documento, crear un nuevo documento, o serializarlo en un fichero o flujo de datos. Estas importantes funciones son realizadas por clases específicas del analizador correspondiente.
JAXP, el API de Java para procesamiento de XML, palía algo esta deficiencia de DOM proporcionando formas estándar (independientes del analizador) para analizar documentos existentes, crear nuevos documentos y serializar árboles DOM (en memoria) en ficheros XML.
Si el analizador utilizado implementa JAXP, entonces en lugar de usar clases
específicas del analizador, se pueden usar las clases DocumentBuilderFactory
y DocumentBuilder
, del paquete javax.xml.parsers
,
para analizar los documentos. Los pasos a seguir son:
DocumentBuilderFactory.newInstance()
que devuelve un objeto DocumentBuilderFactory
. newDocumentBuilder()
de dicho objeto para devolver
una instancia específica del analizador de la clase abstracta DocumentBuilder
.
javax.xml.parsers.DocumentBuilderFactory
.
Si dicha propiedad no tiene ningún valor asignado, entonces JAXP busca en
el fichero de propiedades lib/jaxp.properties
en el directorio
jre
para determinar un valor por defecto para la propiedad del
sistema javax.xml.parsers.DocumentBuilderFactory
. Si se quiere
utilizar un cierto analizador DOM, por ejemplo gnu.xml.dom.JAXPFactory
,
se debe añadir la siguiente línea en ese fichero:
javax.xml.parsers.DocumentBuilderFactory=gnu.xml.dom.JAXPFactory
parse()
de DocumentBuilder
para leer el documento XML y devolver un objeto org.w3c.dom.Document
.
El proceso anterior se ilustra en la Figura 3.2:
Figura 3.2. Esquema del proceso de análisis de un documento XML.
Ejemplo 1. Lectura de un documento XML y obtención de un documento DOM
public class DomEcho { static Document document; public static void main(String argv[]) { if (argv.length != 1) { ... DocumentBuilderFactory factory= DocumentBuilderFactory.newInstance(); } try { DocumentBuilder builder = factory.newDocumentBuilder(); document = builder.parse( new File(argv[0])); } catch (...) { ... //captura de excepciones } } }
Ejemplo 2. Manipulación de un documento DOM
Una vez que el documento ha sido analizado (mediante el método parse
)
podemos olvidar las diferencias existentes entre los distintos analizadores
y trabajar con las interfaces estándar DOM.
Para navegar por la estructura del árbol, la interfaz org.w3c.dom.Node
define varios métodos para navegar por la estructura de un documento
DOM. Las operaciones getFirstChild
, getLastChild
,
getNextSibling
, getPreviousSibling
, y getParentNode
son suficientes para recorrer cualquier elemento del árbol.
La clase Restructurer
reordena los nodos de un árbol de
forma que los que representan instrucciones de procesamiento queden por encima
del nodo raiz, y los comentarios aparezcan debajo del nodo raiz
public class Restructurer { public static void processNode(Node current) throws DOMException { Node nextSibling = current.getNextSibling(); int nodeType = current.getNodeType(); if (nodeType == Node.COMMENT_NODE || nodeType == Node.PROCESSING_INSTRUCTION_NODE) { Node document = current.getOwnerDocument(); Node root = document.getFirstChild(); while (!(root.getNodeType() == Node.ELEMENT_NODE)) { root = root.getNextSibling(); } Node parent = current.getParentNode(); parent.removeChild(current); if (nodeType == Node.COMMENT_NODE) { document.appendChild(current); } else if (nodetype == Node.PROCESSING_INSTRUCTION_NODE) { document.insertBefore(current,root); } } else if (current.hasChildNodes()) { Node firstChild = current.getFirstChild(); processNode(firstChild); } if (nextSibling != null) { processNode(nextSibling); } } }
La clase RestructureDriver construye un documento DOM a partir de un documento
XML desde una URL, lo modifica y muestra el documento a través de System.out
public class RestructureDriver { public static void main(String[] args) { if (args.lenght <=0) { System.out.println("Usage: java RestructureDriver URL"); return; } String url = args[0]; try { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setNamespaceAware(true); DocumentBuilder parser = factory.newDocumentBuilder(); //lectura del documento Document document = parser.parse(url); //modificación del documento Restructurer.processNode(document); //escritura del documento resultante TransformerFactory xformFactory = TransformerFactory.newInstance(); Transformer idTransform = xformFactory.newTransformer(); Source input = new DOMSource(document); Result output = new StreamResult(System.out); idTransform.transform(input,output); } catch (...) { //captura de excepciones } } }
El paquete javax.xml.transform
permite serializar un documento
DOM. Los pasos a seguir son:
TransformerFactory.newInstance()
para obtener
un objeto javax.xml.transform.TransformerFactory
newTransformer()
de dicho objeto obtenido
para obtener una instancia específica de la implementación de
la clase abstracta javax.xml.transform.Transformer
javax.xml.transform.dom.DOMsource
a
partir del objeto DOM Document
javax.xml.transform.stream.StreamResult
conectado al OutputStream
en el que se quiere escribir el documentotransform()
del objeto Transformer
anteriormente creado sobre los objetos original y resultanteEjemplo 3. Creación de un documento DOM
Podemos crear un documento DOM vacío, en lugar de analizar un documento XML existente, al que añadimos un nodo raiz y varios nodos hijo.
La interfaz abstracta que crea nuevos objetos Document
se llama
DomImplementation
, dichos objetos, una vez creados, pueden ser
serializados en un stream
o un fichero. Para crear un documento
se utiliza el método:
public Document createDocument (String rootElementNamespaceURI, String rootElementQualifiedName, DocumentType doctype);
Si el documento simplemente es un documento bien formado, puede utilizarse
null
como argumento doctype
. Si el elemento raíz
del documento no está registrado en un espacio de nombres, también
puede utilizarse el valor null como primer argumento. Los objetos Document
creados carecen de elementos por debajo del nodo raíz, de forma que deben
usarse los métodos de la interfaz Document
para crear nuevos
nodos, y métodos de la interfaz Node
para añadir
dichos nodos al árbol.
La mayoría de programas que crean nuevos documentos siguen la siguiente estructura:
public class FibonacciDOM { public static void main(String[] args) { try { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setNamespaceAware(true); DocumentBuilder builder = factory.newDocumentBuilder(); DOMImplementation impl = builder.getDOMImplementation(); Document doc = impl.createDocument(null, "Fibonacci_Numbers", null); BigInteger low = BigInteger.ONE; BigInteger high = BigInteger.ONE; Element root = doc.getDocumentElement(); for (int i = 0; i < 10; i++) { Text space = doc.createTextNode("\n ");Este programa produce documentos como el siguiente:
root.appendChild(space); Element number = doc.createElement("fibonacci"); Text text = doc.createTextNode(low.toString()); number.appendChild(text); root.appendChild(number); BigInteger temp = high; high = high.add(low); low = temp; } Text lineBreak = doc.createTextNode("\n");
root.appendChild(lineBreak); // Serialización del documento TransformerFactory xformFactory = TransformerFactory.newInstance(); Transformer idTransform = xformFactory.newTransformer(); Source input = new DOMSource(doc); Result output = new StreamResult(System.out); idTransform.transform(input, output); } catch (...) { //captura de excepciones } } }
<?xml version="1.0" encoding="utf-8"?><Fibonacci_Numbers>
<fibonacci>1</fibonacci> <fibonacci>1</fibonacci> <fibonacci>2</fibonacci> <fibonacci>3</fibonacci> <fibonacci>5</fibonacci>
<fibonacci>8</fibonacci> <fibonacci>13</fibonacci> <fibonacci>21</fibonacci> <fibonacci>34</fibonacci> <fibonacci>55</fibonacci>
</Fibonacci_Numbers>
Ejemplo 4. Búsqueda de un nodo
Los métodos getFirstChild
, getLastChild
, getNextSibling
,
getPreviousSibling
, y getParentNode
son suficientes
para recorrer cualquier elemento del árbol.
public Node findSubNode(String name, Node node) { if (node.getNodeType() != Node.ELEMENT_NODE) { System.err.println("Error: El nodo buscado no es de ese tipo"); System.exit(22); } if (! node.hasChildNodes()) return null; NodeList list = node.getChildNodes(); for (int i=0; i < list.getLength(); i++) { Node subnode = list.item(i); if (subnode.getNodeType() == Node.ELEMENT_NODE) { if (subnode.getNodeName() == name) return subnode; } } return null; }
Ejemplo 5. Obtención del contenido de un nodo
Cuando se quiere obtener el texto que contiene un nodo, es conveniente navegar por la lista de nodos hijo, ignorando aquellos nodos que no contienen texto.
public String getText(Node node) { StringBuffer result = new StringBuffer(); if (! node.hasChildNodes()) return ""; NodeList list = node.getChildNodes(); for (int i=0; i < list.getLength(); i++) { Node subnode = list.item(i); if (subnode.getNodeType() == Node.TEXT_NODE) { result.append(subnode.getNodeValue()); } else if (subnode.getNodeType() == Node.CDATA_SECTION_NODE) { result.append(subnode.getNodeValue()); } else if (subnode.getNodeType() == Node.ENTITY_REFERENCE_NODE) { result.append(getText(subnode)); } } return result.toString(); }