• Nem Talált Eredményt

XML dokumentum feldolgozása SAX segítségével

In document Szoftverfejlesztés II. (Pldal 76-83)

6. XML dokumentumok feldolgozása, kezelése

6.2.1 XML dokumentum feldolgozása SAX segítségével

A SAX egy olyan eseményvezérelt feldolgozási eljárás, amelynek segítségé-vel az XML tartalmat szekvenciálisan, elemről elemre tudjuk elérni és feldolgoz-ni. A SAX feldolgozás összetevőit a következő ábrán láthatjuk:

9. ábra: A SAX API

A feldolgozó tartalmaz egy olvasó (SAXReader) objektumot. Amikor a feldolgozó meghívja a parse() metódust, az olvasó meghív egy visszahívási (callback) metódust. Ezek a metódusok a ContentHandler, ErrorHandler, DTDHandler és EntityResolver interfészekben van-nak definiálva.

A SAXParserFactory objektum feladata, hogy bizonyos tulajdonságok beállítása után készítsünk a segítségével egy SAXParser objektumot. A SAXParser getXMLReader() metódusával egy SAXReader objektumot kapunk, ez lesz az, amely a SAX-eseménykezelőket (startDocument, startElement, …) hívja meg. Az ábrán nincs rajta, de a DefaultHandler osztálynak is szerepe van, ez az osztály valósítja meg a 3 Handler és 1 Resolver interfészt. Ezeket felül lehet definiálni a saját feldolgozó osztá-lyunkban, ha a DefaultHandler-ből származtattuk. A 4 interfész feladata:

 ContentHandler: a startDocument, endDocument, startElement, endElement metódusok XML elem találatakor hí-vódnak meg, a characters metódus egy elem szöveges értékét adja vissza

 ErrorHandler: az error, fatalError és warning metódusok feldolgozási hiba következtében hívódnak meg

 DTDHandler: egy DTD-vel való érvényességellenőrzés során van sze-repe

 EntityResolver: a resolveEntity metódus akkor hívódik meg, ha egy URI-val megadott külső adatot kell azonosítani a feldolgozás közben

A SAX API-hoz tartozó osztályok és interfészek az org.xml.sax és a javax.xml.parser csomagokban található.

A SAX API-t legtöbbször szervletek és hálózatos alkalmazások esetén hasz-nálják, ugyanis a leggyorsabb és a legkevésbé memóriaigényes eljárás az XML dokumentumok feldolgozásához. Kevesebb memóriát igényel, mint a DOM feldolgozás során, ugyanis nem épít fel az XML-ből faszerkezetet.

Egy SAX-ot használó alkalmazás esetén legtöbbször csak a ContentHandler metódusait valósítják meg. Mivel a SAX eseményvezérelt, ezért nincs benne lehetőség, hogy a dokumentum egy korábban már feldolgo-zott részére visszatérjünk. Továbbá a SAX úgynevezett állapotfüggetlen feldol-gozási eljárás, ugyanis egy elem kezelése független az őt megelőző elemtől.

Amennyiben ilyenre lenne szükség, akkor vagy leprogramozzuk mi a SAX-ban, vagy StAX-ot (Streaming API for XML) használunk.

Nézzük meg a gyakorlatban a SAX használatát egy feladaton keresztül. A feladat az, hogy egy zenei albumokat tároló xml dokumentum tartalmát listáz-zuk ki. Egy album szerkezete a következőképpen néz ki az XML-ben:

1 <albumok>

2 <album ev="" mufaj="">

3 <eloado> </eloado>

11 <minoseg> </minoseg>

12 <ar> </ar>

13 </album>

14 ...

15 </albumok>

10. ábra: Az albumokat tároló XML felépítése

A feldolgozás során arra kell figyelni, hogy az album megjelenésének évét és a műfaját (amelyek az album elem attribútumai) szeretnénk majd az album címe után kiíratni. Az ilyen esetre történt utalás korábban, hogy nem lehetséges a dokumentum egy korábban feldolgozott részére visszatérni, ugyanis ezeket az információkat az album elem feldolgozása után már nem tudjuk elérni. Ezért ezt a 2 attribútumot el kell tárolni! A feldolgozást végző osztály:

1 public class AlbumParser extends DefaultHandler { 2 String actValue="";

3 String actYear="";

4 String actType="";

5 HashMap<String,String> elements=new HashMap<String, String>();

6 //példányinicializátor 7 {

8 elements.put("eloado","Előadó:");

9 elements.put("cim","Cím:");

10 elements.put("kiado","Kiadó:");

11 elements.put("szam","Szám:");

12 elements.put("hossz","Hossz:");

13 elements.put("hang","Hang:");

14 elements.put("minoseg","Minőség:");

15 elements.put("ar","Ár:");

16 }

17 public void endElement(String uri, String localName, String qName) throws SAXException { 18 if (!localName.equals("album") &&

29 public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { int length) throws SAXException {

36 actValue=new String(ch, start, length);

37 } 38 }

A feldolgozó osztályunkat a DefaultHandler osztályból célszerű szár-maztatni, a számunkra szükséges metódusokat (endElement, startElement, characters) csak felül kell definiálni.

Az actValue-ban fogjuk tárolni az aktuális elem értékét. Az actYear és actType fogja tárolni az album elemhez tartozó ev és mufaj attribútumo-kat. Az elements nevű HashMap-ra az elemnevek ékezetes kiíratása miatt van szükség. Az endElement-ben csak akkor van szükség kiíratásra, ha nem egy album vagy a gyökérelem (albumok) zárócímkéjét találtuk meg, ugyanis ilyen esetben az elem alelemeket tartalmazott, nem pedig az albumhoz tartozó

szöveges információértéket. Az if belsejében kiírjuk az elem nevéhez tartozó ékezetes megfelelőt a HashMap-ból, továbbá annak értékét (actValue).

Amennyiben a cim elemnél jártunk, akkor a cím után megjelenítjük az album, ev és mufaj attribútumait is. Ha az album árát jelenítettük meg, akkor utána teszünk egy üres sort, hogy az albumok megjelenítése ne érjen össze. A startElement metódusban ha találunk egy album elemet, akkor a 2 attribú-tumát eltároljuk az actYear-ben és actType-ban. A characters metó-dusban mindig az aktuális elem szöveges értékét tároljuk el. Itt annyit fontos megjegyezni, hogy nem garantálható, hogy a characters egyszer fut le egy elem szöveges értékének megtalálásakor (hosszú szöveges értékek esetén), éppen ezért célszerű egy StringBuilder vagy StringBuffer használata a karakterek összefűzéséhez.

A feldolgozás elindítása:

1 URI uri=new URI(new File("albumok.xml").toURI().

toString());

Az uri-ban az xml elérését fogjuk tárolni. Akár lokális fájlt (jelenlegi példa), akár egy hálózaton elérhető fájlt is megadhatunk, utóbbinál értelemszerűen a File példányt el kell hagyni. Az uri azért fontos, mert a feldolgozás elindítása-kor egy URI-t kell megadni szöveges formában. Lekérünk egy SAXParserFactory példányt (3. sor), beállítjuk, hogy vegyük figyelembe a névtereket (4. sor), majd előállítunk egy SAXParser-t a SAXParserFactory példányunk segítségével, és elindítjuk a feldolgozást. A parse-nak meg kell adni az XML dokumentum URI-ját és a feldolgozásért fele-lős osztályt, amelyben a visszahívási metódusok vannak.

A programunk már működik, de még nem ártana a hibakezelésre is figyel-ni. A DefaultHandler implementálja az ErrorHandler interfészt. A fel-dolgozás során lefuthatnak a következő metódusok:

 fatalError: ha az XML nem szabványos, a feldolgozás nem folyta-tódik

 error: ha az XML nem érvényes

 warning: pl. ha egy elem többször lett definiálva a sémában.

A fatalError és error metódusokat fogjuk felüldefiniálni az AlbumParser-ben, továbbá létrehozunk egy Writer adattagot log néven, és ebbe fogjuk rögzíteni a hibák leírását. A log-nak a konstruktorban átadott paraméter lesz az értéke. Ha akarjuk, akkor fájlba vagy a konzolra is logolhatunk:

1 Writer logF=new FileWriter(new Fi-le("log.txt"),true);

2 PrintWriter logS=new PrintWriter(new PrintStream(System.out));

3 saxParser.parse(uri.toString(), new AlbumParser(logF));

Az AlbumParser osztályunk konstruktorral és 2 metódussal bővül:

1 public AlbumParser(Writer l) { 2 this.log=l;

3 }

4 public void error(SAXParseException e) throws SAXException {

10 public void fatalError(SAXParseException e) throws SAXException {

A fatalError működését azonnal láthatjuk, ha az XML-ünket elrontjuk, és lehagyunk valamelyik címkéről egy < vagy > jelet. Az XML dokumentumunk nem lesz szabványos, tehát máris fatal errort kapunk.

Hogy meg tudjuk nézni az error metódus működését is, szükségünk lesz egy sémával való érvényesség ellenőrzésére, ugyanis ha nem érvényes az XML dokumentum, akkor kapunk error-t. Az érvényességellenőrzéssel kapcsolat-ban tudnunk kell:

 szükséges egy XML-Schema vagy egy DTD dokumentum

 a séma miatt a feldolgozó meg fogja hívni az

ignorableWhitespace metódust, ha whitespace karaktert talál.

Séma beállítása esetén egyértelmű, hogy mikor kell meghívni a feldolgozó-nak az ignorableWhitespace metódust, korábban ilyen karakterek esetén is a characters metódus futott le.

A sémát a SAXParserFactory példánynál kell beállítani, így olyan SAXParser-t fog létrehozni, amelyben már aktív lesz a megadott séma alap-ján az érvényességellenőrzés. Tehát a korábbi kód kiegészítve:

1 ...

2 SchemaFactory sf=SchemaFactory.newInstance(

"http://www.w3.org/2001/XMLSchema");

3 spf.setSchema(sf.newSchema(new Fi-le("albumok.xsd")));

4 SAXParser saxParser=spf.newSAXParser();

Amennyiben a séma alapján az XML nem érvényes, akkor fut le az ErrorHandlererror metódusa.

In document Szoftverfejlesztés II. (Pldal 76-83)