Чтение и запись XML в Java с помощью DOM и Transform API

XML-файлы используются для хранения и передачи структурированных данных. До популяризации JSON XML часто был основным форматом для представления данных. В современных проектах XML встречается реже, но навык работы с ним по-прежнему полезен: например, при интеграции с устаревшими сервисами, конфигурационными файлами или обмене данными с внешними системами.
Важно: под «XML» мы подразумеваем стандартный текстовый формат с элементами, атрибутами и иерархией. DOM — модель документа, которая загружает весь файл в память; SAX и StAX — альтернативы для других сценариев.
Что вам понадобится для работы с XML в Java
Java SE включает Java API for XML Processing (JAXP) — общий набор API для обработки XML. Основные подходы внутри JAXP:
- DOM — Document Object Model: удобная объектная модель (Element, Node, Attr). Загружает весь документ в память, поэтому плохо подходит для очень больших XML.
- SAX — Simple API for XML: событийно-ориентированный парсер. Небольшой объём памяти, но код сложнее — вы обрабатываете события начала/конца элементов и текста.
- StAX — Streaming API for XML: потоковый, «pull»-парсер. Компромисс между простотой и экономией памяти — удобнее, чем SAX, и эффективнее, чем DOM для больших потоков.
Для работы с DOM и трансформацией вам понадобятся стандартные пакеты (пример импорта приведён в коде ниже):
import javax.xml.parsers.*;
import javax.xml.transform.*;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.*;Подготовка примерного XML-файла
Для демонстрации возьмём фрагмент типичного файла каталога книг (пример из Microsoft). Ключевые элементы: корневой элемент
Пример (фрагмент):
Gambardella, Matthew
XML Developer's Guide
Computer
44.95
2000-10-01
An in-depth look at creating applications
with XML.
Ralls, Kim
... Формат даты в примере — ISO-подобный yyyy-MM-dd.
Чтение XML через DOM API — базовые шаги
Общий подход при DOM: создать фабрику DocumentBuilderFactory, затем DocumentBuilder и распарсить файл в объект Document — корневой узел будет доступен через getDocumentElement().
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
// XML-файл для чтения
File file = new File("");
Document document = builder.parse(file);
Element catalog = document.getDocumentElement(); После этого весь XML доступен в памяти, и вы можете навигировать по дереву через методы DOM.
Извлечение данных через DOM API
Ниже — типичная операция: получить список дочерних элементов
NodeList books = catalog.getChildNodes();
for (int i = 0, ii = 0, n = books.getLength(); i < n; i++) {
Node child = books.item(i);
if (child.getNodeType() != Node.ELEMENT_NODE)
continue;
Element book = (Element) child;
// работа с элементом book
}Чтобы упростить поиск вложенных элементов по имени, удобно добавить утилитные методы:
static private Node findFirstNamedElement(Node parent, String tagName) {
NodeList children = parent.getChildNodes();
for (int i = 0, in = children.getLength(); i < in; i++) {
Node child = children.item(i);
if (child.getNodeType() != Node.ELEMENT_NODE)
continue;
if (child.getNodeName().equals(tagName))
return child;
}
return null;
}И ещё один удобный метод: получение текстового содержимого элемента. В DOM текст может быть представлен несколькими соседними TEXT_NODE, поэтому нужно собрать их.
static private String getCharacterData(Node parent) {
StringBuilder text = new StringBuilder();
if (parent == null)
return text.toString();
NodeList children = parent.getChildNodes();
for (int k = 0, kn = children.getLength(); k < kn; k++) {
Node child = children.item(k);
if (child.getNodeType() != Node.TEXT_NODE)
break;
text.append(child.getNodeValue());
}
return text.toString();
}С этими вспомогательными функциями код для вывода информации по каждому
NodeList books = catalog.getChildNodes();
for (int i = 0, ii = 0, n = books.getLength(); i < n; i++) {
Node child = books.item(i);
if (child.getNodeType() != Node.ELEMENT_NODE)
continue;
Element book = (Element) child;
ii++;
String id = book.getAttribute("id");
String author = getCharacterData(findFirstNamedElement(child, "author"));
String title = getCharacterData(findFirstNamedElement(child, "title"));
String genre = getCharacterData(findFirstNamedElement(child, "genre"));
String price = getCharacterData(findFirstNamedElement(child, "price"));
String pubdate = getCharacterData(findFirstNamedElement(child, "pubdate"));
String descr = getCharacterData(findFirstNamedElement(child, "description"));
System.out.printf("%3d. book id = %s\n"
+ " author: %s\n"
+ " title: %s\n"
+ " genre: %s\n"
+ " price: %s\n"
+ " pubdate: %s\n"
+ " descr: %s\n",
ii, id, author, title, genre, price, pubdate, descr);
}Пояснения к коду:
- Итерация по дочерним узлам корневого элемента catalog.
- Пропускаются узлы, которые не являются элементами (TEXT_NODE, комментарии и т. п.).
- Приведение (Element) child даёт доступ к методам Element.
- Для получения текстовых данных используется getCharacterData.
Пример вывода показан на рисунке ниже:
Запись XML с помощью Transform API
Java предоставляет API преобразований XML (Transformer API). Частый сценарий — использовать «identity»-трансформ (то есть без XSLT), чтобы записать объект Document обратно в поток или файл. Разберём пример: добавление нового элемента
Исходные данные о новой книге можно хранить в properties-файле или получить из базы. Пример properties (фрагмент):
id=bk113
author=Jane Austen
title=Pride and Prejudice
genre=Romance
price=6.99
publish_date=2010-04-01
description=It is a truth universally acknowledged, ...Сначала парсим существующий XML (как в разделе выше):
File file = new File("..."); // XML-файл
Document document = builder.parse(file);
Element catalog = document.getDocumentElement();Затем загружаем properties:
String propsFile = "";
Properties props = new Properties();
try (FileReader in = new FileReader(propsFile)) {
props.load(in);
} Достаём значения:
String id = props.getProperty("id");
String author = props.getProperty("author");
String title = props.getProperty("title");
String genre = props.getProperty("genre");
String price = props.getProperty("price");
String publish_date = props.getProperty("publish_date");
String descr = props.getProperty("description");Создаём элемент
Element book = document.createElement("book");
book.setAttribute("id", id);Добавление дочерних элементов простое; для удобства можно пройти по списку имён:
List elnames = Arrays.asList("author", "title", "genre", "price",
"publish_date", "description");
for (String elname : elnames) {
Element el = document.createElement(elname);
Text text = document.createTextNode(props.getProperty(elname));
el.appendChild(text);
book.appendChild(el);
}
catalog.appendChild(book); Осталось записать изменённый документ наружу. Создаём Transformer и настраиваем отступы:
TransformerFactory tfact = TransformerFactory.newInstance();
Transformer tform = tfact.newTransformer();
tform.setOutputProperty(OutputKeys.INDENT, "yes");
tform.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "3");
// вывод в консоль
tform.transform(new DOMSource(document), new StreamResult(System.out));
// или запись в файл
// tform.transform(new DOMSource(document), new StreamResult(new File("output.xml")));Готово — вы прочитали XML, модифицировали DOM и записали результат.
Когда DOM подходит, а когда нет — сравнение DOM / SAX / StAX
Краткая матрица выбора:
| Критерий | DOM | SAX | StAX |
|---|---|---|---|
| Память | Загружает весь документ (высокая) | Низкая | Низкая / средняя |
| Простота чтения | Очень удобно (объектная модель) | Сложнее (обработчики событий) | Удобнее, чем SAX (pull API) |
| Редактирование дерева | Прямо в памяти | Нет (только обработка) | Частично (построчно/поточно) |
| Подходит для | Небольших/средних документов, когда нужно менять структуру | Больших потоков, парсинг без изменений | Потоковая обработка со средней сложностью |
Decision flow (поможет выбрать подход):
flowchart TD
A[Нужен доступ ко всему дереву?] -->|Да| B[Используйте DOM]
A -->|Нет| C[Нажатие на объём данных]
C -->|Большой| D[Используйте SAX или StAX]
C -->|Средний| E[StAX — баланс удобства и памяти]
D --> F[Если нужен event-driven — SAX, если prefer pull — StAX]Важно: если планируется модификация структуры (добавление/удаление узлов), DOM часто удобнее, но требует памяти. Для сквозной обработки больших файлов — используйте SAX/StAX.
Альтернативные подходы и интеграции
- Использовать сторонние библиотеки (например, JAXB для биндинга XML в Java-объекты) — удобно, когда у вас фиксированная схема и много кода по маппингу. JAXB генерирует классы из XSD и упрощает серилизацию/десериализацию.
- Если требуется многократно выполнять сложные преобразования, стоит рассмотреть XSLT или XQuery.
- Для больших данных рассмотреть парсеры на основе pull-методов (StAX) или стриминговую обработку со специализированными библиотеками.
Примеры не подходят, если вам нужен строго валидированный ввод по сложной XSD-схеме — в этом случае добавьте валидацию при разборе (DocumentBuilderFactory.setSchema(…)).
Практические советы и эвристики
- Ментальная модель DOM: представьте XML как дерево объектов — каждый элемент это узел с детьми и атрибутами. Операции чтения/изменения — обычные манипуляции с деревом.
- При чтении используйте фильтрацию узлов по типу: ELEMENT_NODE — элементы, TEXT_NODE — текст.
- Всегда закрывайте потоки и используйте try-with-resources для input/output.
- Для точности парсинга дат и чисел используйте явную валидацию/парсинг (например, BigDecimal для цен).
Чек-лист ролей
Разработчик:
- Выбрал подходящий API (DOM / SAX / StAX / JAXB)
- Обработаны все сущности XML с фильтрацией текстовых узлов
- Испытаны граничные случаи (пустые теги, отсутствующие атрибуты)
- Добавлены юнит-тесты для парсера
Сопровождающий / DevOps:
- Проверить, что большие файлы не приводят к OOM
- Настроить мониторинг на время обработки и использование памяти
- Обеспечить резервные копии исходных XML перед модификацией
Тестировщик:
- Тестовые файлы с некорректной структурой (сломанный XML)
- Тестовые файлы с отсутствующими полями
- Тесты на сохранение/восстановление данных после трансформации
Чек-лист приёмки
Критерии приёмки:
- Программа корректно читает валидный XML и извлекает требуемые поля.
- Программа корректно добавляет новый элемент и сохраняет файл в формате XML.
- Изменённый файл соответствует базовым требованиям к форматированию (отступы читаемы) и остаётся валидным XML.
- Приложение устойчиво к пустым/неожидаемым элементам и не падает с NPE.
Маленькая шпаргалка (cheat sheet)
- Импорты: javax.xml.parsers., javax.xml.transform., org.w3c.dom.*
- Создать парсер: DocumentBuilderFactory.newInstance() -> newDocumentBuilder()
- Прочитать: builder.parse(new File(“file.xml”))
- Создать элемент: document.createElement(“name”)
- Добавить текст: document.createTextNode(“text”)
- Трансформер: TransformerFactory.newInstance().newTransformer()
- Включить отступы: setOutputProperty(OutputKeys.INDENT, “yes”)
- Записать: tform.transform(new DOMSource(document), new StreamResult(new File(“out.xml”)))
1-строчная глоссарий
- DOM — объектная модель документа (Document Object Model).
- SAX — событийный парсер XML.
- StAX — потоковый парсер с pull-интерфейсом.
- Transformer — API для преобразования/записи XML.
Частые ошибки и как их избежать
- Ошибка: чтение через getChildNodes() и прямое приведение без фильтра по типу — приводит к ClassCastException. Решение: проверять node.getNodeType() == Node.ELEMENT_NODE.
- Ошибка: ожидание, что текст всегда один TEXT_NODE. Решение: собрать все последовательные TEXT_NODE как в getCharacterData.
- Ошибка: использование DOM для многогигабайтных файлов — OOM. Решение: использовать StAX или SAX.
Пример сценариев тестирования
- Тест 1: Чтение каталога из маленького файла — ожидается корректный список книг.
- Тест 2: Добавление книги и сохранение — новый элемент должен присутствовать в output.xml.
- Тест 3: Некорректный XML (ломаная разметка) — парсер должен бросить исключение и не потерять исходный файл.
Итог
Работа с XML в Java — это набор простых приёмов: выбрать подходящий API, корректно парсить узлы и текст, не забывать о проверках и тестах. DOM удобен для редактирования и небольших документов; SAX и StAX — для потоковой обработки и больших объёмов.
Важно: выбирайте инструмент под задачу, добавляйте проверку границ и юнит-тесты, и тогда интеграция с XML будет надёжной и поддерживаемой.
Примечание: при необходимости автоматического маппинга между XML и Java-классами рассмотрите JAXB как альтернативу ручному обходу дерева.
Похожие материалы
Готовьте пасту под плейлист Spotify
Визуальные рецепты: GIF и короткие видео
Как пользоваться голосовыми покупками Alexa
Лучшие приложения для обучения кулинарии на iPhone
Как заморозить строки и столбцы в Google Sheets