Why is NSXMLParser picking up this whitespace in the foundCharacters method? - iphone

I'm learning to use the NSXMLParser API for the iOS platform and so far it's very easy to use. I'm having a small problem, however, in the foundCharacters method. As I understand it, it shouldn't pick up any whitespace since the foundIgnorableWhitespace method is supposed to catch that, but it looks like it is. Here's the my code...
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict
{
//We're at the start of a new data feed
if([elementName isEqualToString:#"data"])
{
if(listOfTimes != nil)
[listOfTimes release];
listOfTimes = [[NSMutableArray alloc] init];
}
else if ( [elementName isEqualToString:#"start-valid-time"]) {
currentElementType = kXMLElementTime;
return;
}
else {
currentElementType = kXMLElementOther;
}
//---------------------------------------------------------------------
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
if(currentElementType == kXMLElementTime)
{
//We don't want anymore than three times
if ([listOfTimes count] >= 3)
return;
[listOfTimes addObject:string];
}
}
It basically stores three "time" elements in an array. The problem, however, is it seems to be picking up whitespace in the form of a newline. Here's the printout of the array in the console...
Printing description of listOfTimes:
(
"2010-08-21T22:00:00-05:00",
"\n ",
"2010-08-22T01:00:00-05:00"
)
and here's a snippet of the XML data I'm processing...
<time-layout time-coordinate="local" summarization="none">
<layout-key>k-p3h-n40-1</layout-key>
<start-valid-time>2010-08-21T22:00:00-05:00</start-valid-time>
<start-valid-time>2010-08-22T01:00:00-05:00</start-valid-time>
<start-valid-time>2010-08-22T04:00:00-05:00</start-valid-time>
<start-valid-time>2010-08-22T07:00:00-05:00</start-valid-time>
<start-valid-time>2010-08-22T10:00:00-05:00</start-valid-time>
.
.
.
Am I misunderstanding how this works?
Thanks in advance for your help!

The easy solution is to create a didEndElement: method where you set currentElement to kXMLElementOther.
There is a good description of Ignorable White Space at Ignorable White Space. The problem is probably that you do not have a DTD associated with your document. So the parser does not actually know what ignorable white space is. (It is not simply white space between tags, which is probably what you think) So it is simply treating everything as character data.

- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
//whatever data i am getting from node i am appending it to the nodecontent variable
[nodecontent appendString:[string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]];
NSLog(#"node content = %#",nodecontent);
}

I found the answer for your question do editing in following piece of code
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
Add this line of code
NSString *stringToDisplay = [string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
Now reply where ever you find "string" in your function mentioned above with "stringToDisplay"
It worked for me. Hope it will work for you too.
Enjoy Coding.

Related

NSXMLParser issue : don't get all data?

I'm developing an application that needs to get data from an XML file. Some of the nodes have a lot of characters and I've a problem using this function :
- (void) parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
currentNodeContent = (NSMutableString *) [string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
}
For example for the description node of an item I will get only the 20-30 last characters whereas I would get 200 or 300.
I checked this out with a NSLog and it appears the problem comes from here. Do you know what's wrong ?
Thanks for any advice.
SAX parsers do not guarantee to get all characters at once. You may get multiple calls with chunks of characters from any given block; your code should concatenate them into a single string.
The parser object may send the delegate several parser:foundCharacters: messages to report the characters of an element. Because string may be only part of the total character content for the current element, you should append it to the current accumulation of characters until the element changes.
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedName attributes:(NSDictionary *)attributeDict {
if ([qualifiedName isEqualToString:#"myTag"]) {
buf = [NSMutableString string];
}
}
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
if ([qualifiedName isEqualToString:#"myTag"]) {
buf = [buf stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
NSLog(#"Got %#", buf);
}
}
- (void) parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
[buf appendString:string];
}

Is there a way to get NSXMLParser to ignore the "&amp" and similar symbols while parsing XML?

I'm currently parsing an xml file that has a string like this between two elements:
Hello & bye
When the foundCharacters delegate gets called it parses the information like this....
it parses:
Hello
&
bye
It makes 3 calls to parse that element and I end up with 3 strings as opposed to 1 string. Is there some way to detect this with out having to append a string together and keep a counter of how many times the delegate was called?
Any help is very appreciated...
Short answer : No. It's only chance that it's returning three strings, it might return 11 strings all one character long if it wanted to. It's up to you to join the strings together.
Long answer : You need to append a string. There's nothing you can do about it. however, I don't understand why you need to keep a count of the number of times the delegate has been called - I think that this code should do what you want :
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedName attributes:(NSDictionary *)attributeDict {
// Make a new string to hold the data
[currentString release];
currentString = [NSMutableString alloc] init];
}
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
// Add the string to our current string
[currentString appendString:string];
}
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
// In here, current string should be it's entire contents,
NSLog(#"%#", currentString);
}
You will need a member in your class to hold the data - a mutable string is easiest one:
#interface MyClass : NSObject {
NSMutableString *currentString;
}
#end
(It doesn't need to be a property but you do need to remember to release it in your dealloc method)

How would you parse the integer out of this XML response in Obj C?

I am working with an API where I get a response back this this, and I want to parse the integer ID out of it:
<?xml version="1.0"?>
<trip>328925</trip>
How would you parse this? I have some really fragile code I want to get rid of, and I'd appreciate some advice:
if ([[response substringWithRange:NSMakeRange(0, 21)]
isEqualToString: #"<?xml version=\"1.0\"?>"]) {
self.tripId = [response substringWithRange:NSMakeRange(28, response.length-35)];
}
I don't think I need an XML parsing library for this task!
I would use an XML parser. Using an XML parser really is the best way to parse XML.
It's not that hard to do either:
// Parser Delegate
#interface ParserDelegate : NSObject {
int inTripElements;
NSMutableString* trip;
}
#property (readonly) NSMutableString* trip;
#end
#implementation ParserDelegate
#synthesize trip;
- (id) init {
if (![super init]) {
[self release];
return nil;
}
trip = [#"" mutableCopy];
return self;
}
- (void) parser:(NSXMLParser *)parser
didStartElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qualifiedName
attributes:(NSDictionary *)attributeDict {
if ([elementName isEqualToString:#"trip"]) {
++inTripElements;
}
}
- (void) parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
if (inTripElements > 0) {
[trip appendString:string];
}
}
- (void) parser:(NSXMLParser *)parser
didEndElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qName {
if ([elementName isEqualToString:#"trip"]) {
--inTripElements;
}
}
#end
// Parsing
NSString* toParse = #"<?xml version=\"1.0\"?>"
"<trip>328925</trip>";
NSData* data = [NSData dataWithBytes:[toParse UTF8String]
length:strlen([toParse UTF8String])];
ParserDelegate* parserDelegate = [[ParserDelegate alloc] init];
NSXMLParser* parser = [[NSXMLParser alloc] initWithData:data];
[parser setDelegate:parserDelegate];
[parser parse];
[parser release];
NSLog(#"trip=%#", parserDelegate.trip);
[parserDelegate release];
If you really don't want to use an XML Parser to create a proper object why not use the regular expression <trip>\d*</trip> to match the element? Then you can get the integer part by removing the start and end tag and parsing to ensure correct as with any string.
Here is a handy place for testing regular expressions; http://regexlib.com/RETester.aspx
Check out 'NSScanner'. It would be perfect for this.
You should use an XML library for this as there are many cases where the code will change
For example in this case what happens if
The <?xml declaration is not sent or the encoding changes from UTF-8
or somone adds a space before the trip element
In all these cases the provider of the file can say the file is correct
etc.
with the XML parsing all this has been done and your code is more robust
Also in this case the code to parse and find is quite simple.

How do I parse an NSString containing XML in Objective-C?

In my iPhone application, I have the following NSString:
NSString *myxml=#"<students>
<student><name>Raju</name><age>25</age><address>abcd</address>
</student></students>";
How would I parse the XML content of this string?
Download:
https://github.com/bcaccinolo/XML-to-NSDictionary
Then you simply do :
NSDictionary *dic = [XMLReader dictionaryForXMLString:myxml error:nil];
Result is a NSDictionary *dic with dictionaries, arrays and strings inside, depending of the XML:
{
students = {
student = {
address = abcd;
age = 25;
name = Raju;
};
};
}
You should use the NSXMLParser class
Here's a link to the documentation for that class:
http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSXMLParser_Class/Reference/Reference.html
Your code should look something like this:
#implementation MyClass
- (void)startParsing {
NSData *xmlData = (Get XML as NSData)
NSXMLParser *parser = [[[NSXMLParser alloc] initWithData:xmlData] autorelease];
[parser setDelegate:self];
[parser parse];
}
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedName attributes:(NSDictionary *)attributeDict {
NSLog(#"Started %#", elementName);
}
Another answer is: Don't use XML. Use a plist instead, which is written using XML but more easily parsable in Objective-C into distinct data types (NSArray for example has a method to convert a file or NSData plist into an NSArray).
Like #Jon Hess mentioned, just create a wrapping class for the "optional" methods of the NSXMLParserDelegate. These methods help you separate the tasks that you might find useful when you parse your xml.
One really good online journal file I found is Elegant XML parsing with Objective-C. Phil Nash really took his time to show the basics of the parsing options at your reach. It can take a new programmer and guide him/her through the whole setup.
Loading the xml can be a modification of #Jon Hess method.
You can setup the:
-(void)parser:(NSXMLParser *)parser
didStartElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qName
attributes:(NSDictionary *)attributeDict{
}
to handle events on certain elements.
Also implement the:
-(void)parser:(NSXMLParser *)parser
foundCharacters:(NSString *)string {
}
to place the strings found into a collection of objects.
I think the best equivalent to XMLDocument is AbacigilXQ Library. You should look at it. I'm using it.
http://code.google.com/p/abacigilxq-library/

Datatypes for use with NSXMLParser

I'm using NSXMLParser to parse XML data from a remote server. I followed a 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.
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];
}
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.
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.
You're correct in that parser:foundCharacters: can be called multiple times for a single element.
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];