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