Hinweis getrennt werden, dass es einfacher sein könnte, um Ihre Anwendung auf Core Data oder NSXML (unter Verwendung eines geeigneten init method of NSXMLDocument
) zu stützen, anstatt Verantwortung zu übernehmen, die XML-Dateien für die Analyse. Wenn Sie dies wünschen, lesen Sie weiter.
Durch die Verwendung verschiedener Elementnamen für die Unterfeldelemente wird das Problem der Clobberbildung behoben.
<book>
<title>Book 1</title>
<author>
<first> Jason </first>
<last> Alfonso. </last>
</author>
<summary>
<city> Milano </city>
<country> Italy </country>
</summary>
</book>
Allerdings gibt es noch bessere Möglichkeiten.
Um eine XML-Datei korrekt analysieren zu können, müssen Sie im Allgemeinen einen Stapel von Elementen in Bearbeitung halten. Wenn das Parsen eines Elements beginnt, erstellen Sie ein neues Element und fügen es dem Stapel hinzu. Wenn das Parsen eines Elements abgeschlossen ist, blenden Sie ein Element aus dem Stapel und geben es dem Element jetzt oben auf dem Stapel. Sie können Elemente verschiedener Klassen basierend auf dem Elementnamen erstellen, indem Sie eine Factory-Methode (unter -nodeWithTag:attributes:parser:
) und ein Wörterbuch verwenden, das Elementnamen Klassen zuordnet (unten, 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];
}
Ein weiterer Fehler
Werfen Sie einen Blick auf:
} 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];
}
}
Wenn der erste Test erfolgreich ist, werden Sie nie die zweite erreichen. Die triviale fix hier ist es, die Blöcke zu kombinieren, obwohl dies immer noch die clobber Problem haben:
} 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];
}
}
Zeigen Sie den Code, den Sie ausprobiert haben. Weitere Informationen finden Sie unter ["Umgang mit XML-Elementen und -Attributen"] (http://developer.apple.com/library/ios/ipad/#documentation/cocoa/Conceptual/XMLParsing/Articles/HandlingElements.html) in den Apple-Dokumenten und Beispielcode. – Anna
zumindest Code anzeigen. Apple hat mehrere Beispiele für den Umgang mit XML, die Sie überprüft haben? –
Für weitere Fragen zu SO, lesen Sie ["Die perfekte Frage schreiben"] (http://tinyurl.com/so-hints) und ["Die kurze, unabhängige, korrekte (kompilierbar), Beispiel"] (http : // sscce.org /). – outis