Vydělávej až 160.000 Kč měsíčně! Akreditované rekvalifikační kurzy s garancí práce od 0 Kč. Více informací.
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.

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

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.

V dnešním Java tutoriálu si ukážeme načtení XML souboru s uživateli a sestavení příslušné objektové struktury (listu uživatelů).

Založme si nový projekt, půjde opět o konzolovou aplikaci. Pojmenujeme ji XmlSaxCteni.

Soubor soubor.xml

Do adresáře itnetwork, v domovském adresáři, nakopírujeme tento náš XML soubor soubor.xml:

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

Třída Uzivatel

A zde je naše třída Uzivatel:

public class Uzivatel {
    private final String jmeno;
    private final int vek;
    private final 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;
    }
}

Třída Konstanty

Než se přesuneme k samotnému čtení, vytvoříme si pomocnou třídu nazvanou Konstanty, 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";

}

Třída XmlSaxCteni

Vytvořenou třídu XmlSaxCteni 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ý list ArrayList nazvaný uzivatele.

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

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;

Metoda parsuj()

V hlavní třídě XmlSaxCtenisi založíme privátní metodu parsuj(Path soubor), která bude jako parametr přijímat cestu k XML souboru v podobě instance třídy Path:

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

V těle této metody odstartujeme samotné parsování. Ke čtení XML pomocí SAX nám Java poskytuje abstraktní třídu SAXParser. Instanci této třídy získáme pomocí metody newSAXParser(), kterou poskytuje tovární třída SAXParserFactory, jejíž instanci získáme zavoláním SAXParseFactory.newInstance(). 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(Path soubor) throws SAXException, IOException, ParserConfigurationException {
    // Vytvoření instance parseru
    SAXParser parser = SAXParserFactory.newInstance().newSAXParser();
    // Spuštění parsování
    parser.parse(soubor.toFile(), this);
    // Nakonec si uživatele vypíšeme do konzole
    uzivatele.forEach(System.out::println);
}

Metody startElement(), endElement() a characters()

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
}

Metoda 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;
    }
}

Metoda endElement()

V metodě endElement(), která se volá na při setkání s uzavíracím tagem, 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;
    }
}

Metoda characters()

Poslední metodu, kterou ještě 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í kolekce typu HashMap, kdy si pro zpracování jednotlivých atributů vytvoříme lambda funkci, kterou uložíme právě do kolekce typu HashMap. Jako klíč použijeme název atributu. Více o implementaci si můžete přečíst v článku se ZIP soubory.

Metoda main()

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

public static void main(String[] args) {
    Path soubor = Paths.get(System.getProperty("user.home"), "itnetwork", "soubor.xml");
    try {
        new XmlSaxCteni().parsuj(soubor);
    } catch (SAXException | IOException | ParserConfigurationException e) {
        e.printStackTrace();
    }
}

Testování

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

Konzolová aplikace
Pavel Slavík, 22, 21.March 2000
Jan Novák, 31, 30.October 2012
Tomáš Marný, 16, 1.January 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é.

V příští lekci Čtení a zápis XML souborů pomocí DOM v Javě, se podíváme na DOM, tedy objektový přístup k XML dokumentu.


 

Měl jsi s čímkoli problém? Stáhni si vzorovou aplikaci níže a porovnej ji se svým projektem, chybu tak snadno najdeš.

Stáhnout

Stažením následujícího souboru souhlasíš s licenčními podmínkami

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

 

Předchozí článek
Zápis XML souborů SAXem v Javě
Všechny články v sekci
Soubory v Javě
Přeskočit článek
(nedoporučujeme)
Čtení a zápis XML souborů pomocí DOM v Javě
Článek pro vás napsal David Hartinger
Avatar
Uživatelské hodnocení:
32 hlasů
David je zakladatelem ITnetwork a programování se profesionálně věnuje 15 let. Má rád Nirvanu, nemovitosti a svobodu podnikání.
Unicorn university David se informační technologie naučil na Unicorn University - prestižní soukromé vysoké škole IT a ekonomie.
Aktivity