Reputation: 5302
I'm trying to use NSXMLParser
to populate a UITableView
with titles of articles from several separate RSS feeds:
for (NSString* feedString in [[NSUserDefaults standardUserDefaults] objectForKey:@"RSSFeeds"])
{
NSURL *url = [NSURL URLWithString:feedString];
parser = [[NSXMLParser alloc] initWithContentsOfURL:url];
[parser setDelegate:self];
[parser setShouldResolveExternalEntities:NO];
[parser parse];
}
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict
{
element = elementName;
if ([element isEqualToString:@"item"])
{
item = [[NSMutableDictionary alloc] init];
title = [[NSMutableString alloc] init];
link = [[NSMutableString alloc] init];
}
}
This seems to work well if there is only one RSS feed but as soon as there is more than one, the first title from the second feed is appended to the the last title from the first feed and they share the same cell in the table view.
I think this method is at the heart of the problem but am struggling to get it to work properly:
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
if ([element isEqualToString:@"title"])
{
[title appendString:string];
}
else if ([element isEqualToString:@"link"])
{
[link appendString:string];
}
}
I appreciate I'm probably overlooking something obvious but any pointers would be appreciated.
My didEnd
code:
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
{
if ([elementName isEqualToString:@"item"])
{
[item setObject:title forKey:@"title"];
[item setObject:link forKey:@"link"];
[feeds addObject:[item copy]];
}
}
- (void)parserDidEndDocument:(NSXMLParser *)parser
{
[self.tableView reloadData];
}
Upvotes: 1
Views: 864
Reputation: 6211
When using NSXMLParser
, it is completely up to you to create your object structure as NSXMLParser
is dumb (and I don't mean that it is functionless, I mean that it leaves all the smart stuff up to you)
NSXMLParser
just reads a file line by line and tells you what it found on the currently parsed line (in the -foundCharacters
method).
It doesn't tell you about previous lines or upcoming lines.
I'm going to give you the most basic of code that will create an object hierarchy based on the XML structure that gets parsed.
You can make this much easier or much more discerning based on your current needs.
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qName
attributes:(NSDictionary *)attributeDict
{
//NSLog(@"started element %@", elementName);
if(resultString) {
if(eliminateWhiteSpace)
[[objects objectAtIndex:[objects count] - 1] setStringValue:[resultString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]];
else
[[objects objectAtIndex:[objects count] - 1] setStringValue:resultString];
resultString = nil;
}
XMLNode* currentObject = [[XMLNode alloc] init];
if(!objects) {
objects = [[NSMutableArray alloc] init];
}
[objects addObject:currentObject];
[currentObject setNodeName:elementName];
if([[attributeDict allKeys] count] > 0) {
currentObject.attributes = attributeDict;
}
}
- (void)parser:(NSXMLParser*)parser foundCharacters:(NSString *)string
{
NSLog(@"found characters %@", string);
if(!resultString) {
resultString = [[NSMutableString alloc] init];
}
[resultString appendString:string];
}
- (void)parser:(NSXMLParser *)parser foundCDATA:(NSData *)CDATABlock
{
resultString = [[NSMutableString alloc] initWithData:CDATABlock
encoding:NSUTF8StringEncoding];
}
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qName
{
//NSLog(@"ended element %@", elementName);
if(resultString) {
if(eliminateWhiteSpace)
[[objects objectAtIndex:[objects count] - 1] setStringValue:[resultString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]];
else
[[objects objectAtIndex:[objects count] - 1] setStringValue:resultString];
}
if([objects count] > 1) {
[[objects objectAtIndex:[objects count] - 2] addSubNode:[objects objectAtIndex:[objects count] - 1]];
[objects removeObjectAtIndex:[objects count] - 1];
}
resultString = nil;
}
I have a few class variables in there and an XMLNode
class.
You will have to tailor those to suit your needs so I won't explain exactly what those are.
Likely for your situation you can just remove them completely.
Read through that code a few times, you should be able to get the hang of what NSXMLParser
does for you and what it expects of you.
Good luck.
Upvotes: 2
Reputation: 20274
It seems you aren't ever resetting title
or link
Try this (not sure since your logic is a bit out of the ordinary)
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qName
{
if ([elementName isEqualToString:@"item"]) {
[feeds addObject:[item copy]];
}
if ([elementName isEqualToString:@"title"]) {
[item setObject:[title copy] forKey:@"title"];
title = @"";
}
if ([elementName isEqualToString:@"link"]) {
[item setObject:[link copy] forKey:@"link"];
link = @"";
}
}
Upvotes: 1
Reputation: 20274
NOTE: not the most optimized solution but should work (i'll edit this later)
for (NSString* feedString in [[NSUserDefaults standardUserDefaults] objectForKey:@"RSSFeeds"])
{
NSURL *url = [NSURL URLWithString:feedString];
parser = [[NSXMLParser alloc] initWithContentsOfURL:url];
[parser setDelegate:self];
[parser setShouldResolveExternalEntities:NO];
[parser parse];
//declare "NSMutableString *strCurrent;" in the .h of this class
strCurrent = [NSMutableString alloc] init];
}
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
[strCurrent appendString:string];
}
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qName
attributes:(NSDictionary *)attributeDict
{
if ([elementName isEqualToString:@"item"]) {
item = [[NSMutableDictionary alloc] init];
}
if ([elementName isEqualToString:@"title"]) {
strCurrent = [[NSMutableString alloc] init];
}
if ([elementName isEqualToString:@"link"]) {
strCurrent = [[NSMutableString alloc] init];
}
}
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qName
{
if ([elementName isEqualToString:@"title"]) {
[item setObject:strCurrent forKey:@"title"];
}
if ([elementName isEqualToString:@"link"]) {
[item setObject:strCurrent forKey:@"link"];
}
if ([elementName isEqualToString:@"item"]) {
[feeds addObject:[item copy]];
}
}
Upvotes: 1