Robert
Robert

Reputation: 5302

NSXMLParser adding two elements to one TableView cell

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

Answers (3)

Putz1103
Putz1103

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

staticVoidMan
staticVoidMan

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

staticVoidMan
staticVoidMan

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

Related Questions