JavaRush /Курси /JAVA 25 SELF /Робота з DOM, SAX: розбір XML

Робота з DOM, SAX: розбір XML

JAVA 25 SELF
Рівень 47 , Лекція 1
Відкрита

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.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ