W3docs

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.

ConcettoInterfacciaCosa rappresenta
DocumentDocumentL'intero file analizzato; punto di ingresso dell'albero
ElementElementUn tag come <book>, con attributi e figli
AttributeAttrUna coppia nome/valore su un elemento
TextTextDati carattere all'interno di un elemento
Node listNodeListUna 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.

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.

java— editable, runs on the server

Cosa ricavare dall'esecuzione:

  • L'elemento radice viene stampato come library perché 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 con Double.parseDouble, quindi 45.00 e 38.50 sommano al totale stampato di 83.50.
  • Dopo appendChild, la stessa query getElementsByTagName("book") restituisce 3, mostrando che l'albero live ha recepito il nodo creato con doc.createElement.
  • La prima riga del prezzo serializzato riporta 49.50, dimostrando che setTextContent ha mutato il nodo in memoria e il Transformer ha scritto il valore aggiornato (45.00 aumentato del 10%) in XML.

Esercitazione

Pratica
Nell'API DOM, perché devi chiamare doc.createElement() prima di appendChild() per aggiungere un nuovo nodo?
Nell'API DOM, perché devi chiamare doc.createElement() prima di appendChild() per aggiungere un nuovo nodo?
Was this page helpful?