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 Kotlin

V minulé lekci, Zápis XML souborů SAXem v Kotlin, jsme si představili formát XML a ukázali si, jak pomocí SAXu vytvořit jednoduché XML.

V dnešním Kotlin tutoriálu si ukážeme načtení XML souboru s uživateli a sestavení příslušné objektové struktury. Načtené uživatele uložíme do kolekce ArrayList a necháme si je vypsat.

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

Soubor soubor.xml

Do domovského adresáře 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

Do projektu přidáme třídu Uzivatel. Ta bude podobná příkladům z dřívějška, upravíme v ní pouze metodu toString(), aby vypisovala nejen jméno, ale i věk a datum registrace:

class Uzivatel(private val jmeno: String, private var vek: Int, private val registrovan: LocalDate) {

    override fun toString(): String {
        return "$jmeno, $vek, " + formatData.format(registrovan)
    }

    companion object {
        var formatData: DateTimeFormatter = DateTimeFormatter.ofPattern("d'.'LLLL yyyy")
    }
}

Třída Konstanty

Než se přesuneme k samotnému čtení, vytvoříme si pomocný objekt nazvaný Konstanty, do kterého si uložíme konstanty s názvy jednotlivých elementů v XML souboru:

object Konstanty {
    const val UZIVATEL = "uzivatel"
    const val VEK = "vek"
    const val JMENO = "jmeno"
    const val 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. Uživatele budeme chtít načíst do nějaké kolekce, vytvořme si tedy prázdný list ArrayList nazvaný uzivatele.

Připravíme si pomocné proměnné pro atributy uživatele. Ty 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 jméno nebo datum registrace:

class XmlSaxCteni : DefaultHandler() {

    private val uzivatele: MutableList<Uzivatel> = ArrayList()
    private var jmeno: String? = null
    private var vek = 0
    private var registrovan: LocalDate? = null
    private var zpracovavamJmeno = false
    private var zpracovavamRegistrovan = false

}

Metoda parsuj()

V hlavní třídě XmlSaxCteni si založíme privátní metodu parsuj(), která bude jako parametr přijímat cestu k XML souboru v podobě instance třídy Path. V těle této metody odstartujeme samotné parsování.

Ke čtení XML pomocí SAX využijeme 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á. Načtené uživatele následně necháme vypsat:

@Throws(SAXException::class, IOException::class, ParserConfigurationException::class)
private fun parsuj(soubor: String) {
    val parser: SAXParser = SAXParserFactory.newInstance().newSAXParser()
    parser.parse(File(soubor), this)
    uzivatele.forEach(Consumer { x: Uzivatel? -> println(x) })
}

Přepsání metod třídy DefaultHandler

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():

@Throws(SAXException::class)
override fun startElement(uri: String?, localName: String?, qName: String?, attributes: Attributes) {
// Metoda se zavolá vždy, když parser narazí na nový element.
}

@Throws(SAXException::class)
override fun endElement(uri: String?, localName: String?, qName: String?) {
// Metoda se zavolá vždy, když parser narazí na zavírací element.
}

@Throws(SAXException::class)
override fun characters(ch: CharArray?, start: Int, length: Int) {
// 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 parametr: qName. Ten obsahuje název elementu, který se právě zpracovává. Abychom zjistili, který element se zrovna zpracovává, použijeme jednoduchý when:

@Throws(SAXException::class)
override fun startElement(uri: String?, localName: String?, qName: String?, attributes: Attributes) {
    when (qName) {
        // Věk uživatele získáme z atributu uživatele.
        Konstanty.UZIVATEL ->
            vek = attributes.getValue(Konstanty.VEK).toInt()
        // Pro zpracování jména si musíme uložit indikátor, že zrovna zpracováváme jméno, čtení hodnoty provedeme jinde.
        Konstanty.JMENO -> zpracovavamJmeno = true
        // Pro zpracování data registrace si musíme uložit indikátor, že zrovna zpracováváme datum registrace, čtení hodnoty provedeme jinde.
        Konstanty.REGISTROVAN -> zpracovavamRegistrovan = true
    }
}

Metoda endElement()

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

@Throws(SAXException::class)
override fun endElement(uri: String?, localName: String?, qName: String?) {
    when (qName) {
        // Pokud jsme zpracovávali jméno, tak přepněme indikátor jména na false.
        Konstanty.JMENO -> zpracovavamJmeno = false
        // Pokud jsme zpracovávali datum registrace, tak přepněme indikátor data registrace na false.
        Konstanty.REGISTROVAN -> zpracovavamRegistrovan = false
        // Pokud jsme přečetli všechna data z uživatele, vytvoříme novou instanci a přidáme ji do kolekce.
        Konstanty.UZIVATEL -> {
            val uzivatel = Uzivatel(jmeno!!, vek, registrovan!!)
            uzivatele.add(uzivatel)
        }
    }
}

Metoda characters()

Poslední metodu, kterou ještě potřebujeme přepsat, 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:

@Throws(SAXException::class)
override fun characters(ch: CharArray?, start: Int, length: Int) {
// Vytvoříme novou instanci textu.
    val text = 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)
    }
}

Máme-li 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.

Metoda spust()

Nakonec přidáme metodu spust(), kterou budeme volat v Main třídě:

fun spust() {
    try {
        XmlSaxCteni().parsuj("soubor.xml")
    } catch (e: SAXException) {
        e.printStackTrace()
    } catch (e: IOException) {
        e.printStackTrace()
    } catch (e: ParserConfigurationException) {
        e.printStackTrace()
    }
}

Hlavní třída

V Main třídě pouze vytvoříme instanci třídy XmlSaxCteni a zavoláme na ní metodu spust():

fun main(args: Array<String>) {
    val xml = XmlSaxCteni()
    xml.spust()
}

Máme hotovo.

Testování

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

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 Kotlin, se podíváme na DOM, tedy objektový přístup k XML dokumentu, kterým můžeme XML soubory číst i zapisovat.


 

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 2x (6.91 MB)
Aplikace je včetně zdrojových kódů v jazyce Kotlin

 

Předchozí článek
Zápis XML souborů SAXem v Kotlin
Všechny články v sekci
Soubory a práce s nimi v Kotlin
Přeskočit článek
(nedoporučujeme)
Čtení a zápis XML souborů pomocí DOM v Kotlin
Článek pro vás napsal Filip Studený
Avatar
Uživatelské hodnocení:
1 hlasů
.
Aktivity