Книга: iOS. Приемы программирования

Обсуждение

Обсуждение

Для синтаксического разбора XML-содержимого класс NSXMLParser использует делегат. Создадим простой XML-файл, содержащий следующие данные (сохраните этот файл в вашем проекте как MyXML.xml):

<?xml version="1.0" encoding="UTF-8"?>
<root>
<person>
<firstName>Anthony</firstName>
<lastName>Robbins</lastName>
<age>51</age>
</person>
<person>
<firstName>Richard</firstName>
<lastName>Branson</lastName>
<age>61</age>
</person>
</root>
Теперь определим свойство типа NSXMLParser:
#import «AppDelegate.h»
@interface AppDelegate () <NSXMLParserDelegate>
@property (nonatomic, strong) NSXMLParser *xmlParser;
@end
@implementation AppDelegate

Кроме того, как видите, я определил делегат моего приложения как делегат XML-парсера, который подчиняется протоколу NSXMLParserDelegate. Согласно этому протоколу, объект делегата XML-парсера должен относиться к типу NSXMLParser. Cчитаем с диска файл MyXML.xml и передадим его на обработку в XML-парсер:

— (BOOL) application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{
NSString *xmlFilePath = [[NSBundle mainBundle] pathForResource:@"MyXML"
ofType:@"xml"];
NSData *xml = [[NSData alloc] initWithContentsOfFile: xmlFilePath];
self.xmlParser = [[NSXMLParser alloc] initWithData: xml];
self.xmlParser.delegate = self;
if ([self.xmlParser parse]){
NSLog(@"The XML is parsed.");
} else{
NSLog(@"Failed to parse the XML");
}
self.window = [[UIWindow alloc] initWithFrame:
[[UIScreen mainScreen] bounds]];
self.window.backgroundColor = [UIColor whiteColor];
[self.window makeKeyAndVisible];
return YES;
}

Сначала считываем содержимое файла в экземпляр NSData, а потом инициализируем XML-парсер с помощью метода initWithData:, используя данные, считанные из XML-файла. Затем вызываем метод parse XML-парсера, чтобы запустить процесс синтаксического разбора. Этот метод заблокирует актуальный поток до тех пор, пока синтаксический разбор не завершится. Если вам требуется произвести синтаксический разбор больших XML-файлов, используйте для этого глобальную диспетчерскую очередь.

Для синтаксического разбора XML-файла необходимо знать методы делегатов, определенные в протоколе NSXMLParserDelegate, а также понимать, за что они отвечают:

• parserDidStartDocument: — вызывается при запуске синтаксического разбора;

• parserDidEndDocument: — вызывается по окончании синтаксического разбора;

• parser: didStartElement: namespaceURI: qualifiedName: attributes: — вызывается, когда парсер встречает и начинает разбирать новый элемент в XML-документе;

• parser: didEndElement: namespaceURI: qualifiedName: — вызывается, когда парсер завершает синтаксический разбор текущего элемента;

• parser: foundCharacters: — вызывается, когда парсер анализирует строковое содержимое элементов.

С помощью этих методов делегата можно определить объектную модель для XML-объектов. Сначала определим объект, который будет представлять XML-элемент. Сделаем это в классе XMLElement:

#import <Foundation/Foundation.h>
@interface XMLElement: NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSString *text;
@property (nonatomic, strong) NSDictionary *attributes;
@property (nonatomic, strong) NSMutableArray *subElements;
@property (nonatomic, weak) XMLElement *parent;
@end
Теперь реализуем класс XMLElement:
#import «XMLElement.h»
@implementation XMLElement
— (NSMutableArray *) subElements{
if (subElements == nil){
subElements = [[NSMutableArray alloc] init];
}
return subElements;
}
@end

Мы хотим, чтобы изменяемый массив subElements создавался лишь тогда, когда при достижении этой точки в коде мы имеем значение nil. Поэтому код для выделения и инициализации свойства subElements класса XMLElement поместим в его собственном методе-получателе. Если у XML-элемента нет дочерних элементов, то использовать это свойство не придется. Ведь отсутствует точка, в которой можно было бы выделить и инициализировать изменяемый массив для данного элемента. Такая техника называется «ленивое выделение» (Lazy Allocation).

Итак, продолжим. Определим экземпляр XMLElement и назовем его rootElement. Наш план — начать синтаксический разбор и подробно изучить XML-файл по мере разбора его и методов его делегата, пока не рассмотрим весь файл целиком:

#import «AppDelegate.h»
#import «XMLElement.h»
@interface AppDelegate () <NSXMLParserDelegate>
@property (nonatomic, strong) UIWindow *window;
@property (nonatomic, strong) NSXMLParser *xmlParser;
@property (nonatomic, strong) XMLElement *rootElement;
@property (nonatomic, strong) XMLElement *currentElementPointer;
@end
@implementation AppDelegate

currentElementPointer будет соответствовать тому XML-элементу, который мы в данный момент разбираем в XML-структуре. В ходе синтаксического разбора можно будет перемещаться по этой структуре вверх и вниз. В отличие от постоянно изменяющегося указателя currentElementPointer, rootElement всегда будет оставаться корневым элементом XML-файла и его значение не изменится в ходе синтаксического разбора данного файла.

Начнем синтаксический разбор. Первый элемент, который нас интересует, — это метод parserDidStartDocument:. В нем мы просто сбрасываем все значения:

— (void)parserDidStartDocument:(NSXMLParser *)parser{
self.rootElement = nil;
self.currentElementPointer = nil;
}

Следующий метод называется parser: didStartElement: namespaceURI: qualifiedName: attributes:. В этом методе создадим корневой элемент (если он еще не создан). Когда в XML-файле начинается разбор любого нового элемента, мы вычисляем, где именно в структуре XML-файла находимся, а потом добавляем новый элемент-объект к актуальному элементу-объекту:

— (void) parser:(NSXMLParser *)parser
didStartElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qName
attributes:(NSDictionary *)attributeDict{
if (self.rootElement == nil){
/* У нас нет корневого элемента. Создадим такой элемент
и укажем на него. */
self.rootElement = [[XMLElement alloc] init];
self.currentElementPointer = self.rootElement;
} else {
/* Корневой элемент уже есть. Создаем новый элемент и добавляем его
в качестве одного из дочерних элементов текущего элемента. */
XMLElement *newElement = [[XMLElement alloc] init];
newElement.parent = self.currentElementPointer;
[self.currentElementPointer.subElements addObject: newElement];
self.currentElementPointer = newElement;
}
self.currentElementPointer.name = elementName;
self.currentElementPointer.attributes = attributeDict;
}

Теперь перед нами метод parser: foundCharacters:. Для каждого текущего элемента этот метод может вызываться несколько раз, поэтому необходимо гарантировать, что мы сможем сделать несколько записей в этом методе. Например, если текст элемента имеет 4000 символов в длину, то парсер может разобрать не более 1000 символов за первый ход, еще 1000 — за второй и т. д. В таком случае синтаксический анализатор вызовет метод parser: foundCharacters: для данного элемента четыре раза. Вероятно, вам потребуется просто аккумулировать результаты и вернуть их в виде строки:

— (void) parser:(NSXMLParser *)parser
foundCharacters:(NSString *)string{
if ([self.currentElementPointer.text length] > 0){
self.currentElementPointer.text =
[self.currentElementPointer.text stringByAppendingString: string];
} else {
self.currentElementPointer.text = string;
}
}

Следующий метод, с которым необходимо разобраться, называется parser: didEndElement: namespaceURI: qualifiedName:. Он вызывается, когда парсер доходит до конца элемента. Здесь нам нужно просто вернуть указатель XML-элементов на уровень выше, к тому элементу, который является родительским для только что проанализированного. Все довольно просто:

— (void) parser:(NSXMLParser *)parser
didEndElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qName{
self.currentElementPointer = self.currentElementPointer.parent;
}

И последний, но немаловажный момент. Нужно также обработать метод parserDidEndDocument: и избавиться от текущего свойства currentElementPointer:

— (void)parserDidEndDocument:(NSXMLParser *)parser{
self.currentElementPointer = nil;
}

Вот и все. Теперь можете выполнить синтаксический разбор нашего документа:

— (BOOL) application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{
NSString *xmlFilePath = [[NSBundle mainBundle] pathForResource:@"MyXML"
ofType:@"xml"];
NSData *xml = [[NSData alloc] initWithContentsOfFile: xmlFilePath];
self.xmlParser = [[NSXMLParser alloc] initWithData: xml];
self.xmlParser.delegate = self;
if ([self.xmlParser parse]){
NSLog(@"The XML is parsed.");
/* self.rootElement сейчас является корневым элементом XML-документа. */
} else{
NSLog(@"Failed to parse the XML");
}
self.window = [[UIWindow alloc] initWithFrame:
[[UIScreen mainScreen] bounds]];
self.window.backgroundColor = [UIColor whiteColor];
[self.window makeKeyAndVisible];
return YES;
}

Теперь можно использовать свойство rootElement для обхода всей структуры нашего XML-документа.

Оглавление книги

Оглавление статьи/книги

Генерация: 0.052. Запросов К БД/Cache: 0 / 0
поделиться
Вверх Вниз