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