3.5 XML¶
XML (eXtensible Markup Language) es otro formato de texto para guardar datos con estructura de árbol. Es más antiguo y más verboso que JSON, pero sigue muy presente en configuraciones, documentos, servicios web antiguos y en el propio Android.
<usuarios>
<usuario>
<id>1</id>
<nombre>Juan</nombre>
<edad>30</edad>
</usuario>
</usuarios>
Partes de un XML¶
| Parte | Qué es | Ejemplo |
|---|---|---|
| Elemento | Una etiqueta de apertura, su contenido y la de cierre | <nombre>Juan</nombre> |
| Atributo | Un dato dentro de la etiqueta de apertura | <usuario id="1"> |
| Texto | El contenido de un elemento | Juan |
| Raíz | El elemento que contiene a todos los demás (solo hay uno) | <usuarios> |
Para que un XML sea válido (bien formado): hay una sola raíz, cada etiqueta que se abre se cierra en el orden correcto, los atributos van entre comillas y se distingue entre mayúsculas y minúsculas. Los caracteres especiales se escriben con entidades: < (<), > (>) y & (&).
Java incluye el estándar DOM (javax.xml.parsers, org.w3c.dom): el documento se carga como un árbol de nodos que se puede recorrer y modificar. Es potente pero verboso. Para escribir el resultado se usa un Transformer.
Leer, modificar y escribir¶
import java.io.StringReader;
import java.io.StringWriter;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
public class Xml1 {
static String texto(Element padre, String etiqueta) {
return padre.getElementsByTagName(etiqueta).item(0).getTextContent();
}
static void mostrar(Element raiz) {
NodeList usuarios = raiz.getElementsByTagName("usuario");
for (int i = 0; i < usuarios.getLength(); i++) {
Element u = (Element) usuarios.item(i);
System.out.println("ID: " + texto(u, "id") + ", Nombre: " + texto(u, "nombre")
+ ", Edad: " + texto(u, "edad"));
}
}
public static void main(String[] args) throws Exception {
String xml = "<usuarios><usuario><id>1</id><nombre>Juan</nombre><edad>30</edad></usuario>"
+ "<usuario><id>2</id><nombre>Ana</nombre><edad>25</edad></usuario></usuarios>";
Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder()
.parse(new InputSource(new StringReader(xml)));
Element raiz = doc.getDocumentElement();
mostrar(raiz);
NodeList usuarios = raiz.getElementsByTagName("usuario");
for (int i = 0; i < usuarios.getLength(); i++) {
Element u = (Element) usuarios.item(i);
if (texto(u, "nombre").equals("Ana")) {
u.getElementsByTagName("edad").item(0).setTextContent("26"); // actualizar
}
}
Element nuevo = doc.createElement("usuario"); // insertar
String[][] campos = {{"id", "3"}, {"nombre", "Eva"}, {"edad", "22"}};
for (String[] campo : campos) {
Element hijo = doc.createElement(campo[0]);
hijo.setTextContent(campo[1]);
nuevo.appendChild(hijo);
}
raiz.appendChild(nuevo);
usuarios = raiz.getElementsByTagName("usuario"); // eliminar
for (int i = 0; i < usuarios.getLength(); i++) {
Element u = (Element) usuarios.item(i);
if (texto(u, "id").equals("1")) {
raiz.removeChild(u);
break;
}
}
System.out.println("--- después de los cambios ---");
mostrar(raiz);
Transformer t = TransformerFactory.newInstance().newTransformer();
t.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
StringWriter salida = new StringWriter();
t.transform(new DOMSource(raiz), new StreamResult(salida));
System.out.println(salida);
}
}
Salida:
ID: 1, Nombre: Juan, Edad: 30
ID: 2, Nombre: Ana, Edad: 25
--- después de los cambios ---
ID: 2, Nombre: Ana, Edad: 26
ID: 3, Nombre: Eva, Edad: 22
<usuarios><usuario><id>2</id><nombre>Ana</nombre><edad>26</edad></usuario><usuario><id>3</id><nombre>Eva</nombre><edad>22</edad></usuario></usuarios>
La idea es la misma que con JSON: cargar el texto como un árbol, buscar los elementos que interesan (usuario, y dentro id, nombre, edad), modificar el árbol y volver a convertirlo en texto. El texto de entrada de este ejemplo está en una sola línea para que la salida sea idéntica en los cuatro lenguajes; en un archivo real lo normal es escribirlo con sangría.
Crear un árbol, atributos y errores¶
import java.io.StringReader;
import java.io.StringWriter;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
public class Xml2 {
public static void main(String[] args) throws Exception {
DocumentBuilder constructor = DocumentBuilderFactory.newInstance().newDocumentBuilder();
Document doc = constructor.newDocument();
Element raiz = doc.createElement("usuarios");
doc.appendChild(raiz);
Element usuario = doc.createElement("usuario");
usuario.setAttribute("id", "1");
usuario.setTextContent("Juan");
raiz.appendChild(usuario);
Transformer t = TransformerFactory.newInstance().newTransformer();
t.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
StringWriter salida = new StringWriter();
t.transform(new DOMSource(raiz), new StreamResult(salida));
System.out.println(salida);
System.out.println("atributo id: " + usuario.getAttribute("id"));
try {
constructor.parse(new InputSource(new StringReader("<usuarios><usuario></usuarios>")));
} catch (SAXException e) {
System.out.println("El texto no es un XML válido");
}
}
}
Salida:
<usuarios><usuario id="1">Juan</usuario></usuarios>
atributo id: 1
El texto no es un XML válido
Este ejemplo muestra tres cosas: crear un árbol desde cero (un XML vacío con su raíz es el punto de partida cuando un archivo no existe), leer un atributo (id) y detectar un XML inválido. Un XML mal formado lanza una SAXException (concretamente SAXParseException); el analizador además escribe el aviso en la salida de errores.
JSON o XML¶
| JSON | XML | |
|---|---|---|
| Aspecto | Compacto | Más verboso (etiquetas de apertura y cierre) |
| Estructura | Objetos y arrays | Árbol de elementos, con atributos |
| Comentarios | No | Sí |
| Uso típico | APIs web, configuración | Documentos, configuraciones, Android |
Errores frecuentes¶
| Error | Cómo evitarlo |
|---|---|
| Olvidar cerrar una etiqueta o cerrarla en otro orden | Comprueba que el XML sea válido antes de procesarlo |
| Más de un elemento raíz | Un documento tiene una sola raíz |
& o < sueltos dentro del texto |
Escríbelos como & y < |
| Asumir que un elemento existe | Comprueba que el resultado de la búsqueda no sea nulo |
Para practicar¶
Haz el ejercicio 3.5 de XML, igual que el de JSON pero con un árbol de elementos.