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.
GO TO FULL VERSION