.NET

Celá platforma .NET je postavena na využití XML (pro webové služby, konfigurační soubory apod.) a nabízí proto velmi dobrou programátorskou podporu pro zpracování XML. Kromě toho samotné vývojové prostředí VisualStudio.NET nabízí mnoho užitečných nástrojů, jako editory XML a XML schémat. V tomto článku se však nebudeme zabývat možnostmi vývojového prostředí, ani podporou webových služeb. Soustředíme se výhradně na knihovny pro zpracování XML.

Všechny .NET jazyky používají stejné knihovny. Podpora XML je soustředěna ve jmenném prostoru System.Xml – jmenné prostory jsou obdobou javových balíčků. Microsoft měl výbornou implementaci XML k dispozici již dříve v podobě knihovny MSXML. System.Xml je nástupcem MSXML – vylepšena byla především rychlost a možnost rozšiřování.

System.Xml obsahuje asi vše, co pro práci s XML vývojář potřebuje. Jako základ pro čtení XML slouží třída XmlReader, která implementuje pull parser. Pokud potřebujeme komfortnější přístup k dokumentu, máme k dispozici rozšířené DOM2 rozhraní. Pro serializaci XML dokumentů zase můžeme použít XmlWriter. Nad jakýmkoliv dokumentem můžeme pokládat XPath výrazy a provádět na něm XSLT transformace. Všechny parsery mohou pracovat ve validujícím režimu a podporují dokonce XML schémata. Díky tomu, že celá XML knihovna je od jednoho výrobce, její jednotlivé části do sebe pěkně zapadají. Podívejme se na ně tedy podrobněji.

Obrázek 7. Nejdůležitější XML třídy .NETu a jejich vzájemné vztahy

XmlReader

XmlReader je abstraktní třída pro sekvenční čtení XML dokumentů. .NET přináší její tři různé implementace – XmlTextReader pro čtení ze vstupních proudů (soubory, HTTP spojení apod.), XmlNodeReader pro sekvenční čtení části DOM stromu a XmlValidatingReader pro čtení a současnou validaci dokumentu. Můžeme si vytvořit vlastní implementace; na Internetu se dají získat např. třídy, které umožňují čtení celého souborového systému nebo registru jako XML dokumentu.

Samotné vytvoření parseru je velmi jednoduché. Konstruktor totiž rovnou počítá se jménem souboru, který chceme zpracovat:

XmlTextReader reader = new XmlTextReader("faktura.xml");

Nyní můžeme dokument postupně číst a zjišťovat informace o jeho jednotlivých částech. Knihovna navíc umí nezajímavé části dokumentu přeskakovat, vracet obsah elementu jako XML fragment apod. Celé zpracování dokumentu se pak redukuje na postupné procházení dokumentu ve smyčce. Mnoho programátorů vnímá tento přístup snazší než použití SAXu, kde se musí starat o velké množství stavových informací. Sečtení faktury lze napsat opravdu na pár řádcích:

while (reader.Read())
{
  if (reader.NodeType == XmlNodeType.Element 
      && reader.Name == "cena")
  {
    double sazbaDPH = Double.Parse(reader.GetAttribute("dph")) / 100;
    double castka = Double.Parse(reader.ReadString());
    suma += castka;
    sumaDPH += sazbaDPH * castka;
  }
}

Jediné na co si musíme dát pozor je čtení atributů a obsahu elementu ve správném pořadí. Nejprve musíme číst atributy a teprve poté obsah elementu. Kdybychom v naší ukázce dva zmíněné řádky prohodili, přestal by program fungovat.

Práce s třídou je tak příjemná, že brzy zapomenete na push parsery jako je SAX. I když není problém nad XmlReaderem vytvořit SAX obálku.

DOM

Pokud potřebujeme dokument procházet nesekvenčně nebo jej v paměti modifikovat, máme k dispozici standardní rozhraní DOM. To je navíc rozšířeno o několik dalších metod a vlastností, které programátorskou práci usnadňují – zejména vlastnosti InnerText a InnerXml usnadňují čtení obsahu elementu ve srovnání s tím, co nabízí standard. Hnidopich by možná řekl, že aplikace pak nebude používat standardní DOM rozhraní, ale vzhledem k tomu, že stejně poběží jen v .NETu a bude používat parser ze System.Xml, je to asi jedno.

DOM rozhraní je také rozšířeno o metody Load a Save, které umožňují načtení a uložení XML dokumentu ze souboru případně z dalších míst. Dokument tak lze načíst například i přímo z již existujícího XmlReaderu, který byl vygenerován jako výsledek XSLT transformace. Vytvoření DOM reprezentace dokumentu v paměti je velmi jednoduché:

XmlDocument doc = new XmlDocument();
doc.Load("faktura.xml");

Protože používáme standardní DOM, bude kód pro sečtení faktury velmi podobný javové ukázce. Využitím vlastnosti InnerText, která vrací textový obsah všech dětských uzlů, si však ušetříme mnoho práce.

// získání všech elementů cena
XmlNodeList nl = doc.GetElementsByTagName("cena");

// průchod uzly
int n = nl.Count;
double suma = 0;
double sumaDPH = 0;

for (int i=0; i<n; i++)
{
  XmlElement cena = (XmlElement) nl.Item(i);
  double sazbaDPH = Double.Parse(cena.GetAttribute("dph")) / 100;
  double castka = Double.Parse(cena.InnerText);
  suma += castka;
  sumaDPH += sazbaDPH * castka;
}

// výpis statistiky
System.Console.WriteLine("Celkem Kč:  " + suma);
System.Console.WriteLine("Celkem DPH: " + sumaDPH);

Kromě třídy XmlDocument obsahuje .NET i třídu XmlDataDocument, která slouží pro ukládání a manipulaci se záznamy z relačních databáze. Tato třída paralelně nabízí dva způsoby práce s daty – jako s klasickými záznamy nebo jako s XML dokumentem pomocí DOM rozhraní. Můžeme tak na databázové záznamy snadno aplikovat XSLT transformace nebo se dotazovat pomocí XPathu.

XPath

V předchozím textu jsme si již několikrát zmínili, že často bývá jednodušší použít jednoduchý XPath dotaz, než se ručně brodit po zákoutích XML dokumentu. .NET obsahuje speciální třídu XPathNavigator, která umožňuje vyhodnocování XPath výrazů nad libovolným zdrojem dat. V současné době lze jako zdroj dat použít DOM dokument (XmlDocument), fragment databáze (XmlDataDocument) a speciální reprezentaci XML dokumentu určenou jen pro čtení a rychlé provádění XSLT transformací (XPathDocument).

Typická práce s XPath navigátorem vypadá tak, že si načteme XML dokument do paměti

XPathDocument doc = new XPathDocument("faktura.xml");

a pak si pro dokument vytvoříme navigátor

XPathNavigator nav = doc.CreateNavigator();

Navigátor umožňuje vyhodnocování libovolných XPath výrazů. Pokud výraz vrací skalární hodnotu, použijeme metodu Evaluate. Sečtení faktury se pak redukuje na jeden XPath výraz a jeho vyhodnocení:

double suma = (double) nav.Evaluate("sum(/faktura/polozka/cena)");
System.Console.WriteLine("Celkem Kč:  " + suma);

Mnohem častější jsou však výrazy, které vracejí množinu uzlů z dokumentu. Výsledkem je pak iterátor, který umožňuje snadný průchod výsledkem dotazu. Použití si ukážeme opět na příkladě sečtení faktury.

// vytvoření iterátoru pro všechny elementy cena
XPathNodeIterator iter = nav.Select("/faktura/polozka/cena");

// průchod vybranými uzly
while (iter.MoveNext())
{
  double sazbaDPH = Double.Parse(iter.Current.GetAttribute("dph","")) / 100;
  double castka = Double.Parse(iter.Current.Value);
  suma += castka;
  sumaDPH += sazbaDPH * castka;
}

// výpis statistiky
System.Console.WriteLine("Celkem Kč:  " + suma);
System.Console.WriteLine("Celkem DPH: " + sumaDPH);

XSLT

XSLT transformaci lze aplikovat na libovolný XPath navigátor – ten můžeme vytvořit pro dokument na disku, pro DOM strom nebo z XmlReaderu. Zcela obdobně můžeme styl načíst z jakéhokoliv zdroje a výsledek uložit buď do souboru, nebo z něj vytvořit nový XmlReader, který dále využijeme například pro zřetězené zpracování.

// načtení dokumentu, který chceme transformovat 
XPathDocument doc = new XPathDocument("faktura.xml");

// vytvoření XSLT transformátoru a načtení stylu
XslTransform t = new XslTransform();
t.Load("faktura.xsl");

// vytvoření XmlWriteru pro uložení výsledku transformace
XmlTextWriter writer = new XmlTextWriter(new StreamWriter("faktura.html"));

// nastavení formátování výstupu
writer.Formatting = Formatting.Indented;
writer.Indentation = 2;

// provedení transformace
t.Transform(doc, null, writer);

Validace

Pokud chceme dokument při čtení validovat, musíme k jeho načtení použít třídu XmlValidatingReader. Vzhledem k modulární architektuře můžeme tuto třídu používat i při vytváření DOM stromu – validaci tedy můžeme provádět bez ohledu na to, jakým způsobem potřebujeme dokument v aplikaci číst. Dokument lze validovat oproti DTD nebo XML schématu, parser podporuje i XDR – historického předchůdce XML schémat z dílny Microsoftu. Při čtení můžeme také zjišťovat datový typ (PSVI), případně přečíst obsah elementu či atributu typově a nechat si ho rovnou uložit do odpovídajícího nativního datového typu.

Chceme-li během čtení dokument validovat, musíme si nejprve vytvořit normální XmlReader a teprve na něj si přidat validační obálku:

// vytvoření pull parseru pro dokument
XmlTextReader reader = new XmlTextReader("faktura.xml");

// vytvoření validujícího parseru
XmlValidatingReader vr = new XmlValidatingReader(reader);

S validujícím XmlReaderem můžeme pracovat stejně jako s nevalidujícím. Můžeme jej použít i jako vstup pro vytvoření DOM stromu:

// provedení validace a vytvoření DOM stromu
doc.Load(vr);

Jak bude aplikace reagovat na chybu, záleží samozřejmě na nás. Můžeme si definovat vlastní funkci, která se stará o obsluhu chybových stavů:

// funkce obsluhující výskyt chyby v&nbsp;dokumentu
static void ValidationCallback (object sender, ValidationEventArgs args)
{
  // vypsání chybového hlášení
  Console.WriteLine("Error: {0}", args.Message);
}

Aby XmlValidatingReader používal naší obslužnou funkci, musíme ji přidat jako obsluhu událostí:

// registrace události pro obsluhu chyb
ValidationEventHandler errorHandler = new ValidationEventHandler(ValidationCallback);
vr.ValidationEventHandler += errorHandler;

Samotné čtení dokumentu pak probíhá zcela klasicky pomocí Read(), která postupně vrací jeden prvek XML za druhým. Pokud chceme dokument jen zvalidovat a nechceme ho nijak zpracovávat, stačí „naprázdno“ přečíst celý dokument.

while (vr.Read()) { }

Při validaci dokumentů se pro každý dokument zjistí jakému má vyhovovat schématu, toto schéma se načte do paměti a zkompiluje a poté se proti němu dokument zkontroluje. V aplikacích, které zpracovávají velké množství dokumentů s předem známými schématy, je tento přístup opakovaného načítání schémat velmi neefektivní. Lze proto použít vyrovnávací paměť pro schémata.

Vyrovnávací paměť je implementována třídou XmlSchemaCollection a pro jmenný prostor do ní můžeme uložit odpovídající schéma. K XML dokumentu se schéma připojuje pomocí speciálních atributů ze jmenného prostoru http://www.w3.org/2001/XMLSchema-instance:

<faktura
  xmlns="urn:x-kosek:schemas:faktura:1.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  xsi:schemaLocation="urn:x-kosek:schemas:faktura:1.0 faktura.xsd"
  …>

Tento zápis říká, že elementy v dokumentu patří do jmenného prostoru urn:x-kosek:schemas:faktura:1.0 a že schéma dokumentu je dostupné v souboru faktura.xsd. Chceme-li, aby dokumenty jako tento byly rychleji zpracovány, uložíme schéma pro tento jmenný prostor do vyrovnávací paměti pro schémata:

XmlSchemaCollection xsc = new XmlSchemaCollection();
xsc.Add("urn:x-kosek:schemas:faktura:1.0", "faktura.xsd");

Validujícímu parseru pak musíme říci, že má tuto vyrovnávací paměť a v ní uložená a předkompilovaná schémata používat:

vr.Schemas.Add(xsc);

Vyrovnávací paměť pro schémata je překvapivě účinná. Po jejím aktivování se propustnost systému může zvýšit na dvoj- až trojnásobek.

Práce se schématy

Podobně jako můžeme s obecným dokumentem manipulovat v paměti pomocí rozhraní DOM můžeme s XML schématem v paměti pracovat pomocí Schema Object Model (SOM). SOM je rozhraní speciálně určené pro práci se schématy. Umožňuje programové čtení, modifikování a vytváření XML schémat přímo v paměti bez nutnosti zabývat se syntaxí schémat.

© Jiří Kosek 2002