Java týden Body zdarma
Využij podzimních slev a získej od nás až 40 % bodů zdarma! Více zde
Pouze tento týden sleva až 80 % na Java e-learning!

Lekce 8 - Čtení XML souborů SAXem v Javě

Unicorn College Tento obsah je dostupný zdarma v rámci projektu IT lidem.
Vydávání, hosting a aktualizace umožňují jeho sponzoři.

V minulé lekci, Zápis XML souborů SAXem v Javě, jsme si představili formát XML a ukázali si, jak pomocí SAXu vytvořit jednoduché XML. Nyní na minulý díl navážeme a napíšeme si proces opačný, tedy načtení XML souboru s uživateli a sestavení příslušné objektové struktury (listu uživatelů).

Pro úplnost si opět uvedeme náš XML soubor soubor.xml:

<?xml version="1.0" ?>
<uzivatele>
  <uzivatel vek="22">
    <jmeno>Pavel Slavík</jmeno>
    <registrovan>21.březen 2000</registrovan>
  </uzivatel>
  <uzivatel vek="31">
    <jmeno>Jan Novák</jmeno>
    <registrovan>30.říjen 2012</registrovan>
  </uzivatel>
  <uzivatel vek="16">
    <jmeno>Tomáš Marný</jmeno>
    <registrovan>1.leden 2011</registrovan>
  </uzivatel>
</uzivatele>

A naši třídu Uzivatel.java:

public class Uzivatel {
    private String jmeno;
    private int vek;
    private LocalDate registrovan;
    public static DateTimeFormatter formatData = DateTimeFormatter.ofPattern("d'.'LLLL yyyy");

    public Uzivatel(String jmeno, int vek, LocalDate registrovan){
        this.jmeno = jmeno;
        this.vek = vek;
        this.registrovan = registrovan;
    }

    @Override
    public String toString() {
        return String.format("%s, %d, %s", jmeno, vek, formatData.format(registrovan));
    }

    public String getJmeno() {
        return jmeno;
    }

    public int getVek() {
        return vek;
    }

    public LocalDate getRegistrovan() {
        return registrovan;
    }
}

Založme si nový projekt, půjde opět o konzolovou aplikaci. Pojmenujeme ji XmlSaxCteni a do složky s projektem nakopírujeme náš XML soubor. Vytvořenou třídu oddědíme od třídy org.xml.sax.helpers.DefaultHandler. Tím se nám zpřístupní metody, které později budeme potřebovat při parsování souboru. K projektu připojíme také třídu Uzivatel. Uživatele budeme chtít načíst do nějaké kolekce, vytvořme si tedy prázdný ArrayList uzivatele.

private List<Uzivatel> uzivatele = new ArrayList<>();

Konstanty

Než se přesuneme k samotnému čtení, vytvoříme si pomocnou třídu, ve které si uložíme konstanty s názvy jednotlivých elementů v XML souboru:

public final class Konstanty {

    public static final String UZIVATELE = "uzivatele";

    public static final String UZIVATEL = "uzivatel";

    public static final String VEK = "vek";
    public static final String JMENO = "jmeno";
    public static final String REGISTROVAN = "registrovan";

}

Čtení XML přes SAX

Tento výukový obsah pomáhají rozvíjet následující firmy, které dost možná hledají právě tebe!

V hlavní třídě si založíme privátní metodu parsuj(String soubor), která bude jako parametr přijímat cestu k XML souboru:

private void parsuj(String soubor) throws SAXException, IOException, ParserConfigurationException {
    // TODO zde vyplníme tělo
}

V těle této metody "odstartujeme" samotné parsování. Ke čtení XML přes SAX nám Java poskytuje abstraktní třídu SAXParser. Instanci této třídy získáme pomocí továrny, kterou poskytuje třída SAXParserFactory.newInstance().newSAXParser(). Nad instancí parseru jednoduše zavoláme metodu parse(), které předáme jako parametry soubor, který chceme naparsovat a handler, který se o parsování postará. Tělo metody tedy bude vypadat následovně:

private void parsuj(String soubor) throws SAXException, IOException, ParserConfigurationException {
    // Vytvoření instance parseru
    SAXParser parser = SAXParserFactory.newInstance().newSAXParser();
    // Spuštění parsování
    parser.parse(new File(soubor), this);
    // Nakonec si uživatele vypíšeme do konzole
    uzivatele.forEach(System.out::println);
}

Připravíme si pomocné proměnné pro atributy uživatele. Nemůžeme ukládat přímo do instance, protože třída nemá settery. Druhou možností může být settery přidat, tím ale ztrácíme část zapouzdření. Proměnné naplníme výchozími hodnotami, ty se dosadí v případě, že daná hodnota nebude v XML zapsána. Dále si vytvoříme proměnné pro indikaci, že zpracováváme věk nebo datum registrace:

private String jmeno = "";
private int vek = 0;
private LocalDate registrovan = LocalDate.now();

private boolean zpracovavamJmeno = false;
private boolean zpracovavamRegistrovan = false;

Nyní přišel čas přepsat metody, které nám třída DefaultHandler nabízí. Přepíšeme celkem tři metody: startElement(), endElement() a characters():

@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
    // Metoda se zavolá vždy, když parser narazí na nový element
}

@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
    // Metoda se zavolá vždy, když parser narazí na zavírací element
}

@Override
public void characters(char[] ch, int start, int length) throws SAXException {
    // Metoda se zavolá vždy, když nám parser nabízí přečíst hodnotu mezi elementy
}

startElement()

V metodě startElement() nás budou zajímat především dva parametry: qName a attributes. První jmenovaný parametr obsahuje název elementu, který se právě zpracovává. Druhý obsahuje atributy zpracovávaného elementu. Abychom zjistili, který element se zrovna zpracovává, použijeme jednoduchý switch:

public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
    switch (qName) {
        case Konstanty.UZIVATEL:
            // Věk uživatele získáme z atributu uživatele
            vek = Integer.parseInt(attributes.getValue(Konstanty.VEK));
            break;
        case Konstanty.JMENO:
            // Pro zpracování jména si musíme uložit indikátor, že zrovna zpracováváme jméno; čtení hodnoty provedeme jinde
            zpracovavamJmeno = true;
            break;
        case Konstanty.REGISTROVAN:
            // Pro zpracování data registrace si musíme uložit indikátor, že zrovna zpracováváme datum registrace; čtení hodnoty provedeme jinde
            zpracovavamRegistrovan = true;
            break;
    }
}

endElement()

V metodě endElement(), která se volá na při narazení na uzavírací tag, jednoduše přepneme příslušný indikátor zpět na false:

public void endElement(String uri, String localName, String qName) throws SAXException {
    switch (qName) {
        case Konstanty.JMENO:
            // Pokud jsme zpracovávali jméno, tak přepněme indikátor jména na false
            zpracovavamJmeno = false;
            break;
        case Konstanty.REGISTROVAN:
            // Pokud jsme zpracovávali datum registrace, tak přepněme indikátor data registrace na false
            zpracovavamRegistrovan = false;
            break;
        case Konstanty.UZIVATEL:
            // Pokud jsme přečetli všechna data z uživatele, vytvoříme novou instanci a přidáme ji do kolekce
            Uzivatel uzivatel = new Uzivatel(jmeno, vek, registrovan);
            uzivatele.add(uzivatel);
            break;
    }
}

characters()

Poslední metodu, kterou jestě potřebujeme vyplnit, je metoda characters(), pomocí které budeme číst hodnotu mezi elementy. Ke zjištění, jakou hodnotu zrovna chceme přečíst, využijeme naše indikátory. Metoda tedy bude vypadat takto:

public void characters(char[] ch, int start, int length) throws SAXException {
    // Vytvoříme novou instanci textu
    String text = new String(ch, start, length);
    if (zpracovavamJmeno) { // Pokud zpracováváme jméno, tak ho jednoduše přiřadíme
        jmeno = text;
    } else if (zpracovavamRegistrovan) { // Pokud zpracováváme datum registrace, tak ho naparsujeme
        registrovan = LocalDate.parse(text, Uzivatel.formatData);
    }
}

Pokud máme hodně atributů, které musíme načítat, začne nám metoda characters() nepříjemně "bobtnat". Alternativní způsob zpracování může být pomocí využití HashMapy, kdy si pro zpracování jednotlivých atributů vytvoříme lambda funkci, kterou uložíme právě do HashMapy. Jako klíč použijeme název atributu. Více o implementaci si můžete přečíst v článku se ZIP soubory.

Tím máme parsování hotové. Nakonec přidáme main() metodu, kde vytvoříme novou instanci a spustíme parsování:

public static void main(String[] args) {
    try {
        new XmlSaxCteni().parsuj("soubor.xml");
    } catch (SAXException | IOException | ParserConfigurationException e) {
        e.printStackTrace();
    }
}

Výsledkem spuštěného kódu budou tři načtená jména ze souboru.

Konzolová aplikace
Pavel Slavík, 22, 21.březen 2000
Jan Novák, 31, 30.říjen 2012
Tomáš Marný, 16, 1.leden 2011

Pokud se vám načítání příliš nelíbilo, dám vám za pravdu. Zatímco generování nového XML souboru je SAXem velmi jednoduché a přirozené, načítání je opravdu krkolomné. Příště, v lekci Čtení a zápis XML souborů pomocí DOM v Javě, se podíváme na DOM, tedy objektový přístup k XML dokumentu.


 

Stáhnout

Staženo 390x (36.98 kB)
Aplikace je včetně zdrojových kódů v jazyce java

 

 

Článek pro vás napsal David Čápka
Avatar
Jak se ti líbí článek?
10 hlasů
Autor pracuje jako softwarový architekt a pedagog na projektu ITnetwork.cz (a jeho zahraničních verzích). Velmi si váží svobody podnikání v naší zemi a věří, že když se člověk neštítí práce, tak dokáže úplně cokoli.
Unicorn College Autor sítě se informační technologie naučil na Unicorn College - prestižní soukromé vysoké škole IT a ekonomie.
Předchozí článek
Zápis XML souborů SAXem v Javě
Všechny články v sekci
Práce se soubory v Javě
Miniatura
Následující článek
Čtení a zápis XML souborů pomocí DOM v Javě
Aktivity (7)

 

 

Komentáře

Avatar
Blackess
Člen
Avatar
Blackess:1.3.2014 22:16

Ahoj, díky za další ze skvělých a šikovných článků! Dobrá práce!
Mám jeden dotaz. Netýká se to vyloženě XML, ale třídy Calendar. Proč je potřeba znovu nastavovat proměnou registrovan ve switch bloku? Dělám obdobný případ a bez tohoto kódu je v proměné nějaký náhodný datum, ale nedokážu si vysvětlit proč?.. Díky

 
Odpovědět 1.3.2014 22:16
Avatar
David Čápka
Tým ITnetwork
Avatar
Odpovídá na Blackess
David Čápka:21.3.2014 17:33

Protože jinak bys nastavil datum, které se již někde použilo. getInstance() ti dá nové a ty chceš nové. V tomhle případě by se asi nic nestalo, ale někde by to mohlo vadit.

Odpovědět 21.3.2014 17:33
Jsem moc rád, že jsi na síti, a přeji ti top IT kariéru, ať jako zaměstnanec nebo podnikatel. Máš na to! :)
Avatar
Matěj Kripner
Redaktor
Avatar
Matěj Kripner:18.4.2014 11:48

Zdravím,
děkuji za další skvělý tutoriál, jako vždy mi přinesl spoustu nového. Mám jen takové dva dotazy:

  1. Nemělo by být při uzavírání souboru opodmínkováno, jestli se soubor vůbec podařilo otevřít(if(xsr != null))?
  2. Není lepší používat specializované výjimky místo obecné Exception?

Předem díky za odpověď.

Odpovědět 18.4.2014 11:48
Give me a deep enough pipeline, and I shall move the world.
Tento výukový obsah pomáhají rozvíjet následující firmy, které dost možná hledají právě tebe!
Avatar
vratas
Člen
Avatar
vratas:8.11.2015 15:29

import com.sun.xml.in­ternal.txw2.ou­tput.Indentin­gXMLStreamWri­ter; mam stale podtrzeny, musi se to nejak pridat do knihoven?

 
Odpovědět 8.11.2015 15:29
Avatar
Richard H.
Redaktor
Avatar
Odpovídá na vratas
Richard H.:8.11.2015 17:09

Tohle by mělo být součástí standartních knihoven takže ne a nebo je článek již zastaralí jdu to skusit.

Odpovědět 8.11.2015 17:09
Malý užitečný manuál je vždy lepší než bichle k ničemu.
Avatar
Richard H.
Redaktor
Avatar
Odpovídá na vratas
Richard H.:8.11.2015 17:11

Mě to funguje co máš za javu ?

Odpovědět 8.11.2015 17:11
Malý užitečný manuál je vždy lepší než bichle k ničemu.
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 6 zpráv z 6.