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.