Hledáme pomocníky pro kurzy programování - pohodová brigáda. Více info
Nauč se s námi víc. Využij 50% zdarma na e-learningové kurzy. Zároveň pouze tento týden sleva až 80 % na e-learning týkající se Javy

Lekce 7 - Zápis XML souborů SAXem v Javě

V minulém dílu našeho seriálu tutoriálů pro Javu jsme si popsali formát XML, Úvod do formátu XML souborů v Javě.

Dnes si v Javě takový soubor vytvoříme pomocí přístupu SAX.

Zápis XML

Pojďme si vytvořit jednoduché XML, využijeme k tomu minule uvedený příklad s uživateli. Vytvořte si nový projekt jménem XmlSaxZapis a k projektu přidejte následující třídu:

public class Uzivatel {
    private String jmeno;
    private int vek;
    private LocalDate registrovan;
    public static DateTimeFormatter formatData = DateTimeFormatter.ofPattern("d'.'MMMM yyyy H:mm");

    public Uzivatel(String jmeno, int vek, LocalDate registrovan){
        this.jmeno = jmeno;
        this.vek = vek;
        this.registrovan = registrovan;
    }

    @Override
    public String toString() {
           return getJmeno();
    }

    public String getJmeno() {
        return jmeno;
    }

    public int getVek() {
        return vek;
    }

    public LocalDate getRegistrovan() {
        return registrovan;
    }
}

Kód budeme pro jednoduchost psát do metody main(), pouze si vyzkoušíme funkčnost SAXu. Z minulých dílů víte, jak se aplikace píší správně objektově.

XMLStreamWriter vytváříme pomocí třídy XMLOutputFactory. Do XML můžeme uložit samozřejmě i jen 1 objekt (např. nastavení), my si zde ukážeme uložení seznamu několika objektů. Pokud budete chtít uložit objekt jen jeden, bude úprava již hračkou :)

Jako první si vytvoříme testovací ArrayList uživatelů:

ArrayList<Uzivatel> uzivatele = new ArrayList<>();
LocalDate datum1 = LocalDate.of(2000, Month.MARCH, 21);
LocalDate datum2 = LocalDate.of(2012, Month.OCTOBER, 30);
LocalDate datum3 = LocalDate.of(2011, Month.JANUARY, 1);
uzivatele.add(new Uzivatel("Pavel Slavík", 22, datum1));
uzivatele.add(new Uzivatel("Jan Novák", 31, datum2));
uzivatele.add(new Uzivatel("Tomáš Marný", 16, datum3));

Nyní vytvoříme instanci třídy XMLStreamWriter pomocí XMLOutputFactory, ta samotná se vytváří tovární metodou newInstance(). Bohužel nemůžeme použít blok try-with-resources, protože ho XMLStreamWriter nepodporuje. Instanci jako parametr předáme FileWriter, jako tomu bylo u textových souborů.

XMLOutputFactory xof = XMLOutputFactory.newInstance();
XMLStreamWriter xsw = null;
try{
    xsw = xof.createXMLStreamWriter(new FileWriter("soubor.xml"));
}
catch (Exception e){
        System.err.println("Chyba při zápisu: " + e.getMessage());
}
finally{
    try{
        if (xsw != null){
                xsw.close();
        }
    }
    catch (Exception e){
            System.err.println("Chyba při uzavírání souboru: " + e.getMessage());
    }
}

Kód je kvůli nemožnosti použít try-with-resources poněkud krkolomný, později si ukážeme i další přístupy ke XML dokumentu.

Tento výukový obsah pomáhají rozvíjet následující firmy, které dost možná hledají právě tebe!

Pojďme se pustit do samotného zápisu. Nejprve zapíšeme hlavičku dokumentu:

xsw.writeStartDocument();

Dále (jak již víme) musí následovat kořenový element, ve kterém je celý zbytek XML obsažen. K zapisování elementů máme metody writeStartElement() a writeEndElement(). První bere v atributu název elementu, který otevíráme. Druhá metoda pozná název otevřeného elementu sama z kontextu dokumentu a parametry tedy nemá. Otevřme kořenový element, v našem případě element uzivatele:

xsw.writeStartElement("uzivatele");

Nyní se dostáváme k zápisu jednotlivých uživatelů, ten bude tedy přítomen ve foreach cyklu.

Zápis hodnoty do elementu provedeme pomocí metody writeCharacters(), parametrem je zapisovaná hodnota. Obdobně můžeme elementu přidat atribut metodou writeAttribute(), jejíž parametry jsou název atributu a jeho hodnota. Hodnota je vždy typu String, čili v našem případě musíme věk na String převést. Cyklus a zápis elementu uživatel (zatím ještě bez vnořených elementů) bude tedy vypadat takto:

for (Uzivatel u : uzivatele)
{
    xsw.writeStartElement("uzivatel");
    xsw.writeAttribute("vek", Integer.toString(u.getVek()));
    xsw.writeEndElement();
}

Do programu připíšeme ještě jeden EndElement k uzavření kořenového elementu a EndDocument k ukončení celého dokumentu. Podobně jako u textových souborů musíme i zde vyprázdnit buffer metodou flush(). Celý kód programu tedy nyní vypadá takto:

// testovací kolekce uživatelů
ArrayList<Uzivatel> uzivatele = new ArrayList<>();
LocalDate datum1 = LocalDate.of(2000, Month.MARCH, 21);
LocalDate datum2 = LocalDate.of(2012, Month.OCTOBER, 30);
LocalDate datum3 = LocalDate.of(2011, Month.JANUARY, 1);
uzivatele.add(new Uzivatel("Pavel Slavík", 22, datum1));
uzivatele.add(new Uzivatel("Jan Novák", 31, datum2));
uzivatele.add(new Uzivatel("Tomáš Marný", 16, datum3));

// Zápis uživatelů
XMLOutputFactory xof = XMLOutputFactory.newInstance();
XMLStreamWriter xsw = null;
try {
    xsw = xof.createXMLStreamWriter(new FileWriter("soubor.xml"));
    xsw.writeStartDocument();
    xsw.writeStartElement("uzivatele");

    for (Uzivatel u : uzivatele) {
        xsw.writeStartElement("uzivatel");
        xsw.writeAttribute("vek", Integer.toString(u.getVek()));
        xsw.writeEndElement();
    }

    xsw.writeEndElement();
    xsw.writeEndDocument();
    xsw.flush();
}
catch (Exception e) {
        System.err.println("Chyba při zápisu: " + e.getMessage());
}
finally {
    try {
        if (xsw != null) {
                xsw.close();
        }
    }
    catch (Exception e) {
            System.err.println("Chyba při uzavírání souboru: " + e.getMessage());
    }
}

Program si zkusíme spustit a ujistíme se, že vše funguje. Výstup programu by měl vypadat takto (složka s projektem/sou­bor.xml):

<?xml version="1.0" ?><uzivatele><uzivatel vek="22"></uzivatel><uzivatel vek="31"></uzivatel><uzivatel vek="16"</uzivatel></uzivatele>

Data vypadají v pořádku, ale formátování není žádné. Pojďme to napravit. Pod hlavní metodu main() vytvoříme novou metodu, kterou nazveme formatuj(String soubor). Ta bude v parametru přijímat cestu k souboru, který má zformátovat.

private static void formatuj(String soubor) {
    // TODO sem napíšeme tělo formátovače
}

Do těla metody přidáme následující řádky:

DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.parse(new InputSource(new InputStreamReader(new FileInputStream(soubor))));

// Získáme novou instanci transformeru
Transformer xformer = TransformerFactory.newInstance().newTransformer();
// Nastavíme formátování pro XML
xformer.setOutputProperty(OutputKeys.METHOD, "xml");
// Nastavíme odsazení
xformer.setOutputProperty(OutputKeys.INDENT, "yes");
Source source = new DOMSource(document);
Result result = new StreamResult(new File(soubor));
xformer.transform(source, result);

Výsledný zformátovaný zápis by měl vypadat takto:

<?xml version="1.0" ?>
<uzivatele>
  <uzivatel vek="22"></uzivatel>
  <uzivatel vek="31"></uzivatel>
  <uzivatel vek="16"></uzivatel>
</uzivatele>

Vidíme, že SAX poznal, že v elementu uzivatel není kromě atributu žádná hodnota a tak tag vyrenderoval jako nepárový. Nyní vložíme do elementu uzivatel 2 další elementy, přesněji jeho atributy jméno a datum registrace. K tomu účelu si na třídě Uzivatel vytvořme ještě statický DateTimeFormatter:

public static DateTimeFormatter formatData = DateTimeFormatter.ofPattern("d'.'MMMM yyyy H:mm");

Statika je zde opodstatněná, je to pomocný formátovač pro práci s atributem třídy, který se hodí zpřístupnit i zvenčí.

A kód zápisu v cyklu se rozroste o:

xsw.writeStartElement("jmeno");
xsw.writeCharacters(u.getJmeno());
xsw.writeEndElement();
xsw.writeStartElement("registrovan");
xsw.writeCharacters(Uzivatel.formatData.format(u.getRegistrovan()));
xsw.writeEndElement();

Kód vložíme do místa zápisu elementu uzivatel, tedy mezi jeho writeAttribute() a writeEndElement(). Pro jistotu si ještě uveďme kompletní kód části s cyklem:

for (Uzivatel u : uzivatele) {
    xsw.writeStartElement("uzivatel");
    xsw.writeAttribute("vek", Integer.toString(u.getVek()));
    xsw.writeStartElement("jmeno");
    xsw.writeCharacters(u.getJmeno());
    xsw.writeEndElement();
    xsw.writeStartElement("registrovan");
    xsw.writeCharacters(Uzivatel.formatData.format(u.getRegistrovan()));
    xsw.writeEndElement();
    xsw.writeEndElement();
}

A máme hotovo. Hotový program je jako vždy ke stažení pod článkem.

Příště, Čtení XML souborů SAXem v Javě, budeme přes SAX XML číst.


 

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

 

Předchozí článek
Úvod do formátu XML souborů v Javě
Všechny články v sekci
Soubory a síť v Javě
Článek pro vás napsal David Čápka
Avatar
Jak se ti líbí článek?
5 hlasů
David je zakladatelem ITnetwork a programování se profesionálně věnuje 13 let. Má rád Nirvanu, sushi a svobodu podnikání.
Unicorn university David se informační technologie naučil na Unicorn University - prestižní soukromé vysoké škole IT a ekonomie.
Aktivity

 

 

Komentáře
Zobrazit starší komentáře (7)

Avatar
Hartrik
Redaktor
Avatar
Hartrik:19.5.2014 15:12

Pokud to API opravdu neumožňuje, tak můžeš jednoduše načíst stávající obsah, přidat k němu nový a znovu uložit.

Editováno 19.5.2014 15:13
 
Odpovědět
19.5.2014 15:12
Avatar
ucenidolazni
Člen
Avatar
Odpovídá na Hartrik
ucenidolazni:19.5.2014 16:11

Jojo to mě taky došlo, jen mě napadlo jestli není jednodušší způsob :-( No jelikož je to evidence tak budu každý vytvořený záznam ukládat zvlášť jako xml a bude jednodušší pak záznamy mazat...

 
Odpovědět
19.5.2014 16:11
Avatar
Jaroslav Zakouřil:17.11.2015 11:14

Ahoj Davide, přetvořil jsem si Tvůj kód pro svou potřebu a v NetBeans funguje perfektně. Já ale potřebuju, aby fungoval v telefonu pod Androidem. A když jsem ho tam přenesl, tak nelze importovat IndentingXMLStre­amWriter, XMLOutputFactory a XMLStreamWriter a jejich metody použité v kódu. Jde to nějak vyřešit nebo musím pro Android použít jiný zápis do souboru ? A pokud ano, tak jaký ? Díky za odpověď. Jarda Zakouřil

 
Odpovědět
17.11.2015 11:14
Avatar
karolko
Člen
Avatar
karolko:22.1.2016 12:19

preco nejde ten kod so zanorenim
xsw = new IndentingXMLStre­amWriter(xof.cre­ateXMLStreamWri­ter(new FileWriter("")));

bez zanorenia sa to tako prehliada a kazdy prehliadac mi vypise, ze dokuemnt je zle sformatovany...
som tak trosku beginner, ale mam snahu :)

 
Odpovědět
22.1.2016 12:19
Avatar
Jan Kašpar
Člen
Avatar
Odpovídá na karolko
Jan Kašpar:11.7.2016 9:28

Změň encoding v nastavení celého svého projektu a pak už nebudeš mít problém se špatným formátováním

Editováno 11.7.2016 9:28
 
Odpovědět
11.7.2016 9:28
Tento výukový obsah pomáhají rozvíjet následující firmy, které dost možná hledají právě tebe!
Avatar
rosatislav
Člen
Avatar
rosatislav:17.2.2017 19:30

Vidíme, že SAX poznal, že v elementu uzivatel není kromě atributu žádná hodnota a tak tag vyrenderoval jako nepárový.

Jenom pro úplnost, kde přesně vidím, že ten tag byl vytvořen nepárový, když ukázce nad tou větou, je vše párové a i mně se vytvořil dokument, kde je vše párové?

Editováno 17.2.2017 19:31
 
Odpovědět
17.2.2017 19:30
Avatar
Ruda Pivrnec
Člen
Avatar
Ruda Pivrnec:16.2.2019 18:51

Ahoj, použil jsem tento příklad na prográmek pro převod XML s účtenkami na jiné XML pro import do účetního programu. Funguje mi to bezvadně, ale potřeboval bych poradit - s javou dělám poprvé v životě. Když to spustím v NetBeans, je vygenorovaný soubor OK, ale když spustím přímo .jar, tak je v háji čeština. A ještě prosím maličkost - jak zařídit odřádkování a mezery pro přehlednost ve vytvářeném XML? Díky moc.

 
Odpovědět
16.2.2019 18:51
Avatar
Adam Bucher
Člen
Avatar
Adam Bucher:22.8.2019 19:57
finally{
    try{
        if (xsw != null){
                xsw.close();
        }
    }
    catch (Exception e){
            System.err.println("Chyba při uzavírání souboru: " + e.getMessage());
    }
}

Proč dávat do try bloku i testování, zda proměnná neobsahuje hodnotu null - testování bude bezchybné vždy. Potom mi přijde logičtější:

finally{
    if (xsw != null){
        try {
                xsw.close();
        } catch (Exception e) { ... }
    }

Nepravdivá informace o vyrenderování tagu jako nepárového (viz komentář od rosatislav) je v článku stále, i když na ni bylo upozorněno před více než dvěma lety :-(.

Document document = builder.parse(new InputSource(new InputStreamReader(new FileInputStream(soubor))));

Možná tomu dřív tak nebylo, ale lze tento šílený řádek nahradit jednoduchým

Document document = builder.parse(soubor);

a builder už si sám správně document ze souboru vytvoří.

A nakonec, uvedený DateTimeFormatter pro uložení LocalDate podle ukázky nefunguje (řešením je převést jeho argument na LocalDateTime pomocí u.getRegistro­van().atStartOf­Day()) anebo, lépe, změnou patternu při získávání instance DateTimeFormat­teru zápisem metody ofPattern("d'­.'MMMM yyyy")).

 
Odpovědět
22.8.2019 19:57
Avatar
Rozbita Zaluzie:4.6.2020 11:05

Po přidání všech těhle řádků

DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.parse(new InputSource(new InputStreamReader(new FileInputStream(soubor))));

// Získáme novou instanci transformeru
Transformer xformer = TransformerFactory.newInstance().newTransformer();
// Nastavíme formátování pro XML
xformer.setOutputProperty(OutputKeys.METHOD, "xml");
// Nastavíme odsazení
xformer.setOutputProperty(OutputKeys.INDENT, "yes");
Source source = new DOMSource(document);
Result result = new StreamResult(new File(soubor));
xformer.transform(source, result);

se mi vše rozsvítilo jako vánoční stromeček ale i po přidání všech inportů které mi nabídl NetBeans a vyzkoušení zkrácení document builderu se stále najde pár chyb
nevíte kde by mohl být problém

 
Odpovědět
4.6.2020 11:05
Avatar
Jan Křížek:14. září 16:22

Vůbec nechápu smysl privátní funkce "formatuj", když se stejně nikde nevolá. Vůbec nic to nedělá. Všechno je beze změny ať už to tam je nebo ne.
Budu rád, pokud mi to někdo vysvětlí ať tomu více porozumím.

 
Odpovědět
14. září 16:22
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 10 zpráv z 17. Zobrazit vše