CodeGym /Corsi /JAVA 25 SELF /Lavorare con DOM e SAX: parsing XML

Lavorare con DOM e SAX: parsing XML

JAVA 25 SELF
Livello 47 , Lezione 1
Disponibile

1. DOM (Document Object Model)

A volte un file XML è troppo grande per essere caricato interamente in memoria. Può anche capitare che la struttura del documento non sia nota in anticipo o sia troppo complessa. In altri casi è necessario elaborare solo porzioni dei dati, e non l'intero file, oppure modificare il documento durante l'esecuzione del programma: eliminare un nodo, aggiungere un nuovo elemento o ristrutturare una parte della struttura.

In queste situazioni sono adatti strumenti collaudati — DOM e SAX. Consentono di lavorare direttamente con il contenuto XML senza legarlo a classi Java definite in anticipo.

Come funziona DOM

DOM è un modo per rappresentare un documento XML come un albero di oggetti in memoria. Ogni tag diventa un nodo dell'albero (Node) e attributi, valori di testo e persino i commenti sono oggetti separati. Dopo aver caricato il documento hai pieno accesso alla sua struttura: puoi leggere, modificare, eliminare e aggiungere elementi e attributi.

Classi principali di DOM in Java

  • DocumentBuilderFactory — factory per creare un parser.
  • DocumentBuilder — parser che trasforma l'XML in un albero.
  • Document — oggetto radice dell'albero.
  • Element — elemento XML (tag).
  • NodeList — elenco di nodi (ad esempio, tutti gli <item> dentro <items>).

Esempio: lettura di un file XML con DOM

Supponiamo di dover leggere questo file XML:

<contacts>
    <person id="1">
        <name>Ivan</name>
        <email>ivan@example.com</email>
    </person>
    <person id="2">
        <name>Mariya</name>
        <email>maria@example.com</email>
    </person>
</contacts>

Codice: lettura e iterazione degli elementi

import javax.xml.parsers.*;
import org.w3c.dom.*;
import java.io.File;

public class DomExample {
    public static void main(String[] args) throws Exception {
        // 1. Creiamo la factory e il parser
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        DocumentBuilder builder = factory.newDocumentBuilder();

        // 2. Carichiamo il file XML in memoria
        Document doc = builder.parse(new File("contacts.xml"));

        // 3. Otteniamo l'elemento radice
        Element root = doc.getDocumentElement();
        System.out.println("Elemento radice: " + root.getTagName());

        // 4. Otteniamo l'elenco di tutti i <person>
        NodeList persons = root.getElementsByTagName("person");

        for (int i = 0; i < persons.getLength(); i++) {
            Element person = (Element) persons.item(i);

            String id = person.getAttribute("id");
            String name = person.getElementsByTagName("name").item(0).getTextContent();
            String email = person.getElementsByTagName("email").item(0).getTextContent();

            System.out.println("id: " + id + ", name: " + name + ", email: " + email);
        }
    }
}

Che cosa succede qui:

  • Per prima cosa creiamo il parser e carichiamo il file XML.
  • Otteniamo l'elemento radice (contacts).
  • Troviamo tutti gli elementi <person> e li iteriamo.
  • Per ciascun <person> leggiamo l'attributo id e gli elementi <name> e <email>.

Schema dell'albero DOM per l'esempio

contacts
├── person (id="1")
│   ├── name ("Ivan")
│   └── email ("ivan@example.com")
└── person (id="2")
    ├── name ("Mariya")
    └── email ("maria@example.com")

Modificare XML con DOM

DOM consente non solo di leggere, ma anche di modificare l'XML. Ad esempio, aggiungiamo una nuova persona:

// Creiamo un nuovo elemento <person>
Element newPerson = doc.createElement("person");
newPerson.setAttribute("id", "3");

// <name>
Element name = doc.createElement("name");
name.setTextContent("Sergey");
newPerson.appendChild(name);

// <email>
Element email = doc.createElement("email");
email.setTextContent("sergey@example.com");
newPerson.appendChild(email);

// Aggiungiamo alla radice
root.appendChild(newPerson);

// Salviamo le modifiche nel file
TransformerFactory tf = TransformerFactory.newInstance();
Transformer transformer = tf.newTransformer();
transformer.transform(new DOMSource(doc), new StreamResult(new File("contacts-updated.xml")));

Vantaggi e svantaggi principali di DOM

  • Vantaggi: comodo per file piccoli, facile apportare qualsiasi modifica, si può «navigare» nell'albero in qualsiasi direzione.
  • Svantaggi: l'intero XML è mantenuto in memoria. Per file grandi (centinaia di MB e oltre) — non è un'opzione: la memoria finirà rapidamente.

2. SAX (Simple API for XML)

SAX è un parser basato su eventi. Non costruisce un albero, ma legge l'XML da sinistra a destra e invoca i gestori di eventi: «è iniziato un elemento», «contenuto testuale», «l'elemento è terminato» ecc. Tu scrivi il tuo handler e reagisci agli eventi che ti servono.

Analogia:
Se DOM è come spargere tutte le pagine dell'agenda sul tavolo, SAX è come leggere l'agenda pagina per pagina e prendere appunti solo quando trovi la pagina che ti serve.

Classi principali di SAX in Java

  • SAXParserFactory, SAXParser — factory e parser.
  • DefaultHandler — classe base per la gestione degli eventi.
  • Metodi gestori: startElement, characters, endElement, startDocument, endDocument.

Esempio: lettura di un file XML con SAX

Supponiamo lo stesso file contacts.xml. Vogliamo semplicemente stampare i nomi e gli e‑mail di tutte le persone.

Codice: parser SAX

import javax.xml.parsers.*;
import org.xml.sax.*;
import org.xml.sax.helpers.DefaultHandler;
import java.io.File;

public class SaxExample {
    public static void main(String[] args) throws Exception {
        SAXParserFactory factory = SAXParserFactory.newInstance();
        SAXParser parser = factory.newSAXParser();

        parser.parse(new File("contacts.xml"), new ContactHandler());
    }
}

class ContactHandler extends DefaultHandler {
    private String currentElement = "";
    private String name = "";
    private String email = "";

    @Override
    public void startElement(String uri, String localName, String qName, Attributes attributes) {
        currentElement = qName;
        if ("person".equals(qName)) {
            String id = attributes.getValue("id");
            System.out.println("Nuova persona, id: " + id);
        }
    }

    @Override
    public void characters(char[] ch, int start, int length) {
        String text = new String(ch, start, length).trim();
        if (text.isEmpty()) return;
        if ("name".equals(currentElement)) {
            name = text;
        } else if ("email".equals(currentElement)) {
            email = text;
        }
    }

    @Override
    public void endElement(String uri, String localName, String qName) {
        if ("person".equals(qName)) {
            System.out.println("Nome: " + name + ", email: " + email);
            name = "";
            email = "";
        }
        currentElement = "";
    }
}

Ora vediamo in parole semplici cosa succede qui. Quando il parser SAX incontra un tag di apertura, per esempio <person>, chiama il metodo startElement. Lì leggiamo subito l'attributo id e lo stampiamo a schermo. Quando all'interno compare del testo — per esempio un nome o un e‑mail — il controllo passa al metodo characters, dove salviamo questo testo in variabili temporanee. E quando il parser raggiunge il tag di chiusura </person>, viene chiamato endElement. A quel punto conosciamo già il nome e l'e‑mail della persona e possiamo stamparli. Dopo di ciò le variabili vengono azzerate per essere pronte al contatto successivo.

L'idea è che SAX non conserva l'intero XML in memoria, ma funziona come un «lettore» in streaming: percorre il file dall'alto verso il basso e reagisce agli eventi — inizio tag, testo, fine tag. È veloce e parco di memoria, soprattutto per file grandi.

Confronto rapido tra DOM e SAX

DOM SAX
Stile Albero (tutta la struttura in memoria) Eventi (elaborazione «al volo»)
Memoria Carica l'intero XML Uso minimo della memoria
Modifiche Puoi leggere/modificare/aggiungere Sola lettura (di solito)
Ricerca dei dati Facile cercare ovunque Devi tenere traccia di «dove ti trovi»
Dimensione del file Per file piccoli e medi Per file molto grandi

3. Quando usare DOM e quando SAX

DOM è perfetto se:

  • Il file è piccolo o medio per dimensioni.
  • Devi accedere più volte a parti diverse dell'XML.
  • Devi modificare la struttura del documento.

SAX è la scelta giusta se:

  • Il file è enorme e non puoi caricarlo in memoria.
  • Devi estrarre rapidamente solo una parte delle informazioni (per esempio, trovare tutti gli elementi <item> con un certo attributo).
  • Servono alte prestazioni e uso minimo della memoria.
  • Non prevedi di modificare l'XML, solo di leggerlo.

Consiglio:
Nei progetti reali si usano spesso entrambi gli approcci: DOM — per impostazioni «umane» e piccoli config, SAX — per l'elaborazione di log, esportazioni e grandi importazioni.

4. Esercizio pratico: un piccolo parser per l'applicazione

Nella tua applicazione didattica (per esempio, una «rubrica contatti») è comparso un requisito: contare rapidamente il numero di utenti con e‑mail su gmail.com.

Soluzione DOM:

DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(new File("contacts.xml"));

NodeList emails = doc.getElementsByTagName("email");
int count = 0;
for (int i = 0; i < emails.getLength(); i++) {
    String email = emails.item(i).getTextContent();
    if (email.endsWith("@gmail.com")) {
        count++;
    }
}
System.out.println("Utenti con gmail.com: " + count);

Soluzione SAX:

class GmailCounterHandler extends DefaultHandler {
    private String currentElement = "";
    int count = 0;

    @Override
    public void startElement(String uri, String localName, String qName, Attributes attributes) {
        currentElement = qName;
    }

    @Override
    public void characters(char[] ch, int start, int length) {
        if ("email".equals(currentElement)) {
            String email = new String(ch, start, length).trim();
            if (email.endsWith("@gmail.com")) {
                count++;
            }
        }
    }
    @Override
    public void endDocument() {
        System.out.println("Utenti con gmail.com: " + count);
    }
}

5. Caratteristiche e sfumature di lavoro con DOM e SAX

  • DOM può «mangiarsi» tutta la memoria se il file XML è molto grande. Se all'improvviso vedi un errore OutOfMemoryError, probabilmente è ora di passare a SAX.
  • SAX richiede molta attenzione: devi tenere traccia di quale elemento stai elaborando e raccogliere con cura i dati necessari. Talvolta occorre usare uno stack o variabili aggiuntive per non perdersi in strutture molto annidate.
  • In SAX il metodo characters può essere chiamato più volte per lo stesso blocco di testo (soprattutto se il testo è lungo o contiene caratteri speciali). Meglio accumulare il testo in StringBuilder.
  • DOM è ottimo per ricerca, navigazione e modifica della struttura, ma non per l'elaborazione in streaming.
  • Se non sei sicuro di quale approccio scegliere — inizia con DOM per semplicità; se poi diventa «pesante» in termini di memoria — riscrivi usando SAX.

6. Errori tipici nel lavoro con DOM e SAX

Errore n. 1: gestione errata degli spazi e delle interruzioni di riga in SAX.
Il metodo characters può restituire frammenti di testo, inclusi spazi e interruzioni di riga tra gli elementi. Se non si filtra con .trim().isEmpty(), si possono ottenere molte chiamate «vuote» o ricostruire il testo in modo errato.

Errore n. 2: tentativo di modificare l'XML con SAX.
SAX è sola lettura! Se devi modificare la struttura — usa DOM.

Errore n. 3: violazione dell'ordine degli eventi in SAX.
Se le variabili non vengono azzerate in endElement, puoi ottenere «perdite» di dati tra gli elementi.

Errore n. 4: usare DOM per file enormi.
Risultato — OutOfMemoryError o prestazioni molto lente.

Errore n. 5: cast errato dei tipi in DOM.
In DOM tutto è un Node, ma per lavorare con attributi ed elementi figli bisogna fare il cast a Element. Un cast errato può portare a una ClassCastException.

Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION