Introducción:
En este post aprenderás a combinar dos poderosas herramientas de Java para el manejo de XML:
- SAX (Simple API for XML): para leer el XML de forma eficiente mediante eventos.
- JAXB (Java Architecture for XML Binding): para generar un nuevo XML a partir de objetos Java con anotaciones.
El objetivo del ejercicio es transformar un archivo XML que contiene información de países para que cada país incluya, dentro suyo, la lista de idiomas oficiales. Por ejemplo, el XML de salida tendrá la siguiente estructura para España:
<geografía>
<paises>
<pais nombre="España" habitantes="47420000">
<idiomas_oficiales>
<idioma>español</idioma>
</idiomas_oficiales>
</pais>
...
</paises>
</geografía>
Desarrollo del Ejercicio:
- Lectura del XML con SAX:
Se utiliza un parser SAX para leer el archivopaises.xml
de forma eficiente. Se implementa un handler (la clasePaisesHandler
) que se encarga de procesar cada elemento del XML. Al encontrar un elemento<pais>
, se crea una instancia de la clasePais
y se extrae el atributonombre
. Para los elementos<habitantes>
e<idioma>
, se capturan los datos y se asignan al objeto correspondiente. - Generación del nuevo XML con JAXB:
Una vez leídos los datos, se construye un objeto de tipoGeografia
, que contiene la lista de países. Cada objetoPais
incluye sus atributos y una lista de idiomas oficiales, mapeada mediante anotaciones JAXB. Finalmente, se genera el archivopaises_jaxb.xml
con la estructura deseada.
Código completo del ejercicio:
package EjerciciosClase;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.bind.annotation.*;
import javax.xml.bind.*;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
public class de_XML_a_XML_con_SAX_y_JAXB {
public static void main(String[] args) {
String inputXML = "paises.xml";
String outputXML = "paises_jaxb.xml";
try {
// Configuración del parser SAX para leer el XML de entrada
SAXParserFactory factory = SAXParserFactory.newInstance();
SAXParser saxParser = factory.newSAXParser();
PaisesHandler handler = new PaisesHandler();
saxParser.parse(new File(inputXML), handler);
// Se crea el objeto Geografia con la lista de países leídos
Geografia geografia = new Geografia();
geografia.setPaises(handler.getPaises());
// Configuración de JAXB para generar el XML de salida
JAXBContext jaxbContext = JAXBContext.newInstance(Geografia.class);
Marshaller marshaller = jaxbContext.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(geografia, new File(outputXML));
System.out.println("Archivo generado: " + outputXML);
} catch (Exception e) {
e.printStackTrace();
}
}
}
class PaisesHandler extends DefaultHandler {
private List<Pais> paises = new ArrayList<>();
private Pais paisActual;
private StringBuilder contenido = new StringBuilder();
private boolean enHabitantes = false;
private boolean enIdioma = false;
public List<Pais> getPaises() { return paises; }
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
if (qName.equalsIgnoreCase("pais")) {
paisActual = new Pais();
paisActual.setNombre(attributes.getValue("nombre"));
} else if (qName.equalsIgnoreCase("habitantes")) {
enHabitantes = true;
contenido.setLength(0);
} else if (qName.equalsIgnoreCase("idioma")) {
enIdioma = true;
contenido.setLength(0);
}
// Se ignora el inicio de "idiomas_oficiales" ya que es solo un contenedor
}
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
if (enHabitantes || enIdioma) {
contenido.append(new String(ch, start, length).trim());
}
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
if (qName.equalsIgnoreCase("pais")) {
paises.add(paisActual);
} else if (qName.equalsIgnoreCase("habitantes")) {
paisActual.setHabitantes(contenido.toString().trim());
enHabitantes = false;
} else if (qName.equalsIgnoreCase("idioma")) {
// Se añade el idioma directamente a la lista del país actual
paisActual.addIdioma(contenido.toString().replace("\"", "").trim());
enIdioma = false;
}
// Se ignora el fin de "idiomas_oficiales"
}
}
@XmlRootElement(name = "geografía")
@XmlAccessorType(XmlAccessType.FIELD)
class Geografia {
@XmlElementWrapper(name = "paises")
@XmlElement(name = "pais")
private List<Pais> paises;
public List<Pais> getPaises() { return paises; }
public void setPaises(List<Pais> paises) { this.paises = paises; }
}
@XmlAccessorType(XmlAccessType.FIELD)
class Pais {
@XmlAttribute(name = "nombre")
private String nombre;
@XmlAttribute(name = "habitantes")
private String habitantes;
@XmlElementWrapper(name = "idiomas_oficiales")
@XmlElement(name = "idioma")
private List<String> idiomas = new ArrayList<>();
public String getNombre() { return nombre; }
public void setNombre(String nombre) { this.nombre = nombre; }
public String getHabitantes() { return habitantes; }
public void setHabitantes(String habitantes) { this.habitantes = habitantes; }
public List<String> getIdiomas() { return idiomas; }
public void setIdiomas(List<String> idiomas) { this.idiomas = idiomas; }
public void addIdioma(String idioma) {
this.idiomas.add(idioma);
}
}
Explicación del Código:
- Lectura con SAX:
Se utilizaSAXParser
para procesar el archivopaises.xml
. La clasePaisesHandler
extiendeDefaultHandler
y sobreescribe los métodosstartElement
,characters
yendElement
para capturar la información de cada país y sus atributos (nombre, habitantes e idiomas). - Generación del XML con JAXB:
Una vez recopilada la información, se instancia la claseGeografia
y se asigna la lista de países. Mediante JAXB y las anotaciones@XmlRootElement
,@XmlAccessorType
,@XmlElementWrapper
y@XmlElement
, se genera un XML estructurado en el que cada país contiene un bloque<idiomas_oficiales>
con sus respectivos<idioma>
.
Conclusión:
Este ejercicio es ideal para quienes desean aprender a transformar la estructura de un XML en Java, combinando la eficiencia de SAX para el análisis y la simplicidad de JAXB para la generación de nuevos XML. ¡Pruébalo en tus proyectos y descubre lo potente que es trabajar con XML en Java!
