Dzisiaj post o tym jak sparsować XMLa na iPhonie. Przykład będzie prosty by pokazać zasadę działania ;) Ale za to będzie gotowa klasa do wykorzystania w dowolnym projekcie.
Wstęp
Ze względu na to, że poniższy post ma być tylko wstępem do parsowania, przykładowy XML jest krótki i ma formę jak poniżej:
<list> <link>http://sparhawk.pl</link> <link>http://twoja_strona.pl</link> </list>
Od razu zaznaczam, że osoby które są przyzwyczajone do programowania w innych językach, będą musiały pozbyć się niektórych nawyków. Tu mamy coś co nazywa się Event-Driven XML Programing. Co to jest? To parsowanie XMLa w oparciu o zdarzenia, o których jesteśmy informowani i na nie reagujemy.
Plik nagłówkowy parsera
#import #import "Parser.h" @interface OrderListParser : NSObject { NSMutableString *_contentOfCurrentProperty; NSMutableArray *_parameters; UIViewController *_controller; } @property (nonatomic, retain) NSMutableString *contentOfCurrentProperty; @property (nonatomic, retain) NSMutableArray *parameters; @property (nonatomic, retain) UIViewController *controller; - (void)parseXML:(NSMutableData *)data controller:(UIViewController *)contr parseError:(NSError **)error; @end
W mojej klasie, którą wykorzystuje w jakimś kontrolerze zdefiniowałem parę property
(o których później), a także metodę którą wykorzystuję, w kontrolerze do wywołania parsera i tak:
- (void)parseXML:(NSMutableData *)data controller:(UIViewController *)contr parseError:(NSError **)error;
Pierwszy parametr to treść XMLa do parsowania. Drugi to kontroler do, którego zwrócę wyniki. Ostatni można wykorzystać do przekazania parametrów błędów.
Implementacja parsera
Na początek implementacja metody tworzącej parser.
#import "ExampleListParser.h" #import "ExampleListViewController.h" @implementation ExampleListParser @synthesize contentOfCurrentProperty = _contentOfCurrentProperty; @synthesize parameters = _parameters; @synthesize controller = _controller; - (void)parseXML:(NSMutableData *)data controller:(UIViewController *)contr parseError:(NSError **)error {
Powołanie obiektu parsera do życia:
NSXMLParser *parser = [[NSXMLParser alloc] initWithData:data];
Ustawienie obiektu, który będzie implementował metody delegujące:
[parser setDelegate:delegator];
Poniższe nazwy metod mówią same za siebie, a jeżeli nie odsyłam do dokumentacji klasy NXSMLParser. Nasz XML jest na tyle prosty, że nie ma namespaców.
[parser setShouldProcessNamespaces:NO]; [parser setShouldReportNamespacePrefixes:NO]; [parser setShouldResolveExternalEntities:NO];
Ustawienie kontrolera, do którego będą przekazane dane po sparsowaniu
self.controller = contr; // parsowanie [parser parse];
Obsługa błędów parsera. Można ją rozbudować w zależności od potrzeb:
NSError *parseError = [parser parserError]; if (parseError && error) { *error = parseError; } // zwolnienie obiektu parsera [parser release]; }
Początek parsowania
Po uruchomieniu parsera zostaje nam tylko odpowiednio zaimplementować metody delegowane tak by odpowiednio tworzyły obiekty. Pierwszą jaką musimy zaimplementować jest reakcja na nowo napotkany węzeł:
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict {
Jeżeli otrzymaliśmy kwalifikowana nazwę to ją przyjmij jako podstawową:
if (qName) { elementName = qName; }
Jeżeli trafisz na korzeń to stwórz tablicę dla linków:
if ([elementName isEqualToString:@"list"]) { self.parameters = [NSMutableArray array]; return; }
Jeżeli obecnie znalezionym elementem jest link, to przygotuj obiekt NSMutableString
na treść linka
if([elementName isEqualToString:@"link"]) { self.contentOfCurrentProperty = [NSMutableString string]; } else { // w innym wypadku wyczyść zawartość self.contentOfCurrentProperty = nil; } }
Opis parametrów metody:
- parser – obiekt parsera
- elementName – nazwa elementu w naszym xmlu to np. list, link
- namespaceURI – gdy włączona jest obsługa namespace, tu przekazany jest URI obecnego namespace
- qualifiedName – gdy włączona jest obsługa namespace, jest to nazwa kwalifikowana
- attributeDict – gdy element ma atrybuty, przekazywane one są w postaci słownika (NSDictionary), gdzie klucz to nazwa atrybutu, a wartość to wartość atrybutu
Powyższa metoda będzie wywoływana za każdym razem, jak parser natrafi na znacznik otwierający. Dla zagłębionych elementów będziemy musieli tu odpowiednio tworzyć obiekty (tablice, słowniki, itp.).
Stworzenie tablicy dla linków można by też zaimplementować w metodzie, która jest wywoływana na początku parsowania: - (void)parserDidStartDocument:(NSXMLParser *)parser
.
Trafiliśmy na znacznik zamykający
Gdy parser natrafia na znacznik zamykający wywołuje poniższą metodę:
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName { if (qName) { elementName = qName; }
Jeżeli trafiłem na link to dodaj go do tablicy, self.parameters
. Treść linka jest uzupełniania przez metodę parser:foundCharacters
:
if([elementName isEqualToString:@"link"]) { [self.parameters addObject:self.contentOfCurrentProperty]; return; } }
Parametry powyższej metody tej analogiczne do parser:didStartElement
.
Pozostałe metody
Zostały jeszcze dwie metody. Pierwsza to reakcja na znaleziony znak, a druga to zakończenie parsowania.
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string { // jeżeli znalazłeś znak to dołącz go do obecnej zawartości znacznika if (self.contentOfCurrentProperty) { [self.contentOfCurrentProperty appendString:string]; } } - (void)parserDidEndDocument:(NSXMLParser *)parser { // wywolaj metodę didEndParse na kontrolerze wraz // ze sparsowanymi elementami [self.controller didEndParse:self.parameters]; }
W moim przykładzie, gdy skończy się parsowanie wywołuje metodę na kontrolerze, który sobie przekazałem wraz z treścią XMLa. Każdy może sobie zrobić tu inną dowolną akcję, w tym wsadzić Parser bezpośrednio do kontrolera. Ja wolałem mieć parser poza właściwym kontrolerem.
Ostateczna implementacja
Zamieszczam ostateczny kształt pliku .m
#import "ExampleListParser.h" #import "ExampleListViewController.h" @implementation ExampleListParser @synthesize contentOfCurrentProperty = _contentOfCurrentProperty; @synthesize parameters = _parameters; @synthesize controller = _controller; - (void)parseXML:(NSMutableData *)data controller:(UIViewController *)contr parseError:(NSError **)error { NSXMLParser *parser = [[NSXMLParser alloc] initWithData:data]; [parser setDelegate:delegator]; [parser setShouldProcessNamespaces:NO]; [parser setShouldReportNamespacePrefixes:NO]; [parser setShouldResolveExternalEntities:NO]; self.controller = contr; [parser parse]; NSError *parseError = [parser parserError]; if (parseError && error) { *error = parseError; } [parser release]; } - (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict { if (qName) { elementName = qName; } if ([elementName isEqualToString:@"list"]) { self.parameters = [NSMutableArray array]; return; } if([elementName isEqualToString:@"link"]) { self.contentOfCurrentProperty = [NSMutableString string]; } else { self.contentOfCurrentProperty = nil; } } - (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName { if (qName) { elementName = qName; } if([elementName isEqualToString:@"link"]) { [self.parameters addObject:self.contenOfCurrentProperty]; return; } } - (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string { if (self.contentOfCurrentProperty) { [self.contentOfCurrentProperty appendString:string]; } } - (void)parserDidEndDocument:(NSXMLParser *)parser { [self.controller didEndParse:self.parameters]; } @end
Podsumowanie
Parsowanie XMLa na iPhonie to typowe Event-Driven Development wg. Apple ;) Ma to swoje zalety i wady. Do zalet można zaliczyć, że już na etapie parsowania, można odrzucać nie interesujące nas dane lub je wstępnie przetwarzać. Wady to trochę inne podejście rozrost ilości metod.
Ostatnia uwaga pamiętaj, że na telefonie parsowanie może przy dużych XMLach chwilę trwać i zżera zasoby;)
Najnowsze komentarze