Lekce 7 - Čtení XML SAXem v C#
V minulé lekci, Úvod do XML a zápis SAXem, jsme si představili formát XML a ukázali si, jak pomocí SAXu vytvořit jednoduché XML.
Nyní na minulý C# .NET tutoriál navážeme a napíšeme si proces opačný, tedy načtení XML souboru s uživateli a sestavení příslušné objektové struktury (listu uživatelů).
Pro úplnost si opět uvedeme náš XML soubor soubor.xml
:
<?xml version="1.0" encoding="utf-8"?> <uzivatele> <uzivatel vek="22"> <jmeno>Pavel Slavík</jmeno> <registrovan>21.3.2000</registrovan> </uzivatel> <uzivatel vek="31"> <jmeno>Jan Novák</jmeno> <registrovan>30.10.2012</registrovan> </uzivatel> <uzivatel vek="16"> <jmeno>Tomáš Marný</jmeno> <registrovan>12.1.2011</registrovan> </uzivatel> </uzivatele>
A naši třídu Uzivatel.cs
:
class Uzivatel { public string Jmeno { get; private set; } public int Vek { get; private set; } public DateTime Registrovan { get; private set; } public Uzivatel(string jmeno, int vek, DateTime registrovan) { Jmeno = jmeno; Vek = vek; Registrovan = registrovan; } public override string ToString() { return Jmeno; } }
Založme si nový projekt, půjde opět o konzolovou aplikaci. Pojmenujeme ji
XmlSaxCteni
a do složky bin/debug/
nakopírujeme
náš XML soubor. 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 uzivatele. Kód budeme kvůli jednoduchosti psát do metody
Main()
, jak to udělat dobře objektově jsme si v této sekci již
ukazovali.
List<Uzivatel> uzivatele = new List<Uzivatel>();
Čtení XML přes SAX
Ke čtení XML přes SAX nám .NET framework poskytuje třídu
XmlReader
. Pojďme si vytvořit její instanci. Jako tomu bylo u
třídy XmlWriter
, i zde k tomu využijeme tovární metody
Create()
, jejímž parametrem je název souboru. Nezapomeneme do
using
připsat System.Xml
. Vše bude v bloku
using
, který se nám postará o uzavření souboru:
using (XmlReader xr = XmlReader.Create(@"soubor.xml")) { }
Připravíme si pomocné proměnné pro vlastnosti uživatele. Nemůžeme
ukládat přímo do instance, protože vlastnosti jsou read-only. Druhou
možností může být povolit modifikaci zvenčí, tím ale ztrácíme část
zapouzdření. Vlastnosti naplníme výchozími hodnotami, které tam zůstanou
v případě, že daná hodnota nebude v XML zapsána. Budeme potřebovat někam
ukládat jméno aktuálního elementu, k tomu si zadefinujeme stringovou
proměnnou element
. Kód píšeme pochopitelně do
using
bloku.
string jmeno = ""; int vek = 0; DateTime registrovan = DateTime.Now; string element = "";
Začneme načítat soubor. XmlReader
načítá soubor řádek po
řádku, odshora dolů. Na jeho instanci voláme metodu Read()
. Ta
nám při každém zavolání načte další tzv. uzel. Uzlem může být
element, případně atribut nebo textová hodnota elementu (nás bude zajímat
Element
, Text
a EndElement
), dalším
typem uzlu může být např. komentář, které pro nás nyní nebudou
důležité. Pokud je čtečka na konci souboru, vrátí metoda
Read()
false
, v opačném případě vrací
true
. Postoupíme tedy k postupnému načítání všech uzlů v
dokumentu pomocí while
cyklu:
while (xr.Read())
{
}
Na instanci XmlReaderu
máme několik užitečných vlastností,
budeme používat NodeType
, ve které je uložen typ aktuálního
uzlu, na kterém se čtečka nachází. Dále použijeme vlastnosti
Name
a Value
, ve kterých je uloženo jméno
aktuálního uzlu a jeho hodnota (pokud nějakou má).
Nás budou nyní zajímat 2 typy uzlů, Element
a
Text
. Pojďme na ně reagovat, zatím napíšeme podmínky s
prázdnými těly:
// načítáme element if (xr.NodeType == XmlNodeType.Element) { } // načítáme hodnotu elementu else if (xr.NodeType == XmlNodeType.Text) { }
Nyní vložíme kód do první podmínky. Budeme tedy reagovat na načtení elementu. Je zde potřeba provést 2 akce.
Klíčovou akcí bude uložení názvu elementu do proměnné element. Tak budeme vzápětí schopni v druhé podmínce zjistit, kterého elementu je text, který právě čteme.
Pokaždé, když narazíme na element uzivatel, načteme atribut věk pomocí
metody GetAttribute()
, jejímž parametrem je název atributu.
Atribut aktuálního elementu lze načíst takto jednoduše. S hodnotou to tak
jednoduché není, sice existují metody ReadContentAsTyp()
, ale
pozor, ty z nevysvětlitelného důvodu provedou Read()
a tak
rozbijí běh while
cyklu. V nezanořených XML by čtečka
nefungovala správně. Nějakou dobu jsem se snažil problém vyřešit,
ale řešení byla natolik krkolomná, že jsem došel k závěru
ReadContentAs...()
vůbec nepoužívat. Obsah první podmínky tedy
bude vypadat takto:
element = xr.Name; // název aktuálního elementu if (element == "uzivatel") { vek = int.Parse(xr.GetAttribute("vek")); }
Přejděme do další větve, tedy do zpracování hodnoty elementu. Zde
využijeme předem uložené hodnoty názvu elementu, kterou si vložíme do
switch
. Podle elementu uložíme hodnotu do dané vlastnosti
uživatele:
switch (element) { case "jmeno": jmeno = xr.Value; break; case "registrovan": registrovan = DateTime.Parse(xr.Value); break; }
Již jsme velmi blízko, bystřejší si jistě všimli, že uživatele nikde
nepřidáváme. Jeho přidání nastane ve chvíli načtení uzavíracího
elementu </uzivatel>
. K našim dvoum podmínkám tedy
přidáme třetí:
// načítáme ukončující element else if ((xr.NodeType == XmlNodeType.EndElement) && (xr.Name == "uzivatel")) uzivatele.Add(new Uzivatel(jmeno, vek, registrovan));
Máme hotovo
Pro jistotu přikládám kompletní kód načtení souboru:
using (XmlReader xr = XmlReader.Create(@"soubor.xml")) { string jmeno = ""; int vek = 0; DateTime registrovan = DateTime.Now; string element = ""; while (xr.Read()) { // načítáme element if (xr.NodeType == XmlNodeType.Element) { element = xr.Name; // název aktuálního elementu if (element == "uzivatel") { vek = int.Parse(xr.GetAttribute("vek")); } } // načítáme hodnotu elementu else if (xr.NodeType == XmlNodeType.Text) { switch (element) { case "jmeno": jmeno = xr.Value; break; case "registrovan": registrovan = DateTime.Parse(xr.Value); break; } } // načítáme ukončující element else if ((xr.NodeType == XmlNodeType.EndElement) && (xr.Name == "uzivatel")) uzivatele.Add(new Uzivatel(jmeno, vek, registrovan)); } }
Zbývá uživatele vypsat, abychom věděli, že jsme je načetli správně.
Upravíme si metodu ToString()
ve třídě Uzivatel
tak, aby vracela všechny hodnoty:
public override string ToString() { return String.Format("{0}, {1}, {2}", Jmeno, Vek, Registrovan.ToShortDateString()); }
Uživatele jednoduše vypíšeme:
// výpis načtených objektů foreach (Uzivatel u in uzivatele) { Console.WriteLine(u); } Console.ReadKey();
a výsledek:
Konzolová aplikace
Pavel Slavík, 22, 21.3.2000
Jan Novák, 31, 30.10.2012
Tomáš Marný, 16, 12.1.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, Práce s XML soubory pomocí DOM v C#, 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 727x (28.02 kB)
Aplikace je včetně zdrojových kódů v jazyce C#