Обратите внимание, что может быть проще основать ваше приложение на Базовые данные или NSXML (используя соответствующий метод инициализации NSXMLDocument
), чем принимать ответственность за разбор XML-файлов. Если вы хотите это сделать, читайте дальше.
Использование разных имен элементов для элементов подполей решит проблему с залипанием.
<book>
<title>Book 1</title>
<author>
<first> Jason </first>
<last> Alfonso. </last>
</author>
<summary>
<city> Milano </city>
<country> Italy </country>
</summary>
</book>
Однако есть и лучшие способы.
Вообще говоря, для правильного анализа XML-файла вам потребуется поддерживать стек элементов в процессе. Когда начинается анализ элемента, вы создаете новый элемент и добавляете его в стек. Когда анализ элемента завершается, вы вытаскиваете элемент из стека и отдаете его элементу, находящемуся на вершине стека. Вы можете создавать элементы разных классов на основе имени элемента, используя фабричный метод (ниже, -nodeWithTag:attributes:parser:
) и словарь, который отображает имена элементов в классы (ниже, elementClasses
).
/* category to return a default object (rather than nil)
when a key isn't present in a dictionary.
*/
@interface NSDictionary (defaultObject)
-(id)objectForKey:(NSString*)key default:(id)default;
@end
@implementation NSDictionary (defaultObject)
-(id)objectForKey:(NSString*)key default:(id)default {
id object = [self objectForKey:key];
if (nil == object) {
return default;
}
return object;
}
@end
/* category to add aliases for stack operations
to NSMutableArray
*/
@interface NSMutableArray (stack)
-(void)push:(id)object;
-(id)pop;
-(id)top;
@end
@implementation NSMutableArray (stack)
// could also use class_addMethod to alias push & top
-(void)push:(id)object {
[self addObject:object];
}
-(id)pop {
id last = [self lastObject];
[self removeLastObject];
return last;
}
-(id)top {
return [self lastObject];
}
@end
// the parser delegate.
@interface ... <NSXMLParserDelegate> {
NSMutableArray activeElements;
id item;
...
@property (nonatomic,retain) item;
@end
@implementation ...
@synthesize item;
#pragma mark Class members
// map element names to classes
static NSDictionary *elementClasses;
+(void)initialize {
nodeTypes=[[NSDictionary alloc] initWithObjectsAndKeys:
// Just an illustrative example of a custom class.
// You don't necessarily need a Book class.
[Book class],@"book",
nil];
}
// if you have other init methods, make sure activeElements is created.
-(id)init {
if ((self = [super init])) {
activeElements = [[NSMutableArray alloc] init];
...
}
return self;
}
-(void)parserDidStartDocument:(NSXMLParser *)parser {
// add sentinel element so stack isn't empty at start.
[activeElements push:[self nodeWithTag:@"root" attributes:nil parser:parser]];
}
-(void)parserDidEndDocument:(NSXMLParser *)parser {
// The parser should ensure only case 1 is reachable, but still...
switch ([activeElements count]) {
case 0:
NSLog(@"Root element removed from stack early.");
break;
default:
NSLog(@"Extra elements in stack at parse end.");
[activeElements removeObjectsInRange:NSMakeRange(1, activeElements.count-1)];
// FALLTHRU
case 1:
// top item should be the sentinel
self.item = [activeElements pop];
if ([item.children count] == 1) {
// sentinel can safely be discarded if
self.item = [item.children objectAtIndex:0];
}
break;
}
}
#pragma mark Instance methods
-(void) parser:(NSXMLParser *)parser
didStartElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qName
attributes:(NSDictionary *)attributeDict
{
[activeElements push:[self nodeWithTag:elementName
attributes:attributeDict
parser:parser]];
}
- (void)parser:(NSXMLParser *)parser
didEndElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qName
{
id element = [activeElements pop];
if (element.attributes.count == 0 && element.children.count == 0) {
// simple leafs don't need to be Nodes.
[activeElements.top setValue:element.value forKey:elementName];
} else {
[activeElements.top setValue:element forKey:elementName];
}
}
-(void)parser:(NSXMLParser*)parser foundCharacters:(NSString*)string {
activeElements.top.value = string;
}
/* Factory method. Depending on elementName, create an
object of the appropriate type.
*/
-(id)nodeWithTag:elementName attributes:attrs parser:(NSXMLParser*)parser {
id node =[[[elementClasses objectForKey:elementName
default:[NSMutableDictionary class]]
alloc] init];
for (id key in attrs) {
@try {
[node setValue:[attrs objectForKey:key] forKey:key];
}
@catch (NSException *exc) {
// TODO: warn user of invalid attribute(s) when parsing is finished
if ([exc name] == NSUndefinedKeyException) {
NSLog(@"%d,%d: Set attribute '%@' on a %@, but it doesn't have that property.",
[parser columnNumber], [parser lineNumber],
key, elementName);
} else {
NSLog(@"%d,%d: Caught %@ when setting %@ on a %@.",
[parser columnNumber], [parser lineNumber],
[exc name], key, elementName);
}
}
}
return [node autorelease];
}
-(void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError {
NSLog(@"parse error: %@", parseError);
[self abort];
}
-(void)parser:(NSXMLParser *)parser validationErrorOccurred:(NSError *)validError {
NSLog(@"validation error: %@", validError);
[self abort];
}
-(void)abort {
[activeElements removeAllObjects];
}
Еще одна ошибка
Внимательно посмотрите на:
} else if ([elementName isEqualToString:@"subfield"]) {
if ([[attributeDict valueForKey:@"id"] isEqualToString:@"a"]) {
self.authorName1 = [[NSMutableString alloc] init];
}
} else if ([elementName isEqualToString:@"subfield"]) {
if ([[attributeDict valueForKey:@"id"] isEqualToString:@"b"]) {
self.authorName2 = [[NSMutableString alloc] init];
}
}
Если первый тест пройдёт успешно, вы никогда не достигнете второго. Тривиальное исправление здесь заключается в объединении блоков, хотя это все равно будет иметь проблему с клопом:
} else if ([elementName isEqualToString:@"subfield"]) {
if ([[attributeDict valueForKey:@"id"] isEqualToString:@"a"]) {
self.authorName1 = [[NSMutableString alloc] init];
} else if ([[attributeDict valueForKey:@"id"] isEqualToString:@"b"]) {
self.authorName2 = [[NSMutableString alloc] init];
}
}