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ě XmlSaxCteni
si 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 502x (36.98 kB)
Aplikace je včetně zdrojových kódů v jazyce Java