Como ya comenté, utilizaremos las implementaciones DOM para Java JAXP (Java Api for XML Processing, desarrollada por Sun) y Xerces (desarrollada por Apache). La implementación JAXP viene con la librería básica de Java (vamos la que te descargas de la página oficial de Sun: SDK o JRE), tanto en la versión 1.4.2 como en las posteriores. Según he podido comprobar la implementación de DOM de Xerces también ha sido incluida en la librería básica de Java a partir de la versión 1.5.0. Por tanto si usas java 1.4.2 necesitarás incluir la librería de Xerces en tu aplicación. Por el contrario, con Java 1.5 o 1.6 no necesitarás librerías adicionales.
Vamos a partir del fichero XML siguiente:
<?xml version="1.0" encoding="UTF-8"?>
<etiquetaPrincipal xmlns:xela="http://www.latascadexela.es" >
<etiquetaHija id="1" atributo1="valorAtributo1" atributo2="valorAtributo2">
Texto dentro de la etiqueta hija
</etiquetaHija>
<!-- Comentario -->
<xela:etiquetaConNamespace descripcion="etiqueta con un namespace"/>
</etiquetaPrincipal>
Parseando un XML
Vamos a ver como obtener el objeto Document del árbol de nodos partiendo de un fichero XML.
Con JAXP
// Construimos nuestro DocumentBuilder
DocumentBuilder documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
// Procesamos el fichero XML y obtenemos nuestro objeto Document
Document doc = documentBuilder.parse(new InputSource(new FileInputStream("/ruta_a_fichero/fichero.xml")));
Con Xerces
// Creamos el parseador
DOMParser parser = new DOMParser();
// Procesamos el fichero XML
parser.parse(new InputSource(new FileInputStream("/ruta_a_fichero/fichero.xml")));
// Obtenemos el objeto Document
Document doc = parser.getDocument();
Recorriendo el árbol de nodos
Una vez tenemos el objeto Document podemos ir descendiendo por el árbol de nodos a partir de la etiqueta principal. Y así sucesivamente por los hijos de los nodos.
// Obtenemos la etiqueta raiz
Element elementRaiz = doc.getDocumentElement();
// Iteramos sobre sus hijos
NodeList hijos = elementRaiz.getChildNodes();
for(int i=0;i<hijos.getLength();i++){
Node nodo = hijos.item(i);
if (nodo instanceof Element){
System.out.println(nodo.getNodeName());
}
}
Como podéis ver no está de más asegurarse de que los nodos devueltos son Element.No todos los hijos de un Element lo serán. Puede evitar muchos errores el acostumbrase a ello, ya que son errores en tiempo de ejecución y el compilador no nos avisa de ellos.
Sin embargo, lo que nos interesa normalmente es obtener una serie de etiquetas a partir de su nombre. Es aquí donde podemos usar el método getElementsByTagName o getElementsByTagNameNS si buscamos etiquetas con espacios de nombres. Por cierto, getElementsByTagNameNS no me ha funcionado con JAXP, por lo que si usáis espacios de nombres os aconsejo Xerces.
// Buscamos una etiqueta dentro del XML
NodeList listaNodos = doc.getElementsByTagName("etiquetaHija");
for(int i=0;i<listaNodos.getLength();i++){
Node nodo = listaNodos.item(i);
if (nodo instanceof Element){
System.out.println(nodo.getTextContent());
}
}
// Buscamos una etiqueta dentro del XML con Namespaces
NodeList listaNodosNS = doc.getElementsByTagNameNS("http://www.latascadexela.es","etiquetaConNamespace");
for(int i=0;i<listaNodosNS.getLength();i++){
Node nodo = listaNodosNS.item(i);
if (nodo instanceof Element){
System.out.println(nodo.getAttributes().getNamedItem("descripcion").getTextContent());
}
}
Este método puede parecer la solución a todos tus problemas, pero ¿qué ocurre si tenemos etiquetas con el mismo nombre en distintas partes del XML? Por ejemplo, supongamos el XML siguente:
<?xml version="1.0" encoding="UTF-8"?>
<libros>
<prestamo>
<libro isbn="978-84-8346-520-2">
<titulo>El Ocho</titulo>
<autor>Katherine Neville</autor>
<anyo>1988</anyo>
<editorial>Ballantine Books</editorial>
</libro>
<libro isbn="84-226-6765-7">
<titulo>Qumrán</titulo>
<autor>Eliette Abécasis</autor>
<anyo>1997</anyo>
<editorial>Ediciones B</editorial>
</libro>
</prestamo>
<venta>
<libro isbn="84-675-069-X">
<titulo>Memorias de Idhún I - La Resistencia</titulo>
<autor>Laura Gallego García</autor>
<anyo>2005</anyo>
<editorial>Ediciones SM</editorial>
</libro>
<libro isbn="978-84-8346-808-1">
<titulo>Next</titulo>
<autor>Michael Crichton</autor>
<anyo>2008</anyo>
<editorial>DeBolsillo</editorial>
</libro>
</venta>
</libros>
Si buscamos las etiquetas que se llamen libro obtendremos tanto los libros en préstamo como los libros en venta. ¿Cómo diferenciamos cuáles son unos y cuáles otros? Evidentemente, preguntando por el padre getParentNode(). Pero hay una forma más sencilla, y es aquí cuando entra en escena nuestro amigo XPath.
Buscando con XPath
XPath(XML Path Language) es un lenguaje que permite construir expresiones
que recorren y procesan un documento XML. La idea es parecida a las expresiones regulares pero aplicado a XML.
Básicamente se trata de describir una ruta a través del árbol de nodos como si cada etiqueta fuera un directorio. Por ejemplo, /etiquetaPrincipal/etiquetaHija. Esta ruta XPath seleccionaría la etiquetaHija de nuestro XML. Por supuesto, es mucho más complejo y pontente: hay funciones, caracteres comodín, condicionales,... Podría escribir mucho acerca de XPath pero creo lo mejor es que echéis un vistazo a la página de donde aprendí yo y de paso la guardéis en vuestros favoritos ya que es una buena web de referencia: Tutorial de XPath.
¿Cómo aplicamos XPath con DOM? Los siguientes ejemplos lo muestran:
Con JAXP
// Buscamos una etiqueta mediante XPath.
// Implementación de XPath por defecto en Java
Node etiquetaHija = (Node)(XPathFactory.newInstance().newXPath().evaluate("/etiquetaPrincipal/etiquetaHija", doc, XPathConstants.NODE));
if (etiquetaHija!=null){
System.out.println(etiquetaHija.getTextContent());
}
Mediante el tercer parámetro del método evaluate, indicamos lo que esperamos recibir. Puede ser: booleano, lista de nodos, un nodo, un número o una cadena.
Si vamos a evaluar una misma expresión XPath sobre distintos Document es más eficiente compilar la expresión XPath y a partir del objeto XPathExpresion evaluar sobre los distintos Document:
XPathExpression xpathCompilada = XPathFactory.newInstance().newXPath().compile("/etiquetaPrincipal/etiquetaHija");
Node etiqueta1 = (Node)xpathCompilada.evaluate(doc1, XPathConstants.NODE);
Node etiqueta2 = (Node)xpathCompilada.evaluate(doc2, XPathConstants.NODE);
[..]
Node etiquetaN = (Node)xpathCompilada.evaluate(docN, XPathConstants.NODE);
Con Xerces
// Buscamos una etiqueta mediante XPath.
// Implementación de XPath de Xerces
Node etiquetaHija = XPathAPI.selectSingleNode(doc, "/etiquetaPrincipal/etiquetaHija");
if (etiquetaHija!=null){
System.out.println(etiquetaHija.getTextContent());
}
Existen métodos distintos para obtener una lista de nodos o un único nodo. En este caso hemos usado el de seleccionar un único nodo.
Como podemos observar, el problema del XML de los libros se resuelve fácilmente con XPath. Podemos sacar la lista de libros en préstamo /libros/prestamo/libro y la lista de libros en venta /libros/venta/libro.
Modificando el XML
Como decía, con DOM también se puede modificar el XML. Esto se hace modificando el árbol de nodos. Por ejemplo, veamos cómo añadir una etiqueta nueva. Lo primero que hay que hacer es crearla usando el Document donde la vamos a insertar y después la añadimos como hija a otra etiqueta.
// Añadimos una nueva etiqueta al documento
// Primero creamos la etiqueta (element)
Element nuevaEtiqueta = doc.createElement("nuevaEtiqueta");
// Añadimos atributos
nuevaEtiqueta.setAttribute("atributoNuevo", "Es un nuevo atributo");
// Añadimos contenido
nuevaEtiqueta.setTextContent("Contenido dentro de la nueva etiqueta");
// después se la añadimos como hija a una etiqueta ya existente
etiquetaHija.appendChild(nuevaEtiqueta);
Creando XML desde cero
Con DOM también podemos crear un documento XML nuevo. Este sería el paso inverso al parseo, es decir, mapeo de objetos a XML. Para ello usamos el DocumentBuilder de JAXP.
// Vamos a crear un XML desde cero
// En este caso usamos DocumentBuilder
DocumentBuilder docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
// Creamos el documento XML
Document docNuevo = docBuilder.newDocument();
Copiando nodos de un Document a otro
También podemos copiar o mover elementos de un Document a otro. Como decía en el post anterior, para que un nodo pueda ser añadido al árbol de nodos de un Document concreto ha de pertenecer al ámbito de dicho Document. Por ello lo primero es obtener una instancia del element que queremos copiar bajo el ámbito del Document sobre el que lo queremos pegar. Esto se hace mediante el método import, si queremos copiar, o mediante el método adopt, si lo que queremos es mover. Es decir, adopt elimina el elemento del Document anterior. Una vez hecho esto ya lo situamos donde deseemos.
// Incluso podemos copiar parte de otro documeto XML en este nuevo
// Vamos a copiar todo el XML parseado en este nuevo
// Primero obtenemos la etiqueta Raiz del XML parseado al principio
Element etiquetaRaizACopiar = doc.getDocumentElement();
// Luego la copiamos bajo nuestra etiqueta hijaRaiz, por ejemplo
Node etiquetaRaizCopiada = docNuevo.importNode(etiquetaRaizACopiar, true); // El segundo atributo indica si queremos copiar los hijos
// Ya tenemos una copia de la etiqueta en nuestro document. Ahora la situamos bajo etiquetaHija
hijaRaiz.appendChild(etiquetaRaizCopiada);
// ImportNode hace una copia, dejando el xml original intacto
// AdoptNode mueve, es decir, elimina los elementos del arbol original y los pega en el nuevo-
// Este método siempre es recursivo.
// Por ejemplo, adoptemos la etiquetaHija.
Node etiquetaHijaAdoptada = docNuevo.adoptNode(etiquetaHija);
// Ya tenemos la etiquetaHija en nuestro document y se ha eliminado del anterior.
// La situamos bajo la etiqueta raiz
raiz.appendChild(etiquetaHijaAdoptada);
Una vez ejecutado el ejemplo y si obtenemos la representación en formato de String de los dos Document veríamos lo siguiente:
XML parseado:
<?xml version="1.0" encoding="UTF-8"?>
<etiquetaPrincipal xmlns:xela="http://www.latascadexela.es">
<!-- Comentario -->
<xela:etiquetaConNamespace descripcion="etiqueta con un namespace"/>
</etiquetaPrincipal>
XML nuevo:
<?xml version="1.0" encoding="UTF-8"?>
<etiquetaRaiz>
<etiquetaHijaRaiz>
<etiquetaPrincipal xmlns:xela="http://www.latascadexela.es">
<etiquetaHija atributo1="valorAtributo1" atributo2="valorAtributo2" id="1">
Texto dentro de la etiqueta hija
<nuevaEtiqueta atributoNuevo="Es un nuevo atributo">Contenido dentro de la nueva etiqueta</nuevaEtiqueta>
</etiquetaHija>
<!-- Comentario -->
<xela:etiquetaConNamespace descripcion="etiqueta con un namespace"/>
</etiquetaPrincipal>
</etiquetaHijaRaiz>
<etiquetaHija atributo1="valorAtributo1" atributo2="valorAtributo2" id="1">
Texto dentro de la etiqueta hija
<nuevaEtiqueta atributoNuevo="Es un nuevo atributo">Contenido dentro de la nueva etiqueta</nuevaEtiqueta>
</etiquetaHija>
</etiquetaRaiz>
Como podemos observar, la etiquetaHija fue adoptada por el nuevo Documento y por tanto eliminada del original. Esto no ocurre con el resto de etiquetas ya que tan sólo fueron importadas.
Serializando el XML
Lo primero que hicimos fue pasar un fichero XML a un árbol DOM. A partir de ahí hemos aprendido a modificar el árbol, incluso a crear nuevos documentos XML. Ahora vamos a ver como volver a convertir dicho árbol en un fichero XML. Digo fichero XML pero podría ser un String o un array de bytes. En general, cualquier cosa que pueda ser serializable mediante un Writer o un OutputStream.
// Vamos a convertir el arbol DOM en un String
// Definimos el formato de salida: encoding, identación, separador de línea,...
// Pasamos doc como argumento para tener un formato de partida
OutputFormat format = new OutputFormat(doc);
format.setLineSeparator(LineSeparator.Unix);
format.setIndenting(true);
format.setLineWidth(0);
format.setPreserveSpace(false);
// Definimos donde vamos a escribir. Puede ser cualquier OutputStream o un Writer
CharArrayWriter salidaXML = new CharArrayWriter();
// Serializamos el arbol DOM
XMLSerializer serializer = new XMLSerializer((Writer)salidaXML,format);
serializer.asDOMSerializer();
serializer.serialize(doc);
// Ya tenemos el XML serializado en el objeto salidaXML
System.out.println(salidaXML.toString());
Vemos como hemos hecho uso del OutputFormat para controlar el formato de salida. Podemos controlar si queremos que salga la declaración de xml (<?xml version ....), el encoding, la identación, si queremos un salto de línea tras cada etiqueta,....
Con esto hemos visto lo más importante de DOM según mi punto de vista. A continuación dejo el código completo tanto de la clase de ejemplo de uso de Xerces como la de JAXP.
ProcesaXMLJAXP.java
package es.latascadexela.xml.dom;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
/**
* Clase de ejemplo de procesado de XML mediante DOM.
* Usa la implementación por defecto de Java (JAXP)
*
* @author Xela
*
*/
public class ProcesaXMLJAXP {
public static void main(String[] args) {
try {
// Implementación DOM por defecto de Java
// Construimos nuestro DocumentBuilder
DocumentBuilder documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
// Procesamos el fichero XML y obtenemos nuestro objeto Document
Document doc = documentBuilder.parse(new InputSource(new FileInputStream("/ruta_a_fichero/fichero.xml")));
// Obtenemos la etiqueta raiz
Element elementRaiz = doc.getDocumentElement();
// Iteramos sobre sus hijos
NodeList hijos = elementRaiz.getChildNodes();
for(int i=0;i<hijos.getLength();i++){
Node nodo = hijos.item(i);
if (nodo instanceof Element){
System.out.println(nodo.getNodeName());
}
}
// Buscamos una etiqueta dentro del XML
NodeList listaNodos = doc.getElementsByTagName("etiquetaHija");
for(int i=0;i<listaNodos.getLength();i++){
Node nodo = listaNodos.item(i);
if (nodo instanceof Element){
System.out.println(nodo.getTextContent());
}
}
// Buscamos una etiqueta dentro del XML con Namespaces
NodeList listaNodosNS = doc.getElementsByTagNameNS("http://www.latascadexela.es","etiquetaConNamespace");
for(int i=0;i<listaNodosNS.getLength();i++){
Node nodo = listaNodosNS.item(i);
if (nodo instanceof Element){
System.out.println(nodo.getAttributes().getNamedItem("descripcion").getTextContent());
}
}
// Buscamos una etiqueta mediante XPath.
// Implementación de XPath por defecto en Java
Node etiquetaHija = (Node)(XPathFactory.newInstance().newXPath().evaluate("/etiquetaPrincipal/etiquetaHija", doc, XPathConstants.NODE));
if (etiquetaHija!=null){
System.out.println(etiquetaHija.getTextContent());
}
// Añadimos una nueva etiqueta al documento
// Primero creamos la etiqueta (element)
Element nuevaEtiqueta = doc.createElement("nuevaEtiqueta");
// después se la añadimos como hija a una etiqueta ya existente
etiquetaHija.appendChild(nuevaEtiqueta);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (SAXException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (XPathExpressionException e) {
e.printStackTrace();
} catch (ParserConfigurationException e) {
e.printStackTrace();
}
}
}
ProcesaXMLXerces.java
package es.latascadexela.xml.dom;
import java.io.CharArrayWriter;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.Writer;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import com.sun.org.apache.xerces.internal.parsers.DOMParser;
import com.sun.org.apache.xml.internal.serialize.LineSeparator;
import com.sun.org.apache.xml.internal.serialize.OutputFormat;
import com.sun.org.apache.xml.internal.serialize.XMLSerializer;
import com.sun.org.apache.xpath.internal.XPathAPI;
/**
* Clase de ejemplo de procesado de XML mediante DOM.
* Usa la implementación de Xerces
*
* @author Xela
*
*/
public class ProcesaXMLXerces {
public static void main(String[] args) {
try {
// Implementación DOM de Xerces
// Creamos el parseador
DOMParser parser = new DOMParser();
// Procesamos el fichero XML
parser.parse(new InputSource(new FileInputStream("/ruta_a_fichero/fichero.xml")));
// Obtenemos el objeto Document
Document doc = parser.getDocument();
// Obtenemos la etiqueta raiz
Element elementRaiz = doc.getDocumentElement();
// Iteramos sobre sus hijos
NodeList hijos = elementRaiz.getChildNodes();
for(int i=0;i<hijos.getLength();i++){
Node nodo = hijos.item(i);
if (nodo instanceof Element){
System.out.println(nodo.getNodeName());
}
}
// Buscamos una etiqueta dentro del XML
NodeList listaNodos = doc.getElementsByTagName("etiquetaHija");
for(int i=0;i<listaNodos.getLength();i++){
Node nodo = listaNodos.item(i);
if (nodo instanceof Element){
System.out.println(nodo.getTextContent());
}
}
// Buscamos una etiqueta dentro del XML con Namespaces
NodeList listaNodosNS = doc.getElementsByTagNameNS("http://www.latascadexela.es","etiquetaConNamespace");
for(int i=0;i<listaNodosNS.getLength();i++){
Node nodo = listaNodosNS.item(i);
if (nodo instanceof Element){
System.out.println(nodo.getAttributes().getNamedItem("descripcion").getTextContent());
}
}
// Buscamos una etiqueta mediante XPath.
// Implementación de XPath de Xerces
Node etiquetaHija = XPathAPI.selectSingleNode(doc, "/etiquetaPrincipal/etiquetaHija");
if (etiquetaHija!=null){
System.out.println(etiquetaHija.getTextContent());
}
// Añadimos una nueva etiqueta al documento
// Primero creamos la etiqueta (element)
Element nuevaEtiqueta = doc.createElement("nuevaEtiqueta");
// Añadimos atributos
nuevaEtiqueta.setAttribute("atributoNuevo", "Es un nuevo atributo");
// Añadimos contenido
nuevaEtiqueta.setTextContent("Contenido dentro de la nueva etiqueta");
// después se la añadimos como hija a una etiqueta ya existente
etiquetaHija.appendChild(nuevaEtiqueta);
// Vamos a convertir el arbol DOM en un String
// Definimos el formato de salida: encoding, identación, separador de línea,...
// Pasamos doc como argumento para tener un formato de partida
OutputFormat format = new OutputFormat(doc);
format.setLineSeparator(LineSeparator.Unix);
format.setIndenting(true);
format.setLineWidth(0);
format.setPreserveSpace(false);
// Definimos donde vamos a escribir. Puede ser cualquier OutputStream o un Writer
CharArrayWriter salidaXML = new CharArrayWriter();
// Serializamos el arbol DOM
XMLSerializer serializer = new XMLSerializer((Writer)salidaXML,format);
serializer.asDOMSerializer();
serializer.serialize(doc);
// Ya tenemos el XML serializado en el objeto salidaXML
System.out.println(salidaXML.toString());
// Vamos a crear un XML desde cero
// En este caso usamos DocumentBuilder
DocumentBuilder docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
// Creamos el documento XML
Document docNuevo = docBuilder.newDocument();
// Creamos la etiqueta raiz
Element raiz = docNuevo.createElement("etiquetaRaiz");
docNuevo.appendChild(raiz);
// Creamos una etiqueta hija de la etiqueta raiz
Element hijaRaiz = docNuevo.createElement("etiquetaHijaRaiz");
raiz.appendChild(hijaRaiz);
// Incluso podemos copiar parte de otro documeto XML en este nuevo
// Vamos a copiar todo el XML parseado en este nuevo
// Primero obtenemos la etiqueta Raiz del XML parseado al principio
Element etiquetaRaizACopiar = doc.getDocumentElement();
// Luego la copiamos bajo nuestra etiqueta hijaRaiz, por ejemplo
Node etiquetaRaizCopiada = docNuevo.importNode(etiquetaRaizACopiar, true); // El segundo atributo indica si queremos copiar los hijos
// Ya tenemos una copia de la etiqueta en nuestro document. Ahora la situamos bajo etiquetaHija
hijaRaiz.appendChild(etiquetaRaizCopiada);
// ImportNode hace una copia, dejando el xml original intacto
// AdoptNode mueve, es decir, elimina los elementos del arbol original y los pega en el nuevo-
// Este método siempre es recursivo.
// Por ejemplo, adoptemos la etiquetaHija.
Node etiquetaHijaAdoptada = docNuevo.adoptNode(etiquetaHija);
// Ya tenemos la etiquetaHija en nuestro document y se ha eliminado del anterior.
// La situamos bajo la etiqueta raiz
raiz.appendChild(etiquetaHijaAdoptada);
// Veamos los dos XML el nuevo y cómo ha quedado el parseado.
// Definimos donde vamos a escribir. Puede ser cualquier OutputStream o un Writer
CharArrayWriter salidaXMLParseado = new CharArrayWriter();
// Serializamos el arbol DOM
XMLSerializer serializerXMLParseado = new XMLSerializer((Writer)salidaXMLParseado,format);
serializerXMLParseado.asDOMSerializer();
serializerXMLParseado.serialize(doc);
// Ya tenemos el XML serializado en el objeto salidaXMLParseado
System.out.println("XML parseado: \n"+salidaXMLParseado.toString());
// Definimos donde vamos a escribir. Puede ser cualquier OutputStream o un Writer
CharArrayWriter salidaXMLNuevo = new CharArrayWriter();
// Serializamos el arbol DOM
XMLSerializer serializerXMLNuevo = new XMLSerializer((Writer)salidaXMLNuevo,format);
serializerXMLNuevo.asDOMSerializer();
serializerXMLNuevo.serialize(docNuevo);
// Ya tenemos el XML serializado en el objeto salidaXMLNuevo
System.out.println("XML nuevo: \n"+salidaXMLNuevo.toString());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (SAXException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (TransformerException e) {
e.printStackTrace();
} catch (ParserConfigurationException e) {
e.printStackTrace();
}
}
}
Espero que este post tan largo sea de utilidad.