Zarábaj až 6 000 € mesačne! Akreditované rekvalifikačné kurzy od 0 €. Viac informácií.

8. diel - Čítanie XML súborov SAXom v Kotline

V minulej lekcii, Zápis XML súborov SAXom v Kotline , sme si predstavili formát XML a ukázali si, ako pomocou SAXu vytvoriť jednoduché XML.

V dnešnom Kotline tutoriále si ukážeme načítanie XML súboru s užívateľmi a zostavenie príslušnej objektovej štruktúry. Načítaných užívateľov uložíme do kolekcie ArrayList a necháme si ich vypísať.

Založme si nový projekt, pôjde opäť o konzolovú aplikáciu. Pomenujeme ju XmlSaxCteni.

Súbor soubor.xml

Do domovského adresára, nakopírujeme tento náš XML súbor 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>

Trieda Uzivatel

Do projektu pridáme triedu Uzivatel. Tá bude podobná príkladom z minulosti, upravíme v nej iba metódu toString(), aby vypisovala nielen meno, ale aj vek a dátum registrácie:

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")
    }
}

Trieda Konstanty

Než sa presunieme k samotnému čítaniu, vytvoríme si pomocný objekt nazvaný Konštanty, do ktorého si uložíme konštanty s názvami jednotlivých elementov v XML súbore:

object Konstanty {
    const val UZIVATEL = "uzivatel"
    const val VEK = "vek"
    const val JMENO = "jmeno"
    const val REGISTROVAN = "registrovan"
}

Trieda XmlSaxCteni

Vytvorenú triedu XmlSaxCteni oddedíme od triedy org.xml.sax.helpers.DefaultHandler. Tým sa nám sprístupnia metódy, ktoré neskôr budeme potrebovať pri parsovaní súboru. Užívateľa budeme chcieť načítať do nejakej kolekcie, vytvorme si teda prázdny list ArrayList nazvaný uzivatele.

Pripravíme si pomocné premenné pre atribúty používateľa. Tie nemôžeme ukladať priamo do inštancie, pretože trieda nemá settery.

Druhou možnosťou môže byť settery pridať, tým ale strácame časť zapuzdrenia.

Premenné naplníme predvolenými hodnotami, tie sa dosadia v prípade, že daná hodnota nebude v XML zapísaná. Ďalej si vytvoríme premenné pre indikáciu, že spracovávame meno alebo dátum registrácie:

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

}

Metóda parsuj()

V hlavnej triede XmlSaxCteni si založíme privátnu metódu parsuj(), ktorá bude ako parameter prijímať cestu k XML súboru v podobe inštancie triedy Path. V tele tejto metódy odštartujeme samotné parsovanie.

Na čítanie XML pomocou SAX využijeme abstraktnú triedu SAXParser. Inštanciu tejto triedy získame pomocou metódy newSAXParser(), ktorú poskytuje továrenská trieda SAXParserFactory. Jej inštanciu získame zavolaním SAXParseFactory.newInstance().

Nad inštanciou parseru jednoducho zavoláme metódu parse(), ktoré odovzdáme ako parametre súbor, ktorý chceme naparzovať a handler, ktorý sa o parsovanie postará. Načítaných užívateľov následne necháme vypísať:

@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) })
}

Prepísanie metód triedy DefaultHandler

Teraz prišiel čas prepísať metódy, ktoré nám trieda DefaultHandler ponúka. Prepíšeme celkom tri metódy: startElement(), endElement() a characters():

@Throws(SAXException::class)
override fun startElement(uri: String?, localName: String?, qName: String?, attributes: Attributes) {
// Metóda sa zavolá vždy, keď parser narazí na nový element.
}

@Throws(SAXException::class)
override fun endElement(uri: String?, localName: String?, qName: String?) {
// Metóda sa zavolá vždy, keď parser narazí na zatvárací element.
}

@Throws(SAXException::class)
override fun characters(ch: CharArray?, start: Int, length: Int) {
// Metóda sa zavolá vždy, keď nám parser ponúka prečítať hodnotu medzi elementmi.
}

Metóda startElement()

V metóde startElement() nás budú zaujímať predovšetkým parameter: qName. Ten obsahuje názov elementu, ktorý sa práve spracováva. Aby sme zistili, ktorý element sa práve spracováva, použijeme jednoduchý when:

@Throws(SAXException::class)
override fun startElement(uri: String?, localName: String?, qName: String?, attributes: Attributes) {
    when (qName) {
        // Vek používateľa získame z atribútu používateľa.
        Konstanty.UZIVATEL ->
            vek = attributes.getValue(Konstanty.VEK).toInt()
        // Pre spracovanie mena si musíme uložiť indikátor, že práve spracovávame meno, čítanie hodnoty vykonáme inde.
        Konstanty.JMENO -> zpracovavamJmeno = true
        // Pre spracovanie dátumu registrácie si musíme uložiť indikátor, že práve spracovávame dátum registrácie, čítanie hodnoty vykonáme inde.
        Konstanty.REGISTROVAN -> zpracovavamRegistrovan = true
    }
}

Metóda endElement()

V metóde endElement(), ktorá sa volá pri stretnutí s uzatváracím tagom, jednoducho prepneme príslušný indikátor späť na false:

@Throws(SAXException::class)
override fun endElement(uri: String?, localName: String?, qName: String?) {
    when (qName) {
        // Ak sme spracovávali meno, tak prepnime indikátor mena na false.
        Konstanty.JMENO -> zpracovavamJmeno = false
        // Ak sme spracovávali dátum registrácie, tak prepnime indikátor dátumu registrácie na false.
        Konstanty.REGISTROVAN -> zpracovavamRegistrovan = false
        // Ak sme prečítali všetky údaje z používateľa, vytvoríme novú inštanciu a pridáme ju do kolekcie.
        Konstanty.UZIVATEL -> {
            val uzivatel = Uzivatel(jmeno!!, vek, registrovan!!)
            uzivatele.add(uzivatel)
        }
    }
}

Metóda characters()

Poslednou metódou, ktorú ešte potrebujeme prepísať, je metóda characters(), pomocou ktorej budeme čítať hodnotu medzi elementmi. Na zistenie, akú hodnotu práve chceme prečítať, využijeme naše indikátory. Metóda teda bude vyzerať 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)
    }
}

Ak máme veľa atribútov, ktoré musíme načítať, začne nám metóda characters() nepríjemne „pučať“. Alternatívny spôsob spracovania môže byť pomocou využitia kolekcie typu HashMap, kedy si pre spracovanie jednotlivých atribútov vytvoríme lambda funkciu, ktorú uložíme práve do kolekcie typu HashMap. Ako kľúč použijeme názov atribútu.

Metóda spust()

Nakoniec pridáme metódu spust(), ktorú budeme volať v Main triede:

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

Hlavná trieda

V Main triede iba vytvoríme inštanciu triedy XmlSaxCteni a zavoláme na ňu metódu spust():

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

Máme hotovo.

Testovanie

Výsledkom spusteného kódu budú tri načítané mená zo súboru:

Pavel Slavík, 22, 21.March 2000
Jan Novák, 31, 30.October 2012
Tomáš Marný, 16, 1.January 2011

Pokiaľ sa vám načítanie príliš nepáčilo, dám vám za pravdu. Zatiaľ čo generovanie nového XML súboru je SAXom veľmi jednoduché a prirodzené, načítanie je naozaj krkolomné.

V budúcej lekcii Čítanie a zápis XML súborov pomocou DOM v Kotlin , sa pozrieme na DOM, teda objektový prístup k XML dokumentu, ktorým môžeme XML súbory čítať aj zapisovať.


 

Mal si s čímkoľvek problém? Stiahni si vzorovú aplikáciu nižšie a porovnaj ju so svojím projektom, chybu tak ľahko nájdeš.

Stiahnuť

Stiahnutím nasledujúceho súboru súhlasíš s licenčnými podmienkami

Stiahnuté 2x (6.91 MB)
Aplikácia je vrátane zdrojových kódov v jazyku Kotlin

 

Predchádzajúci článok
Zápis XML súborov SAXom v Kotline
Všetky články v sekcii
Súbory a práce s nimi v Kotlin
Preskočiť článok
(neodporúčame)
Čítanie a zápis XML súborov pomocou DOM v Kotlin
Článok pre vás napísal Filip Studený
Avatar
Užívateľské hodnotenie:
Ešte nikto nehodnotil, buď prvý!
.
Aktivity