ASPNL logo (1 kb)
donderdag 15 mei 2008




Microsoft MVP

.NET Codewise Community
<< vorige | overzicht | volgende >>

XML lezen met SAX en XmlReader

Door Michiel van Otegem
28 november 2007

XML lezen met SAX en XmlReader

Er zijn meerdere mogelijkheden als je XML wilt lezen en manipuleren. Je kunt een XML document zelf parsen, maar je kunt ook gebruik maken van bestaande parsers. Een de facto standaard hiervoor is SAX. Door de opkomst van het .NET Framework wordt ook de XmlReader methode steeds belangrijker. Die twee nemen we in dit artikel onder de loep.

In XML gegevensopslag is parsen met het Document Object Model (DOM) behandeld. DOM is heel eenvoudig, en bovendien de standaard die verzorgd wordt door het W3 Consortium (W3C), de belangrijkste standaardenorganisatie voor het wereldwijde web. Een nadeel aan DOM is dat het een heel document eerst in moet lezen alvorens ermee gewerkt kan worden. Vooral bij grote documenten is dit eigenlijk alleen maar lastig, omdat er dan enorme hoeveelheden geheugen gebruikt worden. Bovendien kan het lang duren om een bepaald element te vinden. Verder is het behoorlijk inefficiënt als je een document van meerdere megabytes inleest, terwijl je alleen waardes van bepaalde elementen wilt weten. Dit betekent overigens niet dat DOM nutteloos is, het is alleen niet geschikt voor iedere situatie. Er zijn echter voldoende situaties waarbij het juist heel nuttig is om het hele document in het geheugen te hebben. In andere situaties kun je grofweg gebruik maken van twee alternatieven voor DOM:

-Simple API for XML (SAX)
-XmlReader

SAX is oorspronkelijk gemaakt voor het Java platform, en wordt daar ook veruit het meest gebruikt. Er zijn ook SAX implementaties voor andere platformen, maar die worden aanzienlijk minder gebruikt. Door de impliciete verbintenis met Java is dit niet onbegrijpelijk. Sinds de komst van het .NET Framework heeft men op het Windows platform met XmlReader ook een alternatief dat veel beter aansluit op de Microsoft filosofie. Daarmee is de keuze voor SAX op Windows vrij ongewoon en onnatuurlijk geworden. SAX en XmlReader zijn allebei geen standaarden die zoals DOM door een standaardenorganisatie ontwikkeld worden of geratificeerd zijn. SAX is ontwikkeld buiten de standaardenorganisaties op het zelfde moment dat de W3C aan de ontwikkeling van DOM werkte. SAX is derhalve geboren uit de noodzaak iets te hebben waarmee XML verwerkt kon worden. SAX 1.0 stamt net als de eerste DOM versie uit 1998, en is daarmee in XML-termen stokoud. In 2001 is er een tweede versie van SAX uitgebracht, en dat is momenteel de dominante standaard. In vergelijking met DOM en SAX is XmlReader relatief nieuw. Het is uitgebracht als onderdeel van het .NET Framework in 2002. Omdat XmlReader onderdeel uitmaakt van het .NET Framework, heeft het per definitie een groot bereik. Het is dus snel gegroeid tot een veel gebruikte technologie.

SAX

De aanpak van SAX verschilt nogal van DOM. Met DOM vraag je elementen op uit het document dat in het geheugen staat. Met SAX daarentegen worden elementen één voor één ingelezen. SAX stelt de applicatie op de hoogte van wat het inleest met een parsing event, oftwel: een signaal. Het is vervolgens aan de applicatie om te bepalen wat het met het parsing event doet. De applicatie kan besluiten iets met het gelezen element te doen, of om het weg te gooien en te wachten op het volgende parsing event. Als je niet gewend bent op deze manier te werken, klinkt dit erg vreemd. Ook is het vreemd als je een XML document als een hiërarchie van elementen ziet, aangezien SAX daar helemaal geen rekening mee houdt. Het leest ‘dom’ elementen in. Deze aanpak heeft echter een aantal belangrijke voordelen. Ten eerste wordt er haast geen geheugen gebruikt. Alleen de elementen die je besluit op te slaan nemen geheugen is, maar het is niet waarschijnlijk dat je ooit het hele document in het geheugen hebt zitten. Ten tweede ben je niet gebonden aan de data structuur van DOM. Je kunt zelf een structuur definiëren, en waarden die ingelezen worden door SAX aan die structuur toekennen. Het is zeer waarschijnlijk dat een dergelijke representatie veel minder geheugen inneemt dat het XML document, omdat een objectrepresentatie veel efficiënter is. Je hoeft immers niet de structuur van het XML document te bewaren, want die heb je al vastgelegd in de objectrepresentatie. Dit betekent uiteraard wel dat het document dat je inleest relevant moet zijn voor de objectrepresentatie. Een laatste voordeel is dat SAX uitermate geschikt is als je maar een gedeelte van de gegevens in het document nodig hebt. Je kunt de parser zijn werk laten doen, en alleen werken met parsing events die van toepassing zijn op je applicatie. Dit is bijvoorbeeld bij webservices handig. Een webservice aanroep bestaat uit een XML document dat bestaat uit een "envelope" voor de adressering en afhandeling, en een "body" die informatie bevat voor de service die aangeroepen wordt. Wanneer een webservice aangeroepen wordt, heeft het webservice mechanisme alleen de gegevens in de envelope nodig, en moet het de body doorgegeven aan de applicatie door middel van de gegevens in de envelope. De gegevens in de body zijn totaal niet interessant voor de webservice, terwijl de envelope niet interessant is voor de onderliggende applicatie. Met SAX kun je de overbodige actie van het inlezen van niet-relevante gegevens voorkomen. SAX heeft uiteraard ook nadelen. Ten eerste betekent de manier van werken dat er geen mogelijkheid is voor zogenaamde Random Access. Dat wil zeggen dat het niet mogelijk is om te zeggen “wat is de inhoud van element X?”. Om die vraag te beantwoorden, moet SAX het hele document doorzoeken, of die informatie al ergens hebben opgeslagen. Aangezien SAX die opslag aan jou over laat, is dat laatste dus niet het geval. In dit soort gevallen is DOM veel handiger, omdat DOM het hele document in het geheugen houdt. Gerelateerd aan dit probleem is dat je geen complexe zoekacties kunt uitvoeren op een document. Je kunt dus moeilijk zeggen “geef me alle elementen X”, omdat ook daarmee het hele document doorlopen moet worden. DOM implementaties zijn daarentegen berekend op dit soort acties, en gaan hier dus efficiënt mee om.

De werkwijze van SAX wordt het best geïllustreerd aan de hand van een voorbeeld. Afbeelding 1 laat de code zien van een simpele applicatie. Java is object georiënteerd, dus de applicatie wordt gemaakt als een class (NetOpus) die gebaseerd is op de DefaultHandler class. Dat is de standaard class die gebruikt wordt voor het maken van SAX2 parsers. De class begint met een main methode die dient als beginpunt van de applicatie. Deze wordt automatisch aangeroepen door de Java omgeving als je de applicatie start. De main methode initieert de applicatie door achtereenvolgens een XML parser op te zetten en deze te voeden aan de hand van een bestand dat je meegeeft op de command line.

 1: //Importeer libraries
 2: import java.io.FileReader;
 3: import org.xml.sax.XMLReader;
 4: import org.xml.sax.Attributes;
 5: import org.xml.sax.InputSource;
 6: import org.xml.sax.helpers.XMLReaderFactory;
 7: import org.xml.sax.helpers.DefaultHandler;
 8:  
 9: public class NetOpus extends DefaultHandler
10: {
11:     //Start van de applicatie
12:     public static void main (String args[])
13:         throws Exception //Standaard foutafhandeling
14:     {
15:         //XML parser op te zetten
16:         XMLReader xr = XMLReaderFactory.createXMLReader();
17:         NetOpus handler = new NetOpus();
18:         xr.setContentHandler(handler);
19:         xr.setErrorHandler(handler);
20:  
21:         //Lees bestand van command line
22:         FileReader r = new FileReader(args[0]);
23:         xr.parse(new InputSource(r));
24:     }
25:  
26:     //Constructor
27:     public NetOpus()
28:     {
29:         super();
30:     }
31:  
32:     //---Parsing event handlers---    
33:     public void startDocument ()
34:     {
35:         System.out.println("Start");
36:     }
37:  
38:     public void endDocument ()
39:     {
40:         System.out.println("Einde");
41:     }
42:  
43:     public void startElement (String uri, String name,
44:                   String qName, Attributes atts)
45:     {
46:         if (name.equals("NetOpus") &&
47:             atts.getValue("artikel") != null)
48:         {
49:             System.out.println("NetOpus: " + atts.getValue("artikel"));
50:         }
51:     }
52:  
53:     public void endElement (String uri, String name, String qName)
54:     {
55:         //Afhandeling eind element hier
56:     }
57:  
58:     public void characters (char ch[], int start, int length)
59:     {
60:         //Karakter afhandeling hier
61:     }
62: }
Afbeelding 1: SAX voorbeeld applicatie (Java)

Wanneer main uitgevoerd is, gaat de SAX parser elementen inlezen en komen de parsing events aan de beurt. Dat begint met startDocument als het begin van het document gelezen is, en endDocument als de parser het hele document gehad heeft. In het voorbeeld leveren die parsing events niet meer dan een stukje tekst die aangeven wat er gebeurd is. Veel interessanter is startElement, omdat daarmee wordt aangegeven dat de parser de begintag van een element gevonden heeft. Het geeft daarbij alle naam informatie mee over het element, en ook de attributen die bij het element horen. In het voorbeeld kijken we of de naam van het element gelijk is aan NetOpus, en of het element een artikel attribuut heeft. Als dat zo is, wordt een tekst geschreven met daarin NetOpus gevolgd door de naam van het artikel. In het voorbeeld gebeurt er niets als de eind tag gevonden wordt, en wordt er ook niets gedaan met het characters parsing event. Met die laatste wordt tekst tussen tags afgehandeld. In Afbeelding 2 staat een voorbeeld van een XML document dat je kunt lezen met de applicatie. Er vanuit gaande dat Afbeelding 2 opgeslagen is als netopus.xml, start je de applicatie van de command line als volgt:

java -Dorg.xml.sax.driver=gnu.xml.aelfred2.SAXDriver NetOpus netopus.xml

   1: - <Magazines>
   2:   <NetOpus artikel="XML Deel 4 - SAX" /> 
   3:   <NetOpus artikel="XML Deel 5 - XSLT" /> 
   4:   <ASPNL artikel="ASP.NET Handleiding" /> 
   5:   </Magazines>
Afbeelding 2: Invoer voorbeeld

De SAX implementatie is gebaseerd op een verzameling interfaces. Deze interfaces bepalen de signatuur van de parsing events. Voor alle SAX implementaties is deze signatuur hetzelfde, waardoor de code vrijwel onafhankelijk is van de daadwerkelijk gebruikte parser. Met name in de Java wereld geldt daarom dat een applicatie die gebruik maakt van SAX door het wijzigen van enkele regels code een andere parser kan gebruiken. Omdat vrijwel elke Java implementatie een SAX parser bevat, is het zelfs zo dat je aan de Java omgeving kunt vragen om een generieke SAX parser, zodat je de code überhaupt niet gewijzigd hoeft te worden. Je kunt echter ook een specifieke parser aanroepen, maar dan moet je wel zeker weten dat die beschikbaar is. In het voorbeeld wordt echter gebruik gemaakt van de generieke parser, en wordt op de command line aangegeven welke "driver" (zeg maar parser) gebruikt moet worden. In dit geval wordt gebruik gemaakt van de Ælfred2 SAX parser, maar je zou ook een andere op kunnen geven, zoals bijvoorbeeld Xerces (org.apache.xerces.parsers.SAXParser). Als de parser die je aangeeft beschikbaar is in de Java omgeving, wordt de applicatie uitgevoerd, en krijg je Afbeelding 3 als resultaat. Merk op dat het ASPNL artikel niet in het resultaat staat, omdat daarop gefilterd wordt in startElement.

   1: Begin
   2: NetOpus: XML Deel 4 - SAX
   3: NetOpus: XML Deel 5 - XSLT
   4: Einde
Afbeelding 3: Uitvoer van Afbeelding 2 gelezen met Afbeelding 1

XmlReader

SAX en XmlReader komen erg overeen, maar gebruiken een omgekeerde aanpak. Met SAX is de parser leidend en roept de applicatie aan. Met XmlReader is dit juist andersom. De applicatie vraagt telkens om het volgende element. Deze aanpak geeft ontwikkelaars iets meer controle over het parsing proces, en geeft daardoor bijvoorbeeld de mogelijkheid om de parser te vertellen onder welke voorwaarden je het volgende element wilt hebben. Zo kun je dus eenvoudig een gedeelte van een document overslaan, zonder dat jouw code uit moet zoeken of het element dat je gekregen hebt wel relevant is. Uiteindelijk betekent dit dat de verwerking een fractie sneller is dan met SAX omdat de parsing events altijd plaatsvinden, en jouw code dus altijd wat moet doen. Een ander voordeel is dat je met XmlReader meerdere documenten tegelijk kunt lezen, iets wat met SAX niet kan, omdat parsing events aan één document gekoppeld zijn. Afbeelding 4 laat een voorbeeld zien van een XmlReader applicatie die hetzelfde resultaat geeft als de SAX applicatie uit Afbeelding 1. Je ziet dat de structuur anders is, omdat er geen sprake is van events. In tegenstelling tot de SAX applicatie, moet je expliciet het volgende element opvragen. Dit wordt gedaan in een ‘loop’ totdat het einde is bereikt. Verder moet je zelf bepalen wat voor element je binnen krijgt, terwijl SAX dat afhandelt doordat je een bepaald parsing event ontvangt. De afhandeling van het element is vervolgens overeenkomstig, hoewel de details wat anders zijn. Voordat je de applicatie kunt uitvoeren, dien je deze eerst te compileren. Met het .NET Framework geïnstalleerd en Afbeelding 4 opgeslagen als netopus.cs, doe je die met het volgende commando:

csc netopus.cs

Vervolgens kun je de applicatie uitvoeren met:

netopus.exe netopus.xml

 1: using System;
 2: using System.Xml;
 3:  
 4: namespace NetOpus
 5: {
 6:     class XmlReaderVoorbeeld
 7:     {
 8:         //Startpunt van de applicatie
 9:         static void Main(string[] args)
10:         {
11:             string filename = args[0];
12:  
13:             //XmlReader initialiseren
14:             XmlTextReader xr = new XmlTextReader(filename);
15:             xr.WhitespaceHandling = WhitespaceHandling.None;
16:  
17:             Console.WriteLine("Begin");
18:  
19:             //Door document heen lopen en elementen opvragen
20:             while(xr.Read()) {
21:                 //Alleen elementen afhandelen (geen tekst enz.)
22:                 if(xr.NodeType == XmlNodeType.Element)
23:                 {
24:                     //Controle op NetOpus element
25:                     if(xr.Name == "NetOpus" &&
26:                        xr.HasAttributes)
27:                     {
28:                         //Controle op artikel attribuut
29:                         string artikel = xr.GetAttribute("artikel");
30:                         if(artikel != null)
31:                         {
32:                             Console.WriteLine("NetOpus: " + artikel);
33:                         }
34:                     }
35:                 }
36:             }
37:             Console.WriteLine("Einde");
38:         }
39:     }
40: }
Afbeelding 4: XmlReader voorbeeld applicatie (C#)

Tot slot

SAX en XmlReader zijn beide heel krachtig, maar wel lastiger om mee te werken dan DOM. Er zijn echter behoorlijk wat situaties waarin SAX en XmlReader veel betere keuzes zijn dan DOM. Het aardige van beide architecturen is ook dat ze documenten kunnen inlezen die niet XML zijn, maar doorgegeven kunnen worden aan de applicatie alsof dit wel zo is. Ten opzichte van DOM is dit vrij eenvoudig te realiseren. Op XML Tools Update vind je een hele serie van XmlReader implementaties die gebruik maken van deze mogelijkheid.

Dit artikel is eerder verschenen in NetOpus, september 2004.

<< vorige | ^ naar boven | overzicht | volgende >>
copyright 2000-2007 ASPNL