Java XML DOM Parser
Impara a analizzare, navigare, modificare e serializzare XML in Java con il DOM parser integrato, con un esempio completo di lettura, modifica e scrittura.
Il parser DOM (Document Object Model) legge un intero documento XML in memoria e fornisce un albero di nodi che puoi navigare, interrogare e modificare. È incluso nel JDK nei pacchetti javax.xml.parsers e org.w3c.dom, quindi non è necessario aggiungere nulla al classpath. DOM è lo strumento giusto quando i documenti sono abbastanza piccoli da essere tenuti in memoria e hai bisogno di accesso casuale a qualsiasi parte dell'albero — per leggere un file di configurazione, trasformare un payload o costruire XML in modo programmatico.
Questo capitolo illustra l'intero ciclo di vita: come DOM modella un documento, come analizzare una sorgente in un albero, come leggere e modificare i nodi e come riscrivere l'albero in formato XML. Se sei nuovo all'XML in Java, inizia dall'introduzione all'XML; per documenti di grandi dimensioni in cui la memoria è un fattore critico, confronta DOM con il parser SAX in streaming.
Come DOM modella un documento
DOM trasforma il markup in un albero di oggetti Node. Ogni elemento, attributo, testo e commento è un nodo, e l'intero documento è appeso a un unico nodo radice Document. Si legge l'albero chiedendo ai nodi i loro figli, e lo si modifica creando, spostando o rimuovendo nodi.
| Concetto | Interfaccia | Cosa rappresenta |
|---|---|---|
| Document | Document | L'intero file analizzato; punto di ingresso dell'albero |
| Element | Element | Un tag come <book>, con attributi e figli |
| Attribute | Attr | Una coppia nome/valore su un elemento |
| Text | Text | Dati carattere all'interno di un elemento |
| Node list | NodeList | Una raccolta ordinata di nodi accessibile tramite indice |
Il compromesso fondamentale: DOM è comodo perché l'intero albero è indirizzabile, ma carica tutto in memoria in una sola volta. Per feed di diversi gigabyte conviene usare SAX o StAX, che elaborano il documento in streaming senza costruire un albero. E se vuoi mappare XML da e verso oggetti Java invece di navigare nodi grezzi, JAXB di solito richiede meno codice rispetto a DOM scritto a mano.
Analizzare un documento
Non si costruisce mai un parser direttamente. Si richiede un DocumentBuilder a una DocumentBuilderFactory, poi si chiama parse su uno stream, un file o un URI. Configura la factory prima di costruire — la consapevolezza dei namespace e la validazione sono impostazioni a livello di factory.
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true);
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(new File("library.xml"));
// Collapse adjacent text nodes and drop empty ones so getTextContent is clean.
doc.getDocumentElement().normalize();parse lancia SAXException per XML malformato e IOException se la sorgente non può essere letta, quindi entrambe sono eccezioni checked che devi gestire. Chiamare normalize() una volta dopo l'analisi unisce i nodi di testo separati — una fonte comune di sorprese quando si legge il testo degli elementi.
Navigare l'albero
Due metodi coprono la maggior parte delle letture: getElementsByTagName trova tutti i discendenti con un dato tag, e getChildNodes restituisce i figli diretti di un nodo. Ricorda che getChildNodes include i nodi di testo contenenti spazi bianchi, quindi filtra per tipo di nodo quando vuoi solo gli elementi.
Element root = doc.getDocumentElement(); // <library>
NodeList books = doc.getElementsByTagName("book"); // every <book> in the tree
for (int i = 0; i < books.getLength(); i++) {
Element book = (Element) books.item(i);
String id = book.getAttribute("id"); // attribute by name
String title = book.getElementsByTagName("title")
.item(0).getTextContent(); // first child <title> text
System.out.println(id + " -> " + title);
}NodeList è basata su indice, non è iterabile, quindi si cicla con getLength() e item(i). getAttribute restituisce una stringa vuota (mai null) quando l'attributo è assente, il che vale la pena sapere prima di scrivere un controllo null che non scatterà mai.
Modificare e creare nodi
L'albero DOM è mutabile. Si modifica il testo con setTextContent, si modificano gli attributi con setAttribute e si amplia l'albero creando nodi tramite i metodi factory di Document e aggiungendoli con appendChild. I nodi devono essere creati dallo stesso documento in cui vengono inseriti.
// Update existing content.
Element price = (Element) book.getElementsByTagName("price").item(0);
price.setTextContent("49.50");
price.setAttribute("currency", "USD");
// Build a new subtree and attach it.
Element added = doc.createElement("book");
added.setAttribute("id", "b3");
Element title = doc.createElement("title");
title.setTextContent("The Pragmatic Programmer");
added.appendChild(title);
doc.getDocumentElement().appendChild(added);createElement crea un nodo scollegato; nulla appare nel documento finché non lo si aggiunge da qualche parte con appendChild. Per rimuovere un nodo, si chiama parent.removeChild(child).
Riscrivere l'albero in XML
DOM non ha un metodo toString() che produca XML. Per serializzare, si passa il documento a un Transformer con una DOMSource e un StreamResult. Lo stesso pacchetto javax.xml.transform permette di scrivere su un file, una stringa o qualsiasi stream, e di impostare opzioni di formattazione.
Transformer tr = TransformerFactory.newInstance().newTransformer();
tr.setOutputProperty(OutputKeys.INDENT, "yes");
tr.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
tr.transform(new DOMSource(doc), new StreamResult(new File("out.xml")));Per input non affidabile, rafforza la factory prima di analizzare — disabilita le dichiarazioni DOCTYPE con factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true) per bloccare gli attacchi XXE (XML External Entity).
Un esempio completo
Questo programma analizza un documento library in memoria, legge ogni libro, aumenta ogni prezzo del 10%, inserisce un nuovo libro e serializza la prima riga del prezzo aggiornato in XML — esercitando l'intero ciclo di lettura-modifica-scrittura su un singolo albero.
Cosa ricavare dall'esecuzione:
- L'elemento radice viene stampato come
libraryperchégetDocumentElement()restituisce il singolo nodo superiore da cui tutto il resto dipende. getElementsByTagName("book")riporta un conteggio di 2 prima dell'inserimento, confermando che ha raccolto entrambi i discendenti<book>della radice.- I prezzi vengono letti con
getTextContent()e analizzati conDouble.parseDouble, quindi45.00e38.50sommano al totale stampato di83.50. - Dopo
appendChild, la stessa querygetElementsByTagName("book")restituisce 3, mostrando che l'albero live ha recepito il nodo creato condoc.createElement. - La prima riga del prezzo serializzato riporta
49.50, dimostrando chesetTextContentha mutato il nodo in memoria e ilTransformerha scritto il valore aggiornato (45.00 aumentato del 10%) in XML.