Search code examples
objective-cnsxmlparser

NSXMLParser Stops When It Finds The Very First Instance


I am trying to parse this XML using NSXMLParser. I have multiple instances of "Coffee" in the XML file, like so:

… More XML
<Coffee>
        <Type Name="Type">Minimum Drinking Amount</Type>
            </Coffee>   
<Coffee>
        <Type Name="Type">Maximum Drinking Amount</Type>
            </Coffee>
… More XML

Now, once NSXMLParser finds the first instance of "Coffee", is finished and moves to the rest of the XML. Which... well isn't what I'd like it to do. I need it to read every instance of "Coffee" This is how I'm handling it in Objective-C.

- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict {
    NSLog(@"Fired Stick1");

    _xmlString = elementName;

    if ([_xmlString isEqualToString:@"Coffee"]) {
        NSLog(@"Stick1 Coffee:");
    }

    if ([_xmlString isEqualToString:@"Tea"]) {
        NSLog(@"Stick1 Tea:");
}

Then the foundCharacters delegate:

-(void) parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {

    if ([_xmlString isEqualToString:@"Coffee"] || [_xmlString isEqualToString:@"Tea"]) {
        NSLog@"%@ Called", _xmlString);
     }
    if (!_coffeeString) {
        _coffeeString = [[NSMutableString alloc] initWithString:string];
    }
    else {
        _coffeeString = string;
    }

    if (coffeeArray == NULL) {
        NSLog(@"Stick1 the coffeeArray was null");
        coffeeArray = [[NSMutableArray alloc] init];
    }

    //add the returned string into an array
    [coffeeArray addObject:_coffeeString];
    NSLog(@"Stick1 array %@", coffeeArray);

    // parse the returned data in array
    NSString *seperate = [coffeeArray componentsJoinedByString:@"\n"];
    NSLog(@"Stick1 seperate is %@", seperate);

    // another parsing
    NSArray* words = [seperate componentsSeparatedByCharactersInSet :[NSCharacterSet whitespaceAndNewlineCharacterSet]];
                                                                                            //or use newLineCharecterSet
    // Take out all of the whitespace like tabs and spaces and new lines
    _singleName = [words componentsJoinedByString:@""];

    NSLog(@"Stick1 final result %@", _singleName);
}

Basically, it gets called twice, but doesn't show both sets of data. Once it finds Coffee, it displays the first part of coffee, (the minimum) but fails to display the second instance of coffee (maximum).


Solution

  • I think you have several issues going on. One is that (at least with the supplied XML), <Coffee>...</Coffee> defines an entire XML document, so you're probably getting an unhandled parsing error. You should implement the following code in your NSXMLParserDelegate and see what problems are reported:

    - (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError {
        NSLog(@"error: %@", parseError);
    }
    - (void)parser:(NSXMLParser *)parser validationErrorOccurred:(NSError *)validationError {
        NSLog(@"error: %@", validationError);
    }
    

    You additionally have issues where you're trying to set a trigger/flag in your code based off of the tag name (elementName) of a node, but you actually care about the data contained in a sub-node.

    Further, parser:foundCharacters: is NOT guaranteed to return the entire contents of the tag in one string, so you are a lot better off handling both parser:didStartElement:... and parser:didEndElement:.... Then, in parser:foundCharacters:, you can make sure you're in an appropriate node and append the characters that have been found.

    Here's some sample code to get you started based on the data you've provided:

    NOTE: I'm just taking a shot at what you actually want, since your data is a bit vague. Also, I did this in the App Delegate of a brand new Single View App. This is NOT the way to do a real implementation!

    //  AppDelegate.m
    #import "AppDelegate.h"
    @interface AppDelegate () <NSXMLParserDelegate> {
    
    }
    @property (assign, nonatomic) BOOL               inNode;
    @property (strong, nonatomic) NSMutableArray    *coffeeArray;
    @property (strong, nonatomic) NSMutableString   *coffeeString;
    @property (copy  , nonatomic) NSString          *singleName;
    @property (copy  , nonatomic) NSString          *xmlString;
    @end
    @implementation AppDelegate
    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
        NSXMLParser *parser = [[NSXMLParser alloc] initWithData:[[self xml] dataUsingEncoding:NSUTF8StringEncoding]];
        parser.delegate     = self;
        [parser parse];
        return YES;
    }
    - (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict {
        NSLog(@"Fired Stick1");
    
        self.xmlString = elementName;
    
        if ([self.xmlString isEqualToString:@"Coffee"]) {
            NSLog(@"Stick1 Coffee:");
            self.coffeeString   = [[NSMutableString alloc] init];
            self.inNode          = YES;
        } else if ([self.xmlString isEqualToString:@"Tea"]) {
            NSLog(@"Stick1 Tea:");
            self.coffeeString   = [[NSMutableString alloc] init];
            self.inNode          = YES;
        } else {
            self.inNode          = NO;
        }
    }
    - (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
        if (self.inNode == YES) {
            if ([self.xmlString isEqualToString:@"Coffee"] || [self.xmlString isEqualToString:@"Tea"]) {
                NSLog(@"%@ Called", self.xmlString);
            }
            [self.coffeeString appendString:string];
        }
    }
    - (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
        if ([elementName isEqualToString:@"Coffee"] || [elementName isEqualToString:@"Tea"]) {
            if (self.coffeeArray == nil) {
                NSLog(@"Stick1 the coffeeArray was null");
                self.coffeeArray    = [[NSMutableArray alloc] init];
            }
    
            //add the returned string into an array
            [self.coffeeArray addObject:self.coffeeString];
            NSLog(@"Stick1 array %@", self.coffeeArray);
    
            // parse the returned data in array
            NSString *seperate = [self.coffeeArray componentsJoinedByString:@"\n"];
            NSLog(@"Stick1 seperate is %@", seperate);
    
            // another parsing
            NSArray* words = [seperate componentsSeparatedByCharactersInSet :[NSCharacterSet whitespaceAndNewlineCharacterSet]];
            //or use newLineCharecterSet
            // Take out all of the whitespace like tabs and spaces and new lines
            _singleName = [words componentsJoinedByString:@""];
    
            NSLog(@"Stick1 final result %@", _singleName);
    
        }
    }
    - (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError {
        NSLog(@"error: %@", parseError);
    }
    - (void)parser:(NSXMLParser *)parser validationErrorOccurred:(NSError *)validationError {
        NSLog(@"error: %@", validationError);
    }
    - (NSString *)xml {
        NSMutableString *xml = [[NSMutableString alloc] init];
        [xml appendString:@"<Document>"];
        [xml appendString:@""
         @"<Coffee>"
         @"  <Type Name=\"Type\">"
         @"    Minimum Drinking Amount"
         @"  </Type>"
         @"</Coffee>"
         @"<Coffee>"
         @"  <Type Name=\"Type\">"
         @"    Maximum Drinking Amount"
         @"  </Type>"
         @"</Coffee>\n"];
        [xml appendString:@""
         @"<Coffee>"
         @"  <Type Name=\"Type\">"
         @"    Minimum Drinking Amount"
         @"  </Type>"
         @"</Coffee>"
         @"<Coffee>"
         @"  <Type Name=\"Type\">"
         @"    Maximum Drinking Amount"
         @"  </Type>"
         @"</Coffee>\n"];
        [xml appendString:@""
         @"<Coffee>"
         @"  <Type Name=\"Type\">"
         @"    Minimum Drinking Amount"
         @"  </Type>"
         @"</Coffee>"
         @"<Coffee>"
         @"  <Type Name=\"Type\">"
         @"    Maximum Drinking Amount"
         @"  </Type>"
         @"</Coffee>\n"];
        [xml appendString:@""
         @"<Coffee>"
         @"  <Type Name=\"Type\">"
         @"    Minimum Drinking Amount"
         @"  </Type>"
         @"</Coffee>"
         @"<Coffee>"
         @"  <Type Name=\"Type\">"
         @"    Maximum Drinking Amount"
         @"  </Type>"
         @"</Coffee>\n"];
        [xml appendString:@""
         @"<Coffee>"
         @"  <Type Name=\"Type\">"
         @"    Minimum Drinking Amount"
         @"  </Type>"
         @"</Coffee>"
         @"<Coffee>"
         @"  <Type Name=\"Type\">"
         @"    Maximum Drinking Amount"
         @"  </Type>"
         @"</Coffee>\n"];
        [xml appendString:@""
         @"<Coffee>"
         @"  <Type Name=\"Type\">"
         @"    Minimum Drinking Amount"
         @"  </Type>"
         @"</Coffee>"
         @"<Coffee>"
         @"  <Type Name=\"Type\">"
         @"    Maximum Drinking Amount"
         @"  </Type>"
         @"</Coffee>\n"];
        [xml appendString:@"</Document>"];
        return [xml copy];
    }
    @end