Con SAX no cargas el XML en memoria como un árbol (DOM), sino que lo procesas en streaming (evento a evento). Por eso:
- SELECT (lectura/filtrado): natural con SAX.
- INSERT/UPDATE/DELETE: no puedes “modificar” el documento cargado (no hay árbol). La forma habitual es leer y reescribir a un XML de salida aplicando los cambios en el momento adecuado (patrón streaming filter).
Ideas clave de SAX
- Usa
SAXParser
+ unDefaultHandler
(oXMLReader
+ContentHandler
). - Sobrescribes:
startDocument()
/endDocument()
startElement(...)
/endElement(...)
characters(char[] ch, int start, int length)
- Mantén estado con variables (p. ej.
boolean enTitulo
,String idActual
, o una pila de etiquetas). - Es rápido y ligero (no consume mucha memoria), pero no permite cambios in situ.
Arranque básico (parser)
SAXParserFactory factory = SAXParserFactory.newInstance();
factory.setNamespaceAware(true); // si usas namespaces
SAXParser saxParser = factory.newSAXParser();
XMLReader reader = saxParser.getXMLReader();
reader.setContentHandler(new MiHandlerSelect()); // tu handler
reader.parse("Catalogo.xml");
SELECT (leer/recorrer con SAX)
Handler típico de lectura/filtrado
class MiHandlerSelect extends org.xml.sax.helpers.DefaultHandler {
private StringBuilder buffer = new StringBuilder();
private String etiquetaActual = "";
private String idActual = null;
private boolean dentroDeObjeto = false; // p.ej. <Libro>
@Override
public void startElement(String uri, String localName, String qName, org.xml.sax.Attributes atts) {
etiquetaActual = qName;
buffer.setLength(0); // limpiar
if ("Libro".equals(qName)) { // cambia por tu TAG_OBJETO
dentroDeObjeto = true;
idActual = atts.getValue("id"); // leer atributo si existe
}
}
@Override
public void characters(char[] ch, int start, int length) {
// Acumula porque SAX puede trocear el texto
buffer.append(ch, start, length);
}
@Override
public void endElement(String uri, String localName, String qName) {
String texto = buffer.toString().trim();
if (dentroDeObjeto) {
if ("Titulo".equals(qName)) {
// ejemplo de uso: imprimir/guardar el titulo
// System.out.printf("Libro
}
// ... otros campos
}
if ("Libro".equals(qName)) {
dentroDeObjeto = false;
idActual = null;
}
etiquetaActual = "";
}
}
Búsqueda selectiva (por id o por condición)
- Mantén
idActual
y enendElement
solo procesa si cumple la condición:if ("Libro".equals(qName) && "123".equals(idActual)) { // mostrar datos acumulados / contadores / etc. }
Nota: con SAX no puedes hacer “getElementsByTagName”. El patrón es: reconocer que entras en el elemento objetivo en
startElement
, acumular datos, y decidir qué hacer al cerrar (endElement
).
“INSERT / UPDATE / DELETE” con SAX (patrón reescritura)
Como no hay modificaciones directas, se usa un handler que además escribe un nuevo XML. Hay dos formas típicas:
- SAX + StAX: el handler escribe con
XMLStreamWriter
. - SAXTransformerFactory (SAX → TransformerHandler) para encaminar eventos a un
StreamResult
.
Opción A: Handler + StAX (escritura manual)
import javax.xml.stream.*;
class FiltroEscrituraHandler extends org.xml.sax.helpers.DefaultHandler {
private XMLStreamWriter out;
private StringBuilder buffer = new StringBuilder();
private boolean dentroDeLibro = false;
private String idActual = null;
FiltroEscrituraHandler(XMLStreamWriter out) {
this.out = out;
}
@Override public void startDocument() {
try { out.writeStartDocument("UTF-8", "1.0"); } catch (Exception ignored) {}
}
@Override public void endDocument() {
try { out.writeEndDocument(); out.flush(); } catch (Exception ignored) {}
}
@Override
public void startElement(String uri, String local, String qName, org.xml.sax.Attributes atts) {
buffer.setLength(0);
try {
// --- DELETE: si este es el nodo a borrar, NO escribir startElement
boolean esLibro = "Libro".equals(qName);
if (esLibro) {
dentroDeLibro = true;
idActual = atts.getValue("id");
}
// Ejemplo DELETE de Libro con id=999: saltar escritura de todo el elemento
if (esLibro && "999".equals(idActual)) {
// Marcamos que vamos a omitir este bloque en endElement también
// En este patrón, puedes usar un flag "omitirBloque" o un contador de profundidad
omitir = true; profundidad = 1;
return;
}
if (omitir) { // si estamos omitiendo, solo contamos profundidad
profundidad++;
return;
}
// Escribir startElement y atributos tal cual
out.writeStartElement(qName);
for (int i = 0; i < atts.getLength(); i++) {
out.writeAttribute(atts.getQName(i), atts.getValue(i));
}
// --- INSERT: si necesitas insertar un nuevo hijo justo al entrar en cierta etiqueta:
// if ("Libros".equals(qName)) { escribirElementoInsertado(out); }
} catch (Exception ignored) {}
}
// Flags auxiliares para manejar DELETE por bloque
private boolean omitir = false;
private int profundidad = 0;
@Override
public void endElement(String uri, String local, String qName) {
try {
if (omitir) {
profundidad--;
if (profundidad == 0) { omitir = false; } // cerramos el salto
return;
}
String texto = buffer.toString();
// --- UPDATE: si estamos en Libro id=123 y cerramos <Titulo>, reescribimos texto nuevo
if ("Titulo".equals(qName) && "123".equals(idActual)) {
texto = "Nuevo título actualizado";
}
// Escribir texto acumulado si lo hay
if (!texto.isEmpty()) {
out.writeCharacters(texto);
}
out.writeEndElement();
if ("Libro".equals(qName)) {
dentroDeLibro = false;
idActual = null;
}
// --- INSERT: si queremos añadir un hermano después de cerrar cierto elemento, podemos escribirlo aquí.
// if ("Libros".equals(qName)) { escribirElementoInsertado(out); }
} catch (Exception ignored) {}
}
@Override
public void characters(char[] ch, int start, int length) {
buffer.append(ch, start, length);
}
// Ejemplo de método para insertar un nuevo <Libro>
private void escribirElementoInsertado(XMLStreamWriter out) throws Exception {
out.writeStartElement("Libro");
out.writeAttribute("id", "1001");
out.writeStartElement("Titulo"); out.writeCharacters("Libro insertado"); out.writeEndElement();
out.writeStartElement("Autor"); out.writeCharacters("Autor X"); out.writeEndElement();
out.writeEndElement(); // </Libro>
}
}
Uso del filtro de escritura:
// Preparar escritor de salida
XMLOutputFactory xof = XMLOutputFactory.newInstance();
XMLStreamWriter xw = xof.createXMLStreamWriter(new java.io.FileWriter("Catalogo_out.xml"), "UTF-8");
// Parsers SAX
SAXParserFactory f = SAXParserFactory.newInstance();
SAXParser p = f.newSAXParser();
XMLReader r = p.getXMLReader();
// Handler que reescribe aplicando INSERT/UPDATE/DELETE
r.setContentHandler(new FiltroEscrituraHandler(xw));
r.parse("Catalogo.xml");
xw.close();
Notas clave de esta opción
- DELETE: detecta el inicio del elemento a eliminar, activa
omitir=true
y lleva un contador de profundidad para saltar todo el subárbol. - UPDATE: modifica texto o atributos antes de escribirlos (normalmente al cerrar el elemento, porque ya acumulaste
characters
). - INSERT: escribe elementos adicionales en el punto deseado (al entrar o salir del padre adecuado).
Opción B: SAX → TransformerHandler (canalizar a salida)
Otra variante es usar SAXTransformerFactory
para que tu handler reemita eventos hacia un TransformerHandler
conectado a un StreamResult
. Funciona parecido a A (insertas/filtras antes de reenviar), pero con menos manejo manual de etiquetas. Útil si quieres identación automática.
Esquema:
SAXTransformerFactory tf = (SAXTransformerFactory) SAXTransformerFactory.newInstance();
TransformerHandler th = tf.newTransformerHandler();
javax.xml.transform.Transformer t = th.getTransformer();
t.setOutputProperty(javax.xml.transform.OutputKeys.INDENT, "yes");
th.setResult(new javax.xml.transform.stream.StreamResult(new java.io.File("Catalogo_out.xml")));
// Tu handler intermedio puede extender XMLFilterImpl y enviar a 'th' los eventos startElement/characters/endElement
Patrones por operación (resumen mental)
- SELECT
Leer en streaming, acumular texto por etiqueta, filtrar con flags/condiciones, contar o mostrar. - INSERT
Reescritura: cuando entres en<Coleccion>
o cuando salgas, emites un nuevo<Objeto>
completo (con atributos e hijos) hacia la salida. - UPDATE
Reescritura: para atributos, cambia el valor enstartElement
antes de escribirlo; para texto, cambia el contenido enendElement
usando el buffer acumulado. - DELETE
Reescritura: al detectar el<Objeto>
que quieres borrar, no emites sus eventos (start/characters/end) hasta cerrar el bloque (usa contador de profundidad).
Buenas prácticas con SAX
- Acumula texto en
characters
(puede llegar troceado). - Usa flags o una pila (
Deque<String>
) para conocer el camino actual (ruta de etiquetas) si tu XML es complejo. - Para DELETE por bloque, contador de profundidad obligatorio.
- Si necesitas reordenar elementos, SAX puro es incómodo; plantéate StAX o DOM puntualmente.
- Valora validación (
factory.setValidating(true)
) si tienes DTD/XSD (y manejasErrorHandler
).
Diferencias rápidas SAX vs DOM (para el examen)
- DOM: Carga todo el árbol, permite modificar en memoria y luego guardar. Más cómodo, más memoria.
- SAX: Procesa en streaming, no permite modificar in situ; para cambiar algo, lees y reescribes. Más rápido y ligero para ficheros grandes.
Estos son apuntes (patrones y esqueletos). Adapta los nombres de etiquetas/atributos y la lógica de inserción/actualización/borrado a tu XML real. Si luego quieres, te preparo un mini esqueleto completo “lector → escritor” con un caso de ejemplo.