1. DOM (Document Object Model)
Іноді XML-файл надто великий, щоб завантажити його цілком у пам’ять. Буває й так, що структура документа заздалегідь невідома або занадто складна. В інших випадках потрібно обробити лише окремі частини даних, а не весь файл, або ж безпосередньо під час роботи програми змінити документ: видалити вузол, додати новий елемент чи перебудувати частину структури.
У таких ситуаціях доречні старі й перевірені інструменти — DOM і SAX. Вони дають змогу працювати безпосередньо з вмістом XML без прив’язки до заздалегідь описаних Java-класів.
Як працює DOM
DOM — це спосіб подати XML-документ у вигляді дерева об’єктів у пам’яті. Кожен тег стає вузлом дерева (Node), а атрибути, текстові значення та навіть коментарі — окремими об’єктами. Після завантаження документа ви отримуєте повний доступ до його структури: можна читати, змінювати, видаляти, додавати елементи та атрибути.
Основні класи DOM у Java
- DocumentBuilderFactory — фабрика для створення парсера.
- DocumentBuilder — парсер, що перетворює XML на дерево.
- Document — кореневий об’єкт дерева.
- Element — елемент XML (тег).
- NodeList — список вузлів (наприклад, усі <item> всередині <items>).
Приклад: читання XML-файлу за допомогою DOM
Припустімо, нам потрібно прочитати такий XML-файл:
<contacts>
<person id="1">
<name>Іван</name>
<email>ivan@example.com</email>
</person>
<person id="2">
<name>Марія</name>
<email>maria@example.com</email>
</person>
</contacts>
Код: читання й обхід елементів
import javax.xml.parsers.*;
import org.w3c.dom.*;
import java.io.File;
public class DomExample {
public static void main(String[] args) throws Exception {
// 1. Створюємо фабрику та парсер
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
// 2. Завантажуємо XML-файл у пам’ять
Document doc = builder.parse(new File("contacts.xml"));
// 3. Отримуємо кореневий елемент
Element root = doc.getDocumentElement();
System.out.println("Кореневий елемент: " + root.getTagName());
// 4. Отримуємо список усіх <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);
}
}
}
Що тут відбувається:
- Спочатку створюємо парсер і завантажуємо XML-файл.
- Отримуємо кореневий елемент (contacts).
- Знаходимо всі елементи <person>, перебираємо їх.
- Для кожного <person> читаємо атрибут id, елементи <name> і <email>.
Схема DOM-дерева для прикладу
contacts
├── person (id="1")
│ ├── name ("Іван")
│ └── email ("ivan@example.com")
└── person (id="2")
├── name ("Марія")
└── email ("maria@example.com")
Зміна XML за допомогою DOM
DOM дає змогу не лише читати, а й змінювати XML. Наприклад, додамо нову людину:
// Створюємо новий елемент <person>
Element newPerson = doc.createElement("person");
newPerson.setAttribute("id", "3");
// <name>
Element name = doc.createElement("name");
name.setTextContent("Сергій");
newPerson.appendChild(name);
// <email>
Element email = doc.createElement("email");
email.setTextContent("sergey@example.com");
newPerson.appendChild(email);
// Додаємо до кореневого елемента
root.appendChild(newPerson);
// Зберігаємо зміни у файл
TransformerFactory tf = TransformerFactory.newInstance();
Transformer transformer = tf.newTransformer();
transformer.transform(new DOMSource(doc), new StreamResult(new File("contacts-updated.xml")));
Основні переваги та недоліки DOM
- Переваги: зручно для невеликих файлів, легко робити будь-які зміни, можна «гуляти» деревом у будь-якому напрямку.
- Недоліки: увесь XML зберігається в пам’яті. Для великих файлів (сотні МБ і більше) — не підходить: пам’ять швидко закінчиться.
2. SAX (Simple API for XML)
SAX — це подієвий парсер. Він не будує дерево, а просто читає XML «зліва праворуч» і викликає обробники подій: «почався елемент», «текстовий вміст», «закінчився елемент» тощо. Ви пишете власний обробник і реагуєте на потрібні вам події.
Аналогія:
Якщо DOM — це як розкласти всі сторінки органайзера на столі, то SAX — це наче читати органайзер посторінково й робити нотатки лише тоді, коли зустрінете потрібну сторінку.
Основні класи SAX у Java
- SAXParserFactory, SAXParser — фабрика й парсер.
- DefaultHandler — базовий клас для обробки подій.
- Методи-обробники: startElement, characters, endElement, startDocument, endDocument.
Приклад: читання XML-файлу за допомогою SAX
Припустімо, той самий файл contacts.xml. Ми хочемо просто вивести імена та email усіх людей.
Код: 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("Нова людина, 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("Ім’я: " + name + ", email: " + email);
name = "";
email = "";
}
currentElement = "";
}
}
Тепер розберімося, що тут відбувається простими словами. Коли SAX-парсер зустрічає відкривальний тег, наприклад <person>, він викликає метод startElement. Там ми одразу читаємо атрибут id і виводимо його на екран. Коли всередині трапляється текст — наприклад, ім’я або email, — керування потрапляє до методу characters, де ми зберігаємо цей текст у тимчасові змінні. А коли парсер доходить до закривального тега </person>, викликається endElement. У цей момент ми вже знаємо ім’я та email людини й можемо їх вивести. Після цього змінні очищаються, щоб бути готовими до наступного контакту.
Ідея в тому, що SAX не зберігає весь XML цілком у пам’яті, а працює як потоковий «читач»: проходить файл згори донизу й реагує на події — початок тега, текст, кінець тега. Це швидко та ощадливо щодо пам’яті, особливо для великих файлів.
Коротке порівняння DOM і SAX
| DOM | SAX | |
|---|---|---|
| Стиль | Дерево (вся структура в пам’яті) | Події (обробка «на льоту») |
| Пам’ять | Завантажує весь XML | Мінімальне використання пам’яті |
| Зміни | Можна читати/змінювати/додавати | Лише читання (зазвичай) |
| Пошук даних | Легко шукати будь-де | Потрібно відстежувати «де ви зараз» |
| Розмір файлу | Для малих і середніх файлів | Для дуже великих файлів |
3. Коли використовувати DOM, а коли SAX
DOM чудово підходить, якщо:
- Файл невеликий або середній за розміром.
- Потрібно багаторазово звертатися до різних частин XML.
- Потрібно змінювати структуру документа.
SAX — ваш вибір, якщо:
- Файл величезний, і завантажити його в пам’ять неможливо.
- Потрібно швидко «дістати» лише частину інформації (наприклад, знайти всі елементи <item> із певним атрибутом).
- Потрібна висока продуктивність і мінімальне використання пам’яті.
- Ви не плануєте змінювати XML, лише читати.
Порада:
У реальних проєктах часто використовують обидва підходи: DOM — для «людських» налаштувань і невеликих конфігів, SAX — для обробки логів, вивантажень, великих імпортів.
4. Практичне завдання: невеликий парсер для застосунку
У вашому навчальному застосунку (наприклад, «контактна книга») припустімо, з’явилося завдання: швидко підрахувати кількість користувачів з email на gmail.com.
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("Користувачів з gmail.com: " + count);
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("Користувачів з gmail.com: " + count);
}
}
5. Особливості та нюанси роботи з DOM і SAX
- DOM може «з’їсти» всю пам’ять, якщо XML-файл дуже великий. Якщо ви раптом побачите помилку OutOfMemoryError, найімовірніше, час переходити на SAX.
- SAX вимагає уважності: потрібно відстежувати, в якому елементі ви перебуваєте, і акуратно збирати потрібні дані. Іноді доводиться використовувати стек або додаткові змінні, щоб не заплутатися в глибоко вкладених структурах.
- У SAX метод characters може викликатися кілька разів для одного й того самого текстового блоку (особливо якщо текст довгий або містить спецсимволи). Краще накопичувати текст у StringBuilder.
- DOM чудово підходить для пошуку, навігації та зміни структури, але не для потокової обробки.
- Якщо ви не впевнені, який підхід обрати — почніть із DOM задля простоти, а якщо стане «важко» щодо пам’яті — перепишіть на SAX.
6. Типові помилки під час роботи з DOM і SAX
Помилка № 1: Неправильна обробка пробілів і перенесень рядків у SAX.
Метод characters може повертати шматочки тексту, у тому числі пробіли й перенесення рядків між елементами. Якщо не фільтрувати .trim().isEmpty(), можна отримати багато «порожніх» викликів або некоректно зібрати текст.
Помилка № 2: Спроба змінити XML за допомогою SAX.
SAX — це лише для читання! Якщо потрібно змінювати структуру — використовуйте DOM.
Помилка № 3: Порушення порядку подій у SAX.
Якщо змінні не очищаються в endElement, можна отримати «витік» даних між елементами.
Помилка № 4: Використання DOM для величезних файлів.
Результат — OutOfMemoryError або дуже повільна робота.
Помилка № 5: Неправильне перетворення типів у DOM.
У DOM усе — це Node, але для роботи з атрибутами й дочірніми елементами потрібно перетворювати до типу Element. Помилка під час перетворення може призвести до ClassCastException.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ