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.
GO TO FULL VERSION