views:

2835

answers:

4

I'm using NSXMLParser to parse XML data from a remote server. I followed this tutorial to get up and running and everything is ok for any (NSString *) members I have in my objects that I'm creating. I also have integers that I need to set from the XML data, such as:

<root>
    <child>
        <name> Hello </name>
        <number> 123 </number>
    </child>
    <child>
        <name> World</name>
        <number> 456 </number>
    </child>
</root>

In this case I would be creating two "child" objects. I'm using:

- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName
      namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
    ...
    [aChild setValue:currentElementValue forKey:elementName];
    ...
}

Delegate to set the values. This is where I get to my problem. The "NSString *name" member is set fine everytime, however if I use an NSInteger, then whenever I try to set "number" I get an EXC_BAD_ACCESS. So I tried using an "int" instead. But now I can't use key-value programming and need to look for the node manually:

if([elementName isEqualToString:@"number"]) {
    aChild.number = [currentElementValue intValue]
}

This is okay, I can deal with that because I know what nodes I'll be getting, but it gets worse. When currentElementValue is an "NSMutableString *" as per the tutorial, it does not return the correct integer even though the string is correct. For instance:

    NSLog(@"NSMutableString Value: %@, and intValue %d\n", currentElementValue, [currentElementValue intValue]); 
// Value will be 123 but intValue will be 0

So I made currentElementValue an NSString instead of an NSMutableString and I can get the proper intValue. But I read online that the reason it is an NSMutableString is because the:

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

Delegate used to set the value can occur more than once, but typically does not. So my question is does anybody know what I'm doing wrong? This seems like a pretty trivial case for NSXMLParser so I'm sure it's something I'm misunderstanding.

Thank you for your help, if anything needs to be more clear please let me know.

+2  A: 

I don't know where your code is failing, but here's the correct way to handle this:

NSMutableString *buffer = [[NSMutableString alloc] init];

- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedName attributes:(NSDictionary *)attributeDict {
    [buffer setString:@""];
}

- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
    if([elementName isEqualToString:@"number"]) {
        [aChild setNumber:[buffer intValue]];
        [buffer setString:@""];
    }
}

- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
    [buffer appendString:string];
}
Can Berk Güder
Hi thanks, do you allocate buffer in the init method and then release it in the dealloc then? I was allocating it in parser:foundCharacters and releaseing it when I was finished in parser:didEndElement.
Sean
Yup. If you allocate in foundCharacters, you will both have a memory leak, and lose data if foundCharacters gets called more than once. Even if it doesn't get called more than once (it does), allocating and releasing a string for every node is overkill.
Can Berk Güder
+2  A: 
  1. intValue should work identically NSString and NSMutableString. Are you sure that Value was '123' and not '\n123' (\n means a new line character), if the string doesn't start with a decimal number then intValue will return 0.

  2. Are you clearing the mutable string correctly at parser:didStartElement:? If you're cleaning only at parser:didEndElement: then parser:foundCharacters: will collect characters from parent element too. Which will prefix your string with newlines in this case and intValue will return 0.

  3. You're correct in that parser:foundCharacters: can be called multiple times for a single element.

mfazekas
I was actually clearing it at the end of parser:didEndElement by doing: [currentElementValue release]; currentElementValue = nil;. I then reallocated it in parser:foundCharacters if it wasn't allocated yet and if it was I was appending the characters to it. Would handling it like Can Berk Guder's
Sean
comment be better?
Sean
Yes you need to clear it at didStartElement too.
mfazekas
Regarding 1: that's only correct if there are non-numeric and non-whitespace characters. intValue for ' 123 ' still returns 123.
Can Berk Güder
+1  A: 

Couple things going on here.

First, you have obvious blanks in your XML character data, e.g.

        <number> 456 </number>

You should really strip out that whitespace. That is likely what is causing the return value of [NSString intValue] to be wrong. If you can remove it at the source, great. If not, you can strip it out on the receiving end by doing:

currentElementValue = [currentElementValue stringByTrimmingCharactersInSet:
    [NSCharacterSet whitespaceAndNewlineCharacterSet]];

The reason you couldn't use key/value is that you can't store an NSInteger value in an NSMutableDictionary. Both keys and values in the dictionary have to descend from NSObject, and NSInteger is (I'm surmising, here) just a platform-safe typedef of int. So you should use an NSNumber instead:

NSNumber *theInt = [NSNumber numberWithInt:[currentElementValue intValue]];
[aChild setObject:theInt forKey:elementName];
Tim Keating
Hi thanks, I'm going to try this this evening after work. The XML example I posted is much different than what I'm using, the 'real' one doesn't have spaces before and after and I have full control over the source. I got it to work with an 'int', but if using NSNumber allows key/value that would be
Sean
great. I was originally allocating the NSMutableString in foundCharacters and releasing after didEndElement as per the tutorial I linked. I changed that per the comments to this question and now [.. intValue] does return an integer, but I think an NSNumber would be better. Thanks again.
Sean
A: 

Hi! I have a simular problem and I have followed the same tutorial as Sean, as it seems ;-) My problem is that when printing out the values that I store into the object (in my case the object is a car) always remains null. I save all the objects into an MutableArray an it has the length/count 0...what am I doing wrong? The array is, as can be seen in the code, defined in the delegate class...

- (XMLParser *) initXMLParser {
    [super init];
    appDelegate = (Car2GoAppDelegate *) [[UIApplication sharedApplication]delegate];
    currentElementValue = [[NSMutableString alloc]init];
    return self;
}


- (void) parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedName attribute:(NSDictionary *)attributeDict {   
    if([elementName isEqualToString:@"kml"]) {
     appDelegate.cars = [[NSMutableArray alloc]init];
    }
    else if([elementName isEqualToString:@"Placemark"]) {
     aCar = [[Car alloc]init];
    }
    else
    return;
}


- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *) string {
    [currentElementValue appendString:string];

}


- (void) parser:(NSXMLParser *)parser didEndElement:(NSString *) elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
    if([elementName isEqualToString:@"kml"])
     return;
    if([elementName isEqualToString:@"Style"])
     return;
    if([elementName isEqualToString:@"IconStyle"])
     return;
    if([elementName isEqualToString:@"Icon"])
     return;
    if([elementName isEqualToString:@"color"])
     return;
    if([elementName isEqualToString:@"colorMode"])
     return;
    if([elementName isEqualToString:@"scale"])
     return;
    if([elementName isEqualToString:@"href"])
     return;
    if([elementName isEqualToString:@"styleUrl"])
     return;
    if([elementName isEqualToString:@"a"])
     return;

    if([elementName isEqualToString:@"Placemark"]) {
     [appDelegate.cars addObject:aCar];
     [aCar release];
     aCar = nil;
    }

    else if([elementName isEqualToString:@"name"]) {
     [aCar setValue:currentElementValue forKey:elementName];
    }

    else if([elementName isEqualToString:@"description"]) {
     [aCar setValue:currentElementValue forKey:elementName];
     [currentElementValue setString:@""];
    }
    else if([elementName isEqualToString:@"coordinates"]) {
     [aCar setValue:currentElementValue forKey:elementName];
     [currentElementValue setString:@""];
     NSLog(@"aCar coordinates:%@", [aCar coordinates]);
    }
    else
     return;
}

- (void) dealloc {
    [aCar release];
    [currentElementValue release];
    [super dealloc];
}

@end

/loop

loop