views:

600

answers:

3

hello all, I'm parsing an xml file and i've been trying to strip out the whitespace characters in my currentElementValue because it's messing up a couple of things.

I can see in my output window that a couple of carriage returns and tabs are present

(gdb) po string
Keep the arms on the side, and lift your leg.
(gdb) po currentElementValue



     Keep the arms on the side, and lift your leg.
(gdb)

This is my foundCharacters function and I've been trying to use stringByTrimmingCharactersInSet unfortunately with no success.

- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string { 
    if(!currentElementValue) 
     currentElementValue = [[NSMutableString alloc] initWithString:string];
    else
    {  

     [currentElementValue appendString:string];

     currentElementValue = [currentElementValue stringByTrimmingCharactersInSet:
             [NSCharacterSet newlineCharacterSet]];  
      NSString *instructions = @"instructions"; 
     [directions setValue:string forKey:instructions];  //mm 
     [appDelegate.directions setValue:string forKey:instructions];
     //[appDelegate.directions setObject:string forKey:currentElementValue];
    // [appDelegate
    }
}

I've been getting this error *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Attempt to mutate immutable object with appendString:' Which is strange since my currentElementValue is a NSMutableString .. So what's going wrong ? Does anyone have a clue or idea ?

+4  A: 

Let's step through this, find your bug, and solve a memory leak:

First, you create an NSMutableString. Great. (+1 retain count)

Then you append another string onto your NSMutableString. That's fine. (still +1 retain count).

Then you trim the newlineCharacterSet, which returns... an autoreleased NSString. Since this object is different from your original object, you've leaked your original object (since it had a +1 retain count and you no longer have a pointer to it), and you now have an immutable NSString to boot. This means that the next time this method gets called, you're going to try to append a string onto an NSString, which will throw the "can't mutate an immutable object" exception.

Here's the quick way to solve this:

- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string { 
    if(!currentElementValue) 
        currentElementValue = [[NSMutableString alloc] initWithString:string];
    else
    {           

        [currentElementValue appendString:string];

        NSString *trimmedString = [currentElementValue stringByTrimmingCharactersInSet:[NSCharacterSet newlineCharacterSet]];
        [currentElementValue setString:trimmedString];

        NSString *instructions = @"instructions";       
        [directions setValue:string forKey:instructions];  //mm 
        [appDelegate.directions setValue:string forKey:instructions];
        //[appDelegate.directions setObject:string forKey:currentElementValue];
    //  [appDelegate
    }
}

(save the trimmed string to a different variable, then use NSMutableString's setString: method to transfer the contents in, but without losing your pointer to your NSMutableString)

Dave DeLong
aha thanks man... super explanation.. I did put the trimmedString in the if statement though else the trimming doesn't work but this one is perfect thanks.
jovany
I filed a Radar requesting `-[NSMutableString trimCharactersInSet:]` -- please reference rdar://7230868 if you file a duplicate bug report.
Quinn Taylor
A: 

By the way ... I figured that NSXMLParser should provide settings for handling whitespace, i.e., "preserve whitespace" in the W3.org specification for XML / XSLT -- besides, I don't like to have to get the set of characters right for something like trim. Basically, I wanted to have access to "@text()" as I would in XML/XMSLT transformation. So, I did some digging and found this method. Notice, that one can append the whitespace string to the accumulated text. At first attempt it is not working, but I have more experimenting to do and double-checking my syntax (i.e., cut 'n paste skills :))

- (void)parser:(NSXMLParser *)parser foundIgnorableWhitespace:(NSString *)whitespaceString

Parameters: parser A parser object.

whitespaceString A string representing all or part of the ignorable whitespace characters of the current element.

Discussion All the whitespace characters of the element (including carriage returns, tabs, and new-line characters) may not be provided through an individual invocation of this method. The parser may send the delegate several parser:foundIgnorableWhitespace: messages to report the whitespace characters of an element. You should append the characters in each invocation to the current accumulation of characters.

mobibob
I found a discussion that I think is relevant. I will ask the question of the forum after I post this important statement"Are you using the HTML DOM or the XHTML DOM? The 'include-ignorable-whitespace' feature applies *only* to the latter. Since HTML isn't validated, there's no way for the parser to know what whitespace is ignorable. As such, the parser makes no attempt to remove whitespace, because without the DTD telling it what to remove, any attempt may remove important whitespace."
mobibob
Here is the reference to the above quote, http://osdir.com/ml/java.enhydra.xmlc/2007-05/msg00019.html
mobibob
A: 

I found a clean way to work with node text. It assumes that you have ownership of the XML content/format. What I am now doing is using the CDATA section for my node's "text()" attribute. This does not include the whitespace that I would use to tabulate my xml document.

The code to read is like this:

- (void)parser:(NSXMLParser *)parser foundCDATA:(NSData *)CDATABlock {
NSLog(@"%s", __FUNCTION__);
NSLog(@"[%@]", CDATABlock);

NSString *strData = [[NSString alloc] initWithData:CDATABlock encoding: NSUTF8StringEncoding ];

NSLog(@"convertToString=[%@]", strData);

}

and the XML fragment is like this (tutorial with 'famous quotes'):

<quote><![CDATA[And in the end...in your years.]]></quote>

and the trace output is like this:

2009-11-21 13:17:51.901 Fate[2241:4203] -[XMLReader parser:foundCDATA:]
2009-11-21 13:17:51.901 Fate[2241:4203] [<416e6420 696e2074 68652065 6e642c20 69742773 206e6f74 20746865 20796561 72732069 6e20796f 7572206c 69666520 74686174 20636f75 6e742e20 20497427 73207468 65206c69 66652069 6e20796f 75722079 65617273 2e>]
2009-11-21 13:17:55.774 Fate[2241:4203] convertToString=[And in the end, it's not the years in your life that count.  It's the life in your years.]]>
mobibob
I should point out that for this design, I do not have to overload the method parser:foundCharacters since my text content is now in a CDATA node and my 'whitespace problem' disappears :)
mobibob