parsing xml data into NSMutable Array list - iphone

I am sending a query to a server and getting response using this code
NSString *urlString = #"myPHPQuery";
NSURL *parserUrl = [[[NSURL alloc] initWithString:urlString] autorelease];
NSXMLParser *parser = [[[NSXMLParser alloc] initWithContentsOfURL:parserUrl] autorelease];
[parser setDelegate:self];
[parser parse];
I can get this type of response
<users>
<username>nothan</username>
<score>1000</score>
</users>
<users>
<username>nothan</username>
<score>1000</score>
</users>
I am using this code to parse the data in Mutable Array
int arrayCount = 0;
NSString *elementname;
NSInteger * count;
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict
{
elementname = elementName;
}
-(void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string{
if([elementname isEqualToString:#"username"])
{
if ( count == 0 )
{
NSLog(#"%#" , string);
[self.playerNames replaceObjectAtIndex:arrayCount withObject:string];
arrayCount = arrayCount + 1;
count = count + 1 ;
}
else
count = 0;
}
else if([elementname isEqualToString:#"score"])
{
if ( count == 0 )
{
NSLog(#"%#" , string);
[self.scores replaceObjectAtIndex:arrayCount withObject:string];
count = count + 1 ;
}
else
count = 0;
}
}
- (void)parserDidEndDocument:(NSXMLParser *)parser
{
for (int i = 0 ; i <10 ; i++)
{
NSLog(#"%d - %#" , i,[playerNames objectAtIndex:i]);
}
}
The problem with this code is, it retrieve only one
<users> .... </users> How can I change this code to make it retrieve more users.
Best Regards

For the sample XML you posted above, the parser ran correctly.
Extensible Markup Language (XML) 1.0 (Fifth Edition) §2.1 Well-Formed XML Documents
There is exactly one element, called the root, or document element
After the first <users>…</users> element is parsed, the document has finished and the parser stops.
To have a list if things in an XML document, you must wrap them in an outer element.
<allusers>
<users>
<username>nothan</username>
<score>1000</score>
</users>
<users>
<username>nothan</username>
<score>1000</score>
</users>
</allusers>

When the parser foundCharacters method is called the int count is checked. If it is zero then the first entry in the array is replaced, and count is incremented. On the next call to this method count is not zero, so nothing is replaced, and then count is set back to zero. For the next call to this method the count is zero.....and so on.
Also bear in mind that when the data being retrieved off the net is large enough to be split into blocks by tcp/ip then the initWithContentsOfURL may not return all of it. It is better to use NSURLConnection to build up the received data as an NSData object and then pass that to the parser. In addition there may be more than one call to foundCharacters for a given element and so the element received should be built up and the placing into the array should be done at the delegate didEndElement method.

Related

Difficulty parsing response containing characters like '\u00'

I am using NSXML Parser to do parsing in my iPhone app. Now everything works fine except when data comes in French language.
For example, data from server comes as Ch\u00e9rie FM.
Now under the string argument of foundCharacters method, I only get string as 'Ch' rest of the characters don't come up. So finally my string is truncated only to 'Ch' intead of the whole Cherie fm
What could be done?
Code:
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedName
attributes:(NSDictionary *)attributeDict
{
if (appDelegate.objPlayer.fromFlickrorRecommend == TRUE)
{
if([elementName isEqualToString:#"outline"] && [[attributeDict valueForKey:#"text"] isEqualToString:#"You may also like"])
{
flagCheck = 1;
}
else if ([elementName isEqualToString:#"outline"] && [[attributeDict valueForKey:#"text"] isEqualToString:#"Genres"])
{
flagCheck = 0;
}
if (flagCheck == 1 && [elementName isEqualToString:#"outline"])
{
if([[attributeDict valueForKey:#"type"] isEqualToString:#"audio"])
{
[appDelegate.objPlayer.recommendDataArray addObject:attributeDict];
}
}
}
else
{
if ([elementName isEqualToString:#"location"])
{
flagCheck = 2;
}
else if ([elementName isEqualToString:#"url"])
{
flagCheck = 3;
}
else if ([elementName isEqualToString:#"name"])
{
flagCheck = 4;
}
}
}
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
if (flagCheck == 2)
{
flagCheck = -1;
appDelegate.objPlayer.flickrCity = string;
}
else if(flagCheck == 3)
{
flagCheck = -1;
appDelegate.objPlayer.stationURL = string;
}
else if(flagCheck == 4)
{
flagCheck = -1;
appDelegate.playStationName = string;
}
//else if(flagCheck == 0) // change
// {
// appDelegate.playStationName = string;
// }
}
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
{
//if (flagCheck == 1 && [elementName isEqualToString:#"outline"])
// {
// [appDelegate.objPlayer.recommendDataArray addObject:dataDictionary];
// dataDictionary = nil;
// [dataDictionary release];
// }
}
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string may be called multiple times so you need to accumulate the characters found into an NSMutableString. There is an example of how to implement this in the Event-Driven XML Programming Guide.
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.
Now \u00e9 is UTF-16 for é so the data must be properly encoded to parse past \u00. So if your data was initially a string you can get the data from it like this.
NSString *text = #"<node>Ch\u00e9rie</node>";
//Important or the parser will stop after Ch
NSData *utf16encode = [text dataUsingEncoding:NSUTF16StringEncoding];
NSXMLParser *parser = [[NSXMLParser alloc] initWithData:utf16encode];
Got the Answer:
This link helped while I was going through stackoverflow for the questions similar to my problem.
Why does arrays handle strings containing swedish ÅÄÖ characters by using two or more indexes?
Hope this helps all who are looking out for a solution. :)

Trim every string in object

I have an XML parser and i want to trim whitespace and new lines before it goes to the app delegate. I know it only works with string, but how to do it for the elements inside the object. More important is it smart to do this or is it better to do a separate trimming
newString =[menu.enable stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
In
- (void) parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName{
NSString *trimmedValue=[currentElementValue stringByTrimmingCharactersInSet:
[NSCharacterSet whitespaceAndNewlineCharacterSet]];
//NSLog(#"current value:%#",currentElementValue);
[aFile setValue:trimmedValue forKey:elementName];
}
it will trim every element before saving it to an object. Here, aFile is object
I've had my run ins with this issue myself, and it's not trivial. SriPriya's solution works but only if there are no newlines in the element content. This:
<foo>
hello
hi
</foo>
would (IIRC) come out as
#"hello\n hi"
when trimmed that way.
The solution I came up with to solve this (and there may be more elegant solutions out there - I'm all ears) is the following:
Presuming you're using NSXMLParser with a delegate class that handles the actual parsing (as in SriPriya's example above), where the -parser:foundCharacters: method is located, you would do:
- (NSString *)removeNewlinesAndTabulation:(NSString *)fromString appending:(BOOL)appending
{
NSArray *a = [fromString componentsSeparatedByString:#"\n"];
NSMutableString *res = [NSMutableString stringWithString:appending ? #" " : #""];
for (NSString *s in a) {
s = [s stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
if (s.length > 0
&& res.length > (appending ? 1 : 0)) [res appendString:#" "];
[res appendString:s];
}
return res;
}
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
if (! currentElementValue) {
currentElementValue = [[NSMutableString alloc] initWithString:[self removeNewlinesAndTabulation:string appending:NO]];
} else {
[currentElementValue appendString:[self removeNewlinesAndTabulation:string appending:currentElementValue.length > 0]];
}
}
I know it looks like a lot of code for something this simple, but it will correctly turn
<foo>
hi there all
i am typing some
stuff
</foo>
into
#"hi there all i am typing some stuff"
which sounds like what you're looking for.

Why does arrays handle strings containing swedish ÅÄÖ characters by using two or more indexes?

Ok, my problem is that whenever i collect data from the parser into an array where the string contains Swedish ÅÄÖ characters. In my example the
[schemaInfoArray objectAtIndex:3]
is supposed to be #"Lördag" but is saved as #"L" and the
[schemaInfoArray objectAtIndex:4]
contains the rest of the string that gets presented as
#"ördag"
-(void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
tempStrang = string;
[schemaInfoArray insertObject:tempStrang atIndex:uppraknare];
uppraknare++;
}
-(void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
{
if ( [elementName isEqualToString:#"schemaInfo"] )
{
}
if ( [elementName isEqualToString:#"modfromtid"] )
{
frommodarbtid = [schemaInfoArray objectAtIndex:0];
}
if ([elementName isEqualToString:#"modtomtid"] )
{
tommodarbtid = [schemaInfoArray objectAtIndex:1];
}
if ([elementName isEqualToString:#"modrast"] )
{
modrast = [schemaInfoArray objectAtIndex:2];
}
if ([elementName isEqualToString:#"benamning"] )
{
benamning = [schemaInfoArray objectAtIndex:3];
}
if ([elementName isEqualToString:#"fromnormarb"] )
{
fromnormarbtid = [schemaInfoArray objectAtIndex:4];
}
if ([elementName isEqualToString:#"tomnormarb"] )
{
tomnormarbtid = [schemaInfoArray objectAtIndex:5];
}
if ([elementName isEqualToString:#"rast"] )
{
normrast = [schemaInfoArray objectAtIndex:6];
}
}
Does anyone have any thoughts about how to actually get #"Lördag" to be saved into ONE index instead of getting split into several indexes? This really destroys the structure of things that is supposed to be presented.
This is a documented design choice from Apple, and has nothing to do with Swedish characters:
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.
So you should do just as they say: use a NSMutableString to accumulate the results, and when the element changes, save the buffer to a permanent, (preferrably) immutable NSString.
As requested, here's an example. It was written without any kind of IDE, so chances are that it'll work, but there's no guarantee that it will either compile or work.
#interface Foo : NSObject<NSXMLParserDelegate> {
NSMutableString* accumulator;
NSMutableArray* schemaInfoArray;
int uppraknare; // whatever 'uppraknare' means
}
/* snip */
#end
#implementation Foo
-(void)parser:(NSXMLParser*)parser foundCharacters:(NSString*)string
{
// only accumulate characters, until we get notified that we went through
// the whole XML element
[accumulator appendString:string];
}
-(void)parser:(NSXMLParser*)parser didEndElement:(NSString*)elementName namespaceURI:(NSString*)nsuri qualifiedName:(NSString*)qName
{
// we went through the whole element! time to save!
NSString* immutableResult = [accumulator copy];
[schemaInfoArray insertObject:immutableResult atIndex:uppraknare];
uppraknare++;
[immutableResult release];
// clear the accumulator for the next element
[accumulator deleteCharactersInRange:NSMakeRange(0, [accumulator length])];
/* the rest of your code here */
}
#end
-(void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string is not guaranteed to contain the complete contents of the string. You need to have a class instance variable that is a NSMutableString that can append all of foundCharacters between the calls to didStartElement and didEndElement. Inside of didEndElement add the the string to the schemaInfoArray.

iphone :- how to get data from xml parser?

i want to get data from xml parser ?
how can i get from xml parser ?
which methods for that and how can i get that ?
any example for that
http://codesofa.com/blog/archive/2008/07/23/make-nsxmlparser-your-friend.html
Have a look at this..
NSXMLParser calls your "delegate", which has to implement certain methods. When the parser calls your delegate, you grab the data at that time.
For my simple xml file, I just implement the main "Element" one, which only recognizes <place lat="number" lon="number"... , and starts like this...
- (void)parser:(NSXMLParser *)parser
didStartElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qName
attributes:(NSDictionary *)attributeDict
{
// we here recognize our one crucial element, "place", and its attributes.
if([elementName compare:#"place"] == 0)
{
OmWayPoint p = {0};
p.lat = [[attributeDict valueForKey:#"lat"] doubleValue];
p.lon = [[attributeDict valueForKey:#"lon"] doubleValue];
p.alt = [[attributeDict valueForKey:#"alt"] doubleValue];
int wpCount = [self addWayPoint:&p];
...
}

How add a default value to an array when child element is not found in xml parsing?

I am new to iphone development.I am parsing an XML page and displaying the content in a tableview.In some block in the XML page , the child element is missing , i want to add a value o in my array, when the child element is not fond in the particular block.My XML file is like
<entry>
<id>xxx </id>
<title>xxxxx</title>
<gd:ratings numRaters="2"/>
</entry> ..................................>first block
<entry>
<id>xxx </id>
<title>xxxxx</title>
</entry> ....................................>Second block
<entry>
<id>xxx </id>
<title>xxxxx</title>
<gd:ratings numRaters="2"/>
</entry> .....................................>Third block
I am parsing the gd:ratings tag and display its attribute numRates value in a tableview. I am having an mutable array which stores the value of numRates.I have to add object "O" in the array when the second block is parsed because it doesnot contain gd:ratings tag.Since the value is present in a attribute tag, for retrieving the content and adding it to the mutable array is done in NSXML parser DidStartElement method.
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict{
currentElement = [elementName copy];
if ([elementName isEqualToString:#"entry"]) {
entry = [[NSMutableDictionary alloc] init];
currentTitle = [[NSMutableString alloc] init];
currentDate = [[NSMutableString alloc] init];
NSLog(#"inside image1 ");
}else if([elementName isEqualToString:#"media:thumbnail"])
{ currentImage = [[NSMutableString alloc] init];
if(myUrl == nil){
myUrl = [NSString stringWithString:[attributeDict objectForKey:#"url"]];
}
[currentImage appendString:myUrl];
[stories1 addObject:currentImage];
myUrl=nil;
} else if([elementName isEqualToString:#"gd:rating"])
{
currentRating=[[NSMutableString alloc]init];
myRatings=[NSString stringWithString:[attributeDict objectForKey:#"numRaters"]];
[currentRating appendString:myRatings];
[stories2 addObject:currentRating];
}
}
Since there are 25 blocks of entry tags and in that 10 block doesnot have gd rating element .So the array stories2 array has only 15 values.So i cannot display it in the tableview in order.Is there any way out to retrieve the attribute tag in "found characters" method . Please help me out.Thanks.
You can use the didEndElement method: when the outer element ended, check if the child element was added to the array.
You could use an iVar BOOL entryHasRating and do the following:
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict{
currentElement = [elementName copy];
if ([elementName isEqualToString:#"entry"]) {
entryHasRating = NO;
...
} else if ([elementName isEqualToString:#"gd:rating"]){
entryHasRating = YES;
...
}
}
Then when an element ends:
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
if ([elementName isEqualToString:#"entry"]&& !entryHasRating) {
[stories2 addObject:#"No Rating"];
entryHasRating = YES;
}
...
}
If you know all of the data fields that you'll be expecting, you could build a dictionary with placeholders for all of your expected values before you parse the XML; as you parse the XML just replace the placeholders with real values.