Java XML DOM Parser
Aprenda a analisar, navegar, modificar e serializar XML em Java com o DOM parser integrado, com exemplo completo de leitura e escrita.
O parser DOM (Document Object Model) lê um documento XML inteiro na memória e fornece uma árvore de nós que você pode navegar, consultar e modificar. Ele é fornecido com o JDK em javax.xml.parsers e org.w3c.dom, portanto não é necessário adicionar nada ao classpath. O DOM é a ferramenta certa quando os documentos são pequenos o suficiente para caber na memória e você precisa de acesso aleatório a qualquer parte da árvore — lendo um arquivo de configuração, transformando um payload ou construindo XML programaticamente.
Este capítulo percorre o ciclo de vida completo: como o DOM modela um documento, como analisar uma fonte em uma árvore, como ler e modificar nós e como serializar a árvore de volta como XML. Se você é novo em XML no Java, comece com a introdução ao XML; para documentos grandes onde a memória importa, compare o DOM com o parser SAX de streaming.
Como o DOM modela um documento
O DOM transforma a marcação em uma árvore de objetos Node. Cada elemento, atributo, trecho de texto e comentário é um nó, e o documento inteiro está pendurado em uma única raiz Document. Você lê a árvore pedindo a nós os seus filhos, e a altera criando, movendo ou removendo nós.
| Conceito | Interface | O que representa |
|---|---|---|
| Document | Document | O arquivo analisado completo; ponto de entrada da árvore |
| Element | Element | Uma tag como <book>, com atributos e filhos |
| Attribute | Attr | Um par nome/valor em um elemento |
| Text | Text | Dados de caracteres dentro de um elemento |
| Node list | NodeList | Uma coleção ordenada e acessível por índice de nós |
O principal compromisso: o DOM é conveniente porque toda a árvore é acessível, mas carrega tudo na memória de uma só vez. Para feeds de vários gigabytes, você usaria SAX ou StAX, que transmitem o documento sem construir uma árvore. E se você estiver mapeando XML de e para objetos Java em vez de percorrer nós brutos, o JAXB geralmente requer menos código do que DOM escrito à mão.
Analisando um documento
Você nunca constrói um parser diretamente. Você pede a um DocumentBuilderFactory um DocumentBuilder, depois chama parse em um stream, arquivo ou URI. Configure a factory antes de construir — a consciência de namespace e a validação são configurações em nível de 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 lança SAXException para XML malformado e IOException se a fonte não puder ser lida, portanto ambas são exceções verificadas que você deve tratar. Chamar normalize() uma vez após a análise mescla nós de texto divididos — uma fonte comum de surpresas ao ler texto de elementos.
Navegando na árvore
Dois métodos cobrem a maior parte da leitura: getElementsByTagName encontra todos os descendentes com uma determinada tag, e getChildNodes retorna os filhos diretos de um nó. Lembre-se de que getChildNodes inclui nós de texto de espaço em branco, então filtre pelo tipo de nó quando quiser apenas elementos.
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 é baseado em índice, não iterável, então você percorre com getLength() e item(i). getAttribute retorna uma string vazia (nunca null) quando o atributo está ausente, o que vale saber antes de escrever uma verificação de null que nunca dispara.
Modificando e criando nós
A árvore DOM é mutável. Você altera texto com setTextContent, altera atributos com setAttribute e aumenta a árvore criando nós através dos métodos factory do Document e os anexando. Os nós devem ser criados pelo mesmo documento em que são inseridos.
// 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 cria um nó desanexado; nada aparece no documento até que você o anexe com appendChild em algum lugar. Para remover um nó, chame parent.removeChild(child).
Serializando a árvore de volta
O DOM não tem toString() que produz XML. Para serializar, entregue o documento a um Transformer com um DOMSource e um StreamResult. O mesmo pacote javax.xml.transform permite escrever em um arquivo, uma string ou qualquer stream, e definir opções de formatação.
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")));Para entrada não confiável, proteja a factory antes de analisar — desative declarações DOCTYPE com factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true) para bloquear ataques XXE (XML External Entity).
Um exemplo completo
Este programa analisa um documento de biblioteca em memória, lê cada livro, aumenta todos os preços em 10%, insere um novo livro e serializa a primeira linha de preço atualizada de volta para XML — exercitando o ciclo completo de leitura-modificação-escrita em uma única árvore.
O que extrair da execução:
- O elemento raiz é impresso como
libraryporquegetDocumentElement()retorna o único nó do topo ao qual todo o resto está ligado. getElementsByTagName("book")relata uma contagem de 2 antes da inserção, confirmando que coletou ambos os descendentes<book>da raiz.- Os preços são lidos com
getTextContent()e analisados comDouble.parseDouble, então45.00e38.50somam o total impresso de83.50. - Após
appendChild, a mesma consultagetElementsByTagName("book")retorna 3, mostrando que a árvore em tempo real capturou o nó criado comdoc.createElement. - A primeira linha de preço serializada lê
49.50, provando quesetTextContentmutou o nó em memória e oTransformerescreveu o valor atualizado (45.00 aumentado em 10%) de volta para XML.