CodeGym /Kursy /JAVA 25 SELF /Praca z DOM i SAX: parsowanie XML

Praca z DOM i SAX: parsowanie XML

JAVA 25 SELF
Poziom 47 , Lekcja 1
Dostępny

1. DOM (Document Object Model)

Czasem plik XML jest zbyt duży, aby załadować go w całości do pamięci. Zdarza się też, że struktura dokumentu jest z góry nieznana lub zbyt złożona. W innych przypadkach trzeba przetworzyć tylko wybrane fragmenty danych, a nie cały plik, albo nawet podczas działania programu zmienić dokument: usunąć węzeł, dodać nowy element lub przebudować część struktury.

W takich sytuacjach sprawdzają się stare i niezawodne narzędzia — DOM i SAX. Pozwalają one pracować bezpośrednio z zawartością XML bez powiązania z uprzednio opisanymi klasami Javy.

Jak działa DOM

DOM to sposób przedstawienia dokumentu XML jako drzewa obiektów w pamięci. Każdy tag staje się węzłem drzewa (Node), a atrybuty, wartości tekstowe, a nawet komentarze — osobnymi obiektami. Po załadowaniu dokumentu masz pełny dostęp do jego struktury: możesz czytać, modyfikować, usuwać i dodawać elementy oraz atrybuty.

Podstawowe klasy DOM w Javie

  • DocumentBuilderFactory — fabryka do tworzenia parsera.
  • DocumentBuilder — parser, który zamienia XML w drzewo.
  • Document — obiekt korzeniowy drzewa.
  • Element — element XML (tag).
  • NodeList — lista węzłów (np. wszystkie <item> wewnątrz <items>).

Przykład: Odczyt pliku XML za pomocą DOM

Załóżmy, że musimy odczytać taki plik XML:

<contacts>
    <person id="1">
        <name>Ivan</name>
        <email>ivan@example.com</email>
    </person>
    <person id="2">
        <name>Mariya</name>
        <email>maria@example.com</email>
    </person>
</contacts>

Kod: odczyt i iteracja po elementach

import javax.xml.parsers.*;
import org.w3c.dom.*;
import java.io.File;

public class DomExample {
    public static void main(String[] args) throws Exception {
        // 1. Tworzymy fabrykę i parser
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        DocumentBuilder builder = factory.newDocumentBuilder();

        // 2. Ładujemy plik XML do pamięci
        Document doc = builder.parse(new File("contacts.xml"));

        // 3. Pobieramy element korzeniowy
        Element root = doc.getDocumentElement();
        System.out.println("Element główny: " + root.getTagName());

        // 4. Pobieramy listę wszystkich <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);
        }
    }
}

Co się tutaj dzieje:

  • Najpierw tworzymy parser i ładujemy plik XML.
  • Pobieramy element korzeniowy (contacts).
  • Wyszukujemy wszystkie elementy <person> i iterujemy po nich.
  • Dla każdego <person> odczytujemy atrybut id oraz elementy <name> i <email>.

Schemat drzewa DOM dla przykładu

contacts
├── person (id="1")
│   ├── name ("Ivan")
│   └── email ("ivan@example.com")
└── person (id="2")
    ├── name ("Mariya")
    └── email ("maria@example.com")

Modyfikowanie XML za pomocą DOM

DOM pozwala nie tylko czytać, ale też zmieniać XML. Na przykład dodajmy nową osobę:

// Tworzymy nowy element <person>
Element newPerson = doc.createElement("person");
newPerson.setAttribute("id", "3");

// <name>
Element name = doc.createElement("name");
name.setTextContent("Sergey");
newPerson.appendChild(name);

// <email>
Element email = doc.createElement("email");
email.setTextContent("sergey@example.com");
newPerson.appendChild(email);

// Dodajemy do korzenia
root.appendChild(newPerson);

// Zapisujemy zmiany do pliku
TransformerFactory tf = TransformerFactory.newInstance();
Transformer transformer = tf.newTransformer();
transformer.transform(new DOMSource(doc), new StreamResult(new File("contacts-updated.xml")));

Główne zalety i wady DOM

  • Zalety: wygodne dla niewielkich plików, łatwo wprowadzać dowolne zmiany, można „poruszać się” po drzewie w dowolnym kierunku.
  • Wady: cały XML jest trzymany w pamięci. Dla dużych plików (setki MB i więcej) — to zły pomysł, pamięć szybko się skończy.

2. SAX (Simple API for XML)

SAX to parser zdarzeniowy. Nie buduje drzewa, tylko czyta XML „z lewej do prawej” i wywołuje obsługę zdarzeń: „zaczął się element”, „treść tekstowa”, „element się zakończył” itd. Piszesz własny handler i reagujesz na potrzebne zdarzenia.

Analogia:
Jeśli DOM — to jak rozłożyć wszystkie strony terminarza na stole, to SAX — to jak czytać terminarz strona po stronie i robić notatki tylko wtedy, gdy trafisz na potrzebną stronę.

Podstawowe klasy SAX w Javie

  • SAXParserFactory, SAXParser — fabryka i parser.
  • DefaultHandler — klasa bazowa do obsługi zdarzeń.
  • Metody obsługi: startElement, characters, endElement, startDocument, endDocument.

Przykład: Odczyt pliku XML za pomocą SAX

Załóżmy, ten sam plik contacts.xml. Chcemy po prostu wypisać imiona i e-mail wszystkich osób.

Kod: parser 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("Nowa osoba, 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("Imię: " + name + ", email: " + email);
            name = "";
            email = "";
        }
        currentElement = "";
    }
}

Teraz wyjaśnijmy prostymi słowami, co się tutaj dzieje. Gdy parser SAX napotyka tag otwierający, na przykład <person>, wywołuje metodę startElement. Tam od razu odczytujemy atrybut id i wypisujemy go na ekran. Gdy wewnątrz pojawia się tekst — na przykład imię lub e‑mail — sterowanie trafia do metody characters, gdzie zapisujemy tekst do zmiennych tymczasowych. A gdy parser dochodzi do tagu zamykającego </person>, wywoływana jest endElement. W tym momencie znamy już imię i e‑mail osoby i możemy je wypisać. Po tym zmienne są czyszczone, aby były gotowe na kolejny kontakt.

Idea jest taka, że SAX nie trzyma całego XML w pamięci, tylko działa jak strumieniowy „czytnik”: przechodzi plik od góry do dołu i reaguje na zdarzenia — początek tagu, tekst, koniec tagu. To szybkie i oszczędne pamięciowo, szczególnie dla dużych plików.

Krótkie porównanie DOM i SAX

DOM SAX
Styl Drzewo (cała struktura w pamięci) Zdarzenia (przetwarzanie „w locie”)
Pamięć Ładuje cały XML Minimalne użycie pamięci
Zmiany Można czytać/modyfikować/dodawać Tylko odczyt (zwykle)
Wyszukiwanie danych Łatwo szukać w dowolnym miejscu Trzeba śledzić, gdzie aktualnie się znajdujesz
Rozmiar pliku Dla małych i średnich plików Dla bardzo dużych plików

3. Kiedy używać DOM, a kiedy SAX

DOM świetnie się sprawdza, gdy:

  • Plik jest mały lub średniej wielkości.
  • Musisz wielokrotnie odwoływać się do różnych części XML.
  • Wymagana jest modyfikacja struktury dokumentu.

SAX — to dobry wybór, gdy:

  • Plik jest ogromny i nie da się go załadować do pamięci.
  • Trzeba szybko „wyciągnąć” tylko część informacji (np. znaleźć wszystkie elementy <item> z określonym atrybutem).
  • Wymagana jest wysoka wydajność i minimalne użycie pamięci.
  • Nie planujesz modyfikować XML, tylko go odczytywać.

Wskazówka:
W realnych projektach często używa się obu podejść: DOM — do „ludzkich” ustawień i niewielkich konfiguracji, SAX — do przetwarzania logów, eksportów, dużych importów.

4. Zadanie praktyczne: mały parser dla aplikacji

W Twojej aplikacji ćwiczeniowej (np. „książka kontaktowa”) pojawiło się zadanie: szybko policzyć liczbę użytkowników z e‑mail na gmail.com.

Rozwiązanie 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("Użytkowników z gmail.com: " + count);

Rozwiązanie 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("Użytkowników z gmail.com: " + count);
    }
}

5. Specyfika i niuanse pracy z DOM i SAX

  • DOM może „zjeść” całą pamięć, jeśli plik XML jest bardzo duży. Jeśli zobaczysz błąd OutOfMemoryError, najprawdopodobniej pora przejść na SAX.
  • SAX wymaga dużej uwagi: trzeba śledzić, w jakim elemencie się znajdujesz, i ostrożnie zbierać potrzebne dane. Czasem trzeba użyć stosu lub dodatkowych zmiennych, aby się nie pogubić w głęboko zagnieżdżonych strukturach.
  • W SAX metoda characters może być wywoływana kilkukrotnie dla tego samego bloku tekstu (zwłaszcza gdy tekst jest długi lub zawiera znaki specjalne). Lepiej akumulować tekst w StringBuilder.
  • DOM świetnie nadaje się do wyszukiwania, nawigacji i modyfikowania struktury, ale nie do przetwarzania strumieniowego.
  • Jeśli nie masz pewności, które podejście wybrać — zacznij od DOM dla prostoty, a jeśli zacznie brakować pamięci — przepisz na SAX.

6. Typowe błędy podczas pracy z DOM i SAX

Błąd nr 1: Nieprawidłowe przetwarzanie spacji i znaków nowej linii w SAX.
Metoda characters może zwracać kawałki tekstu, w tym spacje i nowe linie między elementami. Jeśli nie odfiltrować .trim().isEmpty(), można otrzymać wiele „pustych” wywołań lub niepoprawnie złożyć tekst.

Błąd nr 2: Próba modyfikacji XML za pomocą SAX.
SAX służy tylko do odczytu! Jeśli trzeba zmieniać strukturę — użyj DOM.

Błąd nr 3: Naruszenie kolejności zdarzeń w SAX.
Jeśli zmienne nie są zerowane w endElement, może dojść do „wycieku” danych między elementami.

Błąd nr 4: Użycie DOM dla ogromnych plików.
Skutek — OutOfMemoryError lub bardzo wolne działanie.

Błąd nr 5: Nieprawidłowe rzutowanie typów w DOM.
W DOM wszystko to Node, ale aby pracować z atrybutami i elementami potomnymi, trzeba rzutować na Element. Błąd rzutowania może prowadzić do ClassCastException.

1
Zadanie
JAVA 25 SELF, poziom 47, lekcja 1
Niedostępne
Rozpoznanie Głównego Działu Biblioteki 🏛️
Rozpoznanie Głównego Działu Biblioteki 🏛️
1
Zadanie
JAVA 25 SELF, poziom 47, lekcja 1
Niedostępne
Lista postaci lub użytkowników w systemie 👤
Lista postaci lub użytkowników w systemie 👤
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION