Notez qu'il pourrait être plus facile de baser votre application sur Core Data ou NSXML (en utilisant un init method of NSXMLDocument
approprié) plutôt que d'assumer la responsabilité de l'analyse des fichiers XML. Si vous souhaitez le faire, lisez la suite. L'utilisation de différents noms d'éléments pour les éléments de sous-zone corrigera le problème de clobbing.
<book>
<title>Book 1</title>
<author>
<first> Jason </first>
<last> Alfonso. </last>
</author>
<summary>
<city> Milano </city>
<country> Italy </country>
</summary>
</book>
Cependant, il existe encore de meilleurs moyens.
De manière générale, pour analyser correctement un fichier XML, vous devez gérer une pile d'éléments en cours de traitement. Lorsque l'analyse d'un élément commence, vous créez un nouvel élément et l'ajoutez à la pile. Lorsque l'analyse d'un élément se termine, vous enlevez un élément de la pile et vous le donnez à l'élément situé en haut de la pile. Vous pouvez créer des éléments de différentes classes en fonction du nom de l'élément en utilisant une méthode usine (ci-dessous, -nodeWithTag:attributes:parser:
) et un dictionnaire qui mappe les noms des éléments aux classes (ci-dessous, 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];
}
Un autre bug
Jetez un oeil près:
} 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];
}
}
Si le premier test réussit, vous ne serez jamais atteindre la seconde. Le correctif trivial ici est de combiner les blocs, bien que cela aura encore la question clobber:
} 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];
}
}
Afficher le code que vous avez essayé. Voir ["Gestion des éléments et attributs XML"] (http://developer.apple.com/library/ios/ipad/#documentation/cocoa/Conceptual/XMLParsing/Articles/HandlingElements.html) dans les documents Apple pour une bonne explication et un exemple de code. – Anna
au moins afficher du code. Apple a plusieurs exemples pour traiter avec XML, qui avez-vous revue? –
Pour en savoir plus sur les questions sur SO, lisez ["Rédaction de la question parfaite"] (http://tinyurl.com/so-hints) et ["Le court, autonome, correct (compilable), exemple"] (http : // sscce.org /). – outis