Az Objective-C programozási nyelv

NSXMLParser

XML feldolgozás NSXMLParser használatával

Az NSXMLParser egy esemény alapú feldolgozó. Amikor a feldolgozó egy olyan XML tagelemhez ér, mint például a nyitó/záró tagek, a CDATA tagek, vagy akár egy érvénytelen tag, akkor egy esemény váltódik ki. A delegáltja ezeknek az eseményeknek megvalósítja a neki szükséges események lekezelését, ezzel végzi az XML adatok feldolgozását. Mivel ez egy esemény alapú feldolgozó, az adatok csak előre fele olvashatóak vele, vissza nem tudunk menni a feldolgozásban. Az IPhone programok az NSXMLParser, és az NSXMLDocument osztályt használják XML feldolgozására.A fő különbség a feldolgozás kódbeli használatán kívül a két osztály között az, hogy míg az NSXMLDocument a teljes XML fát betölti a memóriába, addig az NSXMLParser nem.

Minta alkalmazás

A működést egy egyszerű navigálós Iphone alkalmazáson mutatom be. Az alkalmazás egy UITableViev-ban jeleníti meg a könyvek címeit, amikre rányomva egy "detail" view-ban jelenek meg a részletek.

Hozzunk létre egy új navigáció alapú alkalmazást az XCode-ban. Szükség lesz egy osztályra az XML-ből beolvasott adatok mentéséhez, hogy később ezeket fel tudjuk használni, és ne keljen minden alkalommal újra be parseolni a fájlt. Ehhez létrehozzuk a Book osztályt ami a book tagek között levő adatokat reprezentálja.

Book osztály

A Book.h, Book.m fájlok kódja:

// // Books.h // books // // Created by Arman Fahmi on 2014.05.26.. // #import // Egy Book entitas az XML fajlban @interface Book : NSObject { NSInteger bookID; NSString *title; NSString *author; NSString *summary; NSString *expiration; } // getter/setter property-k @property (nonatomic, readwrite) NSInteger bookID; @property (nonatomic, retain) NSString *title; @property (nonatomic, retain) NSString *author; @property (nonatomic, retain) NSString *summary; @property (nonatomic, retain) NSString *expiration; @end // // Books.m // books // // Created by Arman Fahmi on 2014.05.26.. // #import "Book.h" @implementation Book @synthesize title, author, summary, bookID, expiration; - (void) dealloc { [summary release]; [author release]; [title release]; [expiration release]; [super dealloc]; } @end

Amit érdemes itt megfigyelni az az hogy az adattagok nevei megegyeznek az XML fájlban levő tagek neveivel. Ezt később meglátjuk miért fontos..

Mivel az XML fájlunk n darab Book tömbböt tartalmaz, szükségünk lesz egy tömbre a Book objektumok tárolásához. Ezt az alkalmazás osztályba veszem fel:

#import <UIKit/UIKit.h> @interface XMLAppDelegate : NSObject < UIApplicationDelegate, NSXMLParserDelegate > { UIWindow *window; UINavigationController *navigationController; NSMutableArray *books; } @property (nonatomic, retain) IBOutlet UIWindow *window; @property (nonatomic, retain) IBOutlet UINavigationController *navigationController; @property (nonatomic, retain) NSMutableArray *books; @end
A delegate

Hogy tisztán maradjon a kódunk, deklarálunk egy delegate-t is az XMLParser osztályban. Az ebben az osztályban levő tömb-ben fogjuk tárolni a beolvasott Book objektumokat.

//XMLParser.h #import <UIKit/UIKit.h> @class XMLAppDelegate, Book; @interface XMLParser : NSObject { NSMutableString *currentElementValue; XMLAppDelegate *appDelegate; Book *aBook; } - (XMLParser *) initXMLParser; @end

A currentElementValue tárolja az aktuális elemet, amit éppen feldolgozzunk karakterenként. Az appDelegate objektumunk tárolja a Books tömböt, amit később a táblázatos megjelenítésnél, és az értesítések létrehozásánál is felhasználunk. És maga az aktuális Book objektum amit minden Book tag feldolgozásakor létrehozzunk, és amíg tart a feldolgozás a záró tagig feltöltünk. Nincs szükség külön külön az adattagok tárolására amiket éppen feldolgozzunk, mert azoknak a neveit megkapjuk az események paraméterében, mint az aktuális XML tag neve, és így egy dictionary-ban eltároljuk az értéküket. Végül van egy konstruktor metódusunk InitXMLParser, lássuk mit csinál:

- (XMLParser *) initXMLParser { [super init]; appDelegate = (XMLAppDelegate *)[[UIApplication sharedApplication] delegate]; return self; }

Lekéri az egyetlen alkalmazás delegate-t, majd visszaadja a referenciát magára.

Az XML fájl parseolása

Már mindent beállitotunk, nézzük az XML parseolást:

@implementation XMLParser // lekeri az alkalmazas objektum referenciajat, mint Singleton-t majd visszaadja a "this"-t - (XMLParser *) initXMLParser { [super init]; appDelegate = (XMLAppDelegate *)[[UIApplication sharedApplication] delegate]; return self; } // Kezdo tagek beolvasasa. Ha akkor inicializalom a books tombot, egyebkent ha akkor letrehozok egy uj Book objektumot - (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedName attributes:(NSDictionary *)attributeDict { if([elementName isEqualToString:@"Books"]) { // tomb inicializalasa appDelegate.books = [[NSMutableArray alloc] init]; } else if([elementName isEqualToString:@"book"]) { //Book objektum inicializalasa aBook = [[Book alloc] init]; //Book ID beallitasa az attributumbol aBook.bookID = [[attributeDict objectForKey:@"id"] integerValue]; NSLog(@"id beolvasasa :%i", aBook.bookID); } NSLog(@"Elem feldolgozasa: %@", elementName); } // egy XML elem ertekenk olvasasa karakterenkent - (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string { // ha ures az aktualis elem, akkor letrehozom if(!currentElementValue) currentElementValue = [[NSMutableString alloc] initWithString:string]; else // egyebkent hozzaadom a karaktert [currentElementValue appendString:string]; NSLog(@"Ertek feldolgozasa: %@", currentElementValue); } // zaro tag feldolgozasa, itt kapja meg a vegleges erteket a Book objektum megfelelo tagja - (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName { // nem kell csinalni semmit if([elementName isEqualToString:@"Books"]) return; // elemnel hozzaadom a Book elemet a books tombhoz es felszabaditom if([elementName isEqualToString:@"book"]) { [appDelegate.books addObject:aBook]; [aBook release]; aBook = nil; } else [aBook setValue:currentElementValue forKey:elementName]; [currentElementValue release]; currentElementValue = nil; } @end

Már mindent implementáltunk ami a parser működéséhez kell, nézzük a tényleges parseolást:

// kezdo muveletek az alkalmazas inditasakor - (void)applicationDidFinishLaunching:(UIApplication *)application { // Books.xml betoltese resource-bol NSString *xmlPath = [[NSBundle mainBundle] pathForResource:@"books" ofType:@"xml"]; NSData *xmlData = [NSData dataWithContentsOfFile:xmlPath]; // NSXMLparser inicializalasa NSXMLParser *xmlParser = [[NSXMLParser alloc] initWithData:xmlData]; // a delegalt inicializalasa XMLParser *parser = [[XMLParser alloc] initXMLParser]; // delegalt beallitasa [xmlParser setDelegate:parser]; // parseolas BOOL success = [xmlParser parse]; // hiba volt a parseolas kozben? if(!success) { // MessageBox-al jelzem a hibat, es kilepek [self showMsg:@"Hiba tortent az XML parse kozben!"]; return; } NSLog(@"parse sikeres volt!"); [self setExpires]; // konyvek lejaratanak ellenorzese, es a local notification-k beallitasa // table view hozzaadasa a foablakhoz [window addSubview:[navigationController view]]; [window makeKeyAndVisible]; }

Miután elindult az alkalmazásunk, beolvassuk az xml fájlunkat, amit most lokálisan tárolunk erőforrásként (legtöbbször NSURL-el olvassuk be egy web címen tárolt xml-ből az adatokat). Ezután létrehozzunk egy NSXMLParser példányt, majd beállítjuk a delegate adattagnak magunkat, és elindítjuk a parseolást. Ha sikeres volt egy Yes-t ad vissza, egyébként ha nem, akkor No értékel tér vissza, ha valamilyen hiba volt, például érvénytelen tag, vagy valamiért a művelet megszakadt.

A kezdő tag feldolgozása

A parser delegatenek nem kell minden metódust implementálnia, így nem muszáj megvalósítanunk a kezdő tag feldolgozását Mi három eseményt fogunk implementálni, azt amikor a parser találkozik egy nyitó tagel, záró tagel, és egy elem értékével.

Nézzük meg a parser:didStartElement:namespaceURI:qualifiedName:attributes metódust, amit akkor hívunk meg amikor a parser egy kezdő tagel találkozik.

- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedName attributes:(NSDictionary *)attributeDict { if([elementName isEqualToString:@"Books"]) { // tomb inicializalasa appDelegate.books = [[NSMutableArray alloc] init]; } else if([elementName isEqualToString:@"book"]) { //Book objektum inicializalasa aBook = [[Book alloc] init]; //Book ID beallitasa az attributumbol aBook.bookID = [[attributeDict objectForKey:@"id"] integerValue]; NSLog(@"id beolvasasa :%i", aBook.bookID); } NSLog(@"Elem feldolgozasa: %@", elementName); }

Ebben az XML fájlban, két kezdő taget figyelek, ha <Books> akkor inicializálom a books tömböt, egyébként ha <Book>, akkor létrehozok egy új Book objektumot, amit majd a záró tagig feltöltök. A books tömbbe pedig a létrehozott book objektumokat pakolom be a feldolgozáskor.

Egy elem értékének a feldolgozása.

Most van egy lokális book objektumunk, ami az aktuális book tag az XML fában, ezt szeretnénk feltölteni értékel. A parser újra a didStartElement eseményt váltja ki, de ez esetben ez minket nem érdekel. A legközelebbi esemény a parser:foundCharacters amit az elem értékére hívódik meg, minden karaktere kiváltódik. Nézzük ennek a kódját:

- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string { if(!currentElementValue) currentElementValue = [[NSMutableString alloc] initWithString:string]; else [currentElementValue appendString:string]; NSLog(@"Ertek feldolgozasa: %@", currentElementValue); }

Ha üres az aktuális elem, akkor létrehozom, egyébként hozzáadom a karaktert az aktuális elemhez.

A záró tag parsolása

A parser az elem végére ugrik, és igy a parser:didEndElement:namespaceURI:qualifiedName üzenet lesz elküldve a delegatenek. Itt állítjuk be az aktuális elem értékét a helyes értékre, majd ezután hozzáadjuk a books tömbhöz a book objektumot, és nil-re állítjuk az értékét. Íme a kód:

- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName { if([elementName isEqualToString:@"Books"]) return; if([elementName isEqualToString:@"book"]) { [appDelegate.books addObject:aBook]; [aBook release]; aBook = nil; } else [aBook setValue:currentElementValue forKey:elementName]; [currentElementValue release]; currentElementValue = nil; }

Ha a záró tag a <Books>, akkor nincs mit csinálni, majdnem készen vagyunk a fájl feldolgozásával. Abban az esetben, ha a záró tag nem <Books>, és nem <Book>, akkor az egy olyan tag ami a <Book> tag egy altagje, pl: <title>, és így a Book objektum egy az aktuális tagnek megfelelő propertyt állítunk be a setValue: forKey: utasítással. Ezért volt fontos, hogy a Book osztály property nevei megegyezzenek az XML Book tagekkel.

Ez az egész ciklus újra kezdődik, a Book objektum inicializálásával, az attribútuma beolvasásával, és a gyerekei értékének a kiolvasásával, és végül az objektumot a Books tömbhöz adjuk. Ezt a ciklus műveletet addig ismétli a parser, ameddig nincs vége a fájlnak.

A teljes forráskód letölthető innen.