I have a problem when parsing an XML feed. There are approximately 15 child nodes per parent node. Currently I am parsing all nodes using stringbytrimmingchartersinset: then NSCharacterset whitespaceandnewline characterset.
Like so:
- (void) parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
currentElementValue = (NSMutableString *) [string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
}
This works great for all but one of the nodes. For this node I want the text to come through as is. I've tried setting up an if statement to treat this particular node differently than the others so I updated to the following.
- (void) parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
if ([testingString isEqualToString:#"AdoptionSummary"]) {
[currentElementValue appendString:string]; //This keeps breaking
}
else {
currentElementValue = (NSMutableString *) [string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
}
however I'm getting an error.
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Attempt to mutate immutable object with appendString:'
The error only occurs if I use a combination of stringbytrimmingchartersinset and appendString. However if I use either stringbytrimmingchartersinset or appendString on there own it parses without error. currentElementValue is an NSMutableString so why is this error being thrown?
I've tried looking at this NSMutableString appendString generates SIGABRT Error, and this error : 'Attempt to mutate immutable object with appendString:' -- but to no avail.
I would like just 'AdoptionSummary" to append 'as is' while the other 14 are trimmed. What am I doing wrong? Any suggestions?
Thanks!
Its because currentElementValue is declared as NSString. Change its type to NSMutableString.
If you have declared this string with NSMutableString then you will have to change object creation also. Change below code:
currentElementValue = (NSMutableString *) [string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
With this code:
currentElementValue = [[NSMutableString alloc] initWithString:[string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]];
Handle alloc swiftly to avoid memory leak.
currentElementValue = (NSMutableString *) [string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
Simply casting an NSString to an NSMutableString doesn't make it one. Make a mutable copy before modifying it.
Related
I'm parsing an XML document. I have an NSMutableString to hold the value for each node I'm passing through and for specific nodes - I want to update the NewsElement class.
This is my code:
[newsElement setValue:currentElementValue forKey:elementName];
The currentElementValue is the NSMutableString the the elementName is key I want to update. Each field is NSString.
The problem is that instead of copying the string, now my NSString points to the currentElementValue address so all of the fields are always the same...
Presumably copying currentElementValue would fix your problem. Have you tried:
[newsElement setValue:[currentElementValue copy] forKey:elementName];
This will make an immutable copy (i.e. an NSString) of the currentElementValue which is an NSMutableString. If you need the copy to be mutable you could use the mutableCopy method instead, for example:
[newsElement setValue:[currentElementValue mutableCopy] forKey:elementName];
I use SOAP to get Time from website, when executes the following souce code, it raises problem.
I guess the line 4 at below source code has problem, but I don't know how to fix, please help.
Thanks.
Source Code:
-(void) parse:(NSXMLParser *)parser didEndElement:(NSString *) elementName namespaceURI:(NSString *)namespceURI qualifiedName:(NSString *)qName {
if ([elementname isEqualToString:#"getOffesetUTCTimeResult"])
{
greeting.text = [[[NSString init] strinWithFormat:#"The local time is:", nameInput.text] stringByAppending:soapResults];
[soapResults release];
soapResults = nil;
}
}
Raised error:
*** Termination app due to uncaught exception 'NSInvalidArgumentException', reason: '** +[NSString <0x267fd8> init]: cnnot init a class object.'
*** First throw call stack:
...
terminate called throwing an exception
You cannot call the initializer without allocing memory for objects. For NSString, you can do something like:
greeting.text = [[NSString stringWithFormat:#"The local time is:", nameInput.text] stringByAppending:soapResults];
You don't have to init NSString.
Just use
greeting.text = [[NSString strinWithFormat:#"The local time is:", nameInput.text] stringByAppending:soapResults];
All,
I have XML in the following format:
<linked-list>
<Description>
<desc></desc>
<IP></IP>
</Description>
</linked-list>
This XML statement could have an infinite number of <Description></Description> inside of the <linked-list></linked-list>.
How should I parse this using NSXMLParser? My current code is as follows, but it parses incorrectly.
#implementation XMLParser
#synthesize response;
- (XMLParser *) initXMLParser
{
self = [super init];
// init dictionary of response data
response = [[NSMutableDictionary alloc] init];
return self;
}
//Gets Start Element of SessionData
- (void)parser:(NSXMLParser *)parser
didStartElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qualifiedName
attributes:(NSDictionary *)attributeDict
{
if ([elementName isEqualToString:#"linked-list"])
{
NSLog(#"Found linked-list in the return XML! Continuing...");
//response is a NSMutableArray instance variable
//THIS SHOULD NEVER NEED TO BE USED
if (!response)//if array is empty, it makes it!
{
NSLog(#"Dictionary is empty for some reason, creating...");
response = [[NSMutableDictionary alloc] init];
}
//END: THIS SHOULD NEVER BE USED
return;
}
else
{
currentElementName = elementName;
NSLog(#"Current Element Name = %#", currentElementName);
return;
}
}
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
if (!currentElementValue) {
// init the ad hoc string with the value
currentElementValue = [[NSMutableString alloc] initWithString:string];
} else {
[currentElementValue setString:string];
NSLog(#"Processing value for : %#", string);
}
}
//Gets End Element of linked-list
- (void)parser:(NSXMLParser *)parser
didEndElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qName {
if ([elementName isEqualToString:#"linked-list"])
{
// We reached the end of the XML document
// dumps dictionary into log
NSLog(#"Dump:%#", [response description]);
return;
}
else
{
//Adds key and object to dictionary
[response setObject:currentElementValue forKey:currentElementName];
NSLog(#"Set values, going around again... brb.");
}
currentElementValue = nil;
currentElementName = nil;
}
#end
Some observations:
An infinite number of WHAT inside of the WHAT?
Assuming there can be more than one Description element, the outer data structure in which you store the contents must be a NSMutableArray, not a dictionary. You then use one mutable dictionary per Description element.
Consequently, in didStartElement:, check if the element name is #"Description" and if so, create a new NSMutableDictionary instance that you store in an ivar.
In foundCharacters:, you always have to append the new characters to the existing currentElementValue because the method can be called multiple times for each element's contents. I see many people do this wrong despite the fact that Apple's sample code clearly demonstrates the correct way.
In didEndElement:, do this:
If the element name is #"desc" or #"IP", assign currentElementValue to the corresponding key in your current mutable dictionary. Don't forget to release currentElementValue before you set it to nil. You currently have a memory leak in your code because you're not doing that.
If the element name is #"Description", add the current mutable dictionary to the mutable array. Release the dictionary and set the ivar to nil. A new dictionary will be created the next time you encounter a #"Description" element in didStartElement:.
If the element name is #"linked-list", the mutable array will contain all the dictionaries and you're done.
I am new to iPhone/Objective-C development, I am successfully parsing XML with NSXMLParser but I can't get exceptions to work properly. I'd like to use exceptions to deal with unexpected XML.
I'm wrapping the code for creating the NSXMLParser object and sending the setDelegate and parse messages to the object inside a #try #catch block, catching #NSException.
If I put NSAssert(FALSE, #"error) inside the #try block, the exception is caught properly. If, however, I have an NSAssert fail inside the delegate calls (eg, didStartElement, didEndElement, foundCharacters), then the program dies (in iPhone Simulator, haven't tried device yet). The debugger stack trace shows the assertion was raised into an exception but it doesn't pull back out into the top level code where the #try block is around the [parser parse] message call. Instead I get "Terminating app due to uncaught exception."
Please let me know if this is a known problem or if I'm doing something silly here.
Thanks -- Alex
Some code to make more concrete; no attempt to make this code correct for memory/releases/etc.
#implementation XMLTester
+(void)runXMLTester
{
BOOL success = FALSE;
XMLTester *tester = [[XMLTester alloc] init];
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:#"http://api.wunderground.com/auto/wui/geo/WXCurrentObXML/index.xml?query=KSFO"]];
NSXMLParser *parser = [[NSXMLParser alloc] initWithData:data];
[parser setDelegate:tester];
#try {
//NSAssert(FALSE, #"error"); // this assertion works fine
success = [parser parse];
}
#catch (NSException * e) {
success = FALSE;
NSLog(#"Exception caught %#: %#", [e name], [e reason]);
}
#finally {
NSLog(#"runXMLTester #finally block hit");
}
}
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedName attributes:(NSDictionary *)attributeDict
{
NSLog(#"Starting element %#", elementName);
NSAssert(FALSE, #"error"); // this assertion does not work - does not hit #try block around parse message
}
According to Bill Bumgarner, catching exceptions in the iPhone Simulator doesn't work correctly. Your best bet is to stop using exceptions here, as it's not really appropriate anyway. You should be calling -[NSXMLParser abortParsing] instead.
Don't use exceptions for flow control. Writing exception-safe (refcounted) Obj-C code is a bit of a pain — in particular, commonly-used stuff like Foo * foo = [[Foo alloc] init]; [foo doStuff]; [foo release]; foo = nil; will leak and [foo lock]; [foo doStuff]; [foo unlock]; will probably deadlock. You can mitigate the former by always autoreleasing immediately (I always do to prevent memory leaks when refactoring code), except you can't autorelease autorelease pools. The latter is hard to avoid unless you sprinkle #try/#finally everywhere.
Additionally, I strongly recommend breakpointing objc_exception_throw(). Sometimes Xcode seems to miss the throw and drop you into the debugger when abort() is called from uncaught_exception_handler() (or whatever it's called) after the stack's been unhelpfully unwound. And several things (notably CoreAnimation) catch, log, and otherwise ignore exceptions, which is a pain to debug unless you're watching the long.
There's one case in an app where I used an exception for control flow (I think I gave it the name "ControlThrow"); every time I hit that breakpoint I'm tempted to replace it with a goto.
below is my code, Leaks says I am getting a memory leak around NSMutableString alloc method. I am sure it is something I simply overlooked, let me know if anyone has any thoughts. Thanks!
-(void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string{
if (!currentValue) {
currentValue = [[NSMutableString alloc] initWithCapacity:[string length]];
}
[currentValue setString:[string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]];
}
-(void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName{
if([elementName isEqualToString:#"phone"]){
currentAgent.phone = currentValue;
}
[currentValue release];
currentValue = nil;
}
-Agent is a custom object that was created when the class was initialized. The XML is valid and has all the appropriate begin/end tags.
Looking over this code, I think it's more likely that your Agent class is leaking phone. Assuming Agent uses retain for the phone property, this will cause the phone to persist longer than it should.
The creator of the object gets "credited" with the leak, even if the extra retain is somewhere else.
In other words, in Agent:
- (void)dealloc {
self.phone = nil;
// anything else you need to do
[super dealloc];
}