baboso baboso - 15 days ago 5
iOS Question

NSXMLParser retrieving wrong data from XML tags

I am reading a xml with some fields, for example:

<url>http://myurl.com/noticias/redirect.html?document=xx222&usuario=44yyy&url=http%3A%2F%2Fwww.environmental-expert.com%2FresultEachPressRelease.aspx%3Fcid%3D23745%26codi%3D234441%26lr%3D1</url>

<title>The world water report – supplies falling, Tensions Rising</title>


And the output is:

2011-04-28 17:08:02.191 MyProject[12093:207] doc found

2011-04-28 17:08:02.192 MyProject[12093:207] url=http%3A%2F%2Fwww.environmental-expert.com%2FresultEachPressRelease.aspx%3Fcid%3D23745%26codi%3D234441%26lr%3D1

2011-04-28 17:08:02.193 MyProject[12093:207] – supplies falling, Tensions Rising


which is not matching the data on the xml file.

my parser has the following methods:

// Start of element
- (void)parser:(NSXMLParser *)parser
didStartElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qName
attributes:(NSDictionary *)attributeDict
{
if ([elementName isEqualToString:@"document"]){
NSLog(@"doc found");

}
}

// Found Character
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSMutableString *)string
{
currentNodeContent = [string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
}


// End Element
- (void) parser:(NSXMLParser *)parser
didEndElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qName
{
if ([elementName isEqualToString:@"url"]){

NSLog(@"%@", currentNodeContent);

}
if ([elementName isEqualToString:@"title"]){

NSLog(@"%@", currentNodeContent);

}



if ([elementName isEqualToString:@"document"]){
[currentNodeContent release];
currentNodeContent = nil;
}

}


Can it be related to the function foundCharacters?

Thanks in advance for your help!

Answer

foundCharacters can be called several times for one field ! You should create a temporary NSMutableString and append foundcaracters result !

// Start of element
- (void)parser:(NSXMLParser *)parser
didStartElement:(NSString *)elementName
  namespaceURI:(NSString *)namespaceURI
 qualifiedName:(NSString *)qName
    attributes:(NSDictionary *)attributeDict
{
    if ([elementName isEqualToString:@"document"]){
        NSLog(@"doc found");

        if (temp != nil) {
            [temp release];
        }

        // Alloc temp string
        currentNodeContent = [[NSMutableString alloc] init];


    }
}

// Found Character
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSMutableString *)string
{
    if (currentNodeContent != nil) {
        [currentNodeContent appendString:[string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
    }
}


// End Element
- (void) parser:(NSXMLParser *)parser
  didEndElement:(NSString *)elementName
   namespaceURI:(NSString *)namespaceURI
  qualifiedName:(NSString *)qName
{
    if ([elementName isEqualToString:@"url"]){

        NSLog(@"%@", currentNodeContent);

    }
    if ([elementName isEqualToString:@"title"]){

        NSLog(@"%@", currentNodeContent);

    }



    if ([elementName isEqualToString:@"document"]){
        [currentNodeContent release];
        currentNodeContent = nil;
    }

    // Release temp string
    [currentNodeContent release];
    currentNodeContent = nil;


}

Be sure to declare currentNodeContent as a NSMutableString and not a NSString !

Here is the xml parser code I'm using in my projects :

//
//  XMLParser.h
//

#import <Foundation/Foundation.h>
#import "XMLNode.h"

#if __IPHONE_OS_VERSION_MAX_ALLOWED > __IPHONE_3_2
@interface XMLParser : NSObject <NSXMLParserDelegate> {
#else
@interface XMLParser : NSObject {
#endif
    XMLNode *rootElement;
    XMLNode *tempElement;
    NSMutableString *temp;
    NSMutableArray *elements;
}

- (NSObject *)parseData:(NSData *)data;

// NSXMLParser delegate implementation
- (void)parserDidStartDocument:(NSXMLParser *)parser;
- (void)parserDidEndDocument:(NSXMLParser *)parser;
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedName attributes:(NSDictionary *)attributeDict;
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName;
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string;

@end

-

//
//  XMLParser.m
//

#import "XMLParser.h"


@implementation XMLParser

- (NSObject *)parseData:(NSData *)data
{
    NSXMLParser *parser = [[NSXMLParser alloc] initWithData:data];
    [parser setDelegate:self];
    [parser setShouldResolveExternalEntities:YES];

    if (![parser parse]) {
        if ([[parser parserError] code] == NSXMLParserUnknownEncodingError) {
            // If encoding is "us-ascii" replace encoding with something known (as NSXMLParser is not supporting this encoding)
            NSString *oldString = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];
            NSString *newEncodingString = [oldString stringByReplacingOccurrencesOfString:@"encoding=\"us-ascii\"" withString:@"encoding=\"iso-8859-1\"" options:0 range:NSMakeRange(0,100)];
            newEncodingString = [newEncodingString stringByReplacingOccurrencesOfString:@"encoding=\"windows-1251\"" withString:@"encoding=\"iso-8859-1\"" options:0 range:NSMakeRange(0,100)];

            [oldString release];
            NSData *nameData = [newEncodingString dataUsingEncoding:NSASCIIStringEncoding];
            NSXMLParser *parser2 = [[NSXMLParser alloc] initWithData:nameData];
            [parser2 setDelegate:self];
            [parser2 setShouldResolveExternalEntities:YES];
            if (![parser2 parse]) {
                DLog(@"parseXML error : %@",[[parser2 parserError] localizedDescription]);
            }
            [parser2 release];

        } else {
            DLog(@"parseXML error : %@",[[parser parserError] localizedDescription]);
        }

    }
    [parser release];

    return rootElement;
}


- (void)dealloc
{
    if (tempElement != nil) {
        [tempElement release];
    } 
    if (rootElement != nil) {
        [rootElement release];
    }
    if (temp != nil) {
        [temp release];
    }
    if (elements != nil) {
        [elements release];
    }

    [super dealloc];
}


// NSXMLParser delegate implementation


- (void)parserDidStartDocument:(NSXMLParser *)parser
{
    if (elements != nil) {
        [elements release];
    }
    elements = [[NSMutableArray alloc] init];
    if (tempElement != nil) {
        [tempElement release];
        tempElement = nil;
    }
    if (temp != nil) {
        [temp release];
        temp = nil;
    }
    if (rootElement != nil) {
        [rootElement release];
        rootElement = nil;
    }
}


- (void)parserDidEndDocument:(NSXMLParser *)parser
{
   if (elements != nil) {
        [elements release];
        elements = nil;
    }
    if (tempElement != nil) {
        [tempElement release];
        tempElement = nil;
    }
    if (temp != nil) {
        [temp release];
        temp = nil;
    }
}


- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName
  namespaceURI:(NSString *)namespaceURI
 qualifiedName:(NSString *)qualifiedName
    attributes:(NSDictionary *)attributeDict
{
    XMLNode *element = [[[XMLNode alloc] initWithName:elementName] autorelease];
    [element setAttributes:attributeDict];
    if (tempElement != nil) {
        [tempElement addChild:element];
        [elements addObject:tempElement];
        [tempElement release];
    } else {
        rootElement = [element retain];
    }

    tempElement = [element retain];
    if (temp != nil) {
        [temp release];
    }
    temp = [[NSMutableString alloc] init];
}


- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName
  namespaceURI:(NSString *)namespaceURI
 qualifiedName:(NSString *)qName
{
    [tempElement setContent:temp];
    [temp release];
    temp = nil;
    [tempElement release];
    tempElement = nil;
    if ([elements count] > 0) {
        tempElement = [[elements lastObject] retain];
        [elements removeLastObject];
    }
}


- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
    if (temp != nil) {
        [temp appendString:string];
    }
}

@end

-

//
//  XMLNode.h
//

#import <Foundation/Foundation.h>

/**
 * XMLNode represents an XML data node.
 *
 * Every node can contain multiple children, attributes, a content string
 * and an element name. An XML document is represented using a reference
 * to the document root node.
 * @ingroup Webservice
 */
@interface XMLNode : NSObject {

    NSMutableArray *children;
    NSDictionary *attributes;
    NSString *content;
    NSString *elementName;

}

/**
 * Return an initialized XMLNode.
 * @param name The element name of the node.
 */
- (id)initWithName:(NSString *)name;

/**
 * Set the node attributes.
 * @param dictionary A dictionary containing the attributes.
 */
- (void)setAttributes:(NSDictionary *)dictionary;

/**
 * Set the content as string.
 * @param string The content as string.
 */
- (void)setContent:(NSString *)string;

/**
 * Add a child node.
 * @param xmlElement The child node.
 */
- (void)addChild:(XMLNode *)xmlElement;

/**
 * Return a specific attribute.
 * @param name The attribute name.
 * @return The attribute for the specified name or nil.
 */
- (NSString *)attributeForName:(NSString *)name;

/**
 * Return a child at a specific index.
 * 
 * If the index is out of the bounds, an NSRangeException is thrown.
 * @param index The position of the child (by document order).
 * @return The child at index
 * @see childElementsCount
 */
- (XMLNode *)childElementAtIndex:(int)index;

/**
 * Return a child with a certain name.
 * @param name The name of the element.
 * @return The element or nil if not available.
 */
- (XMLNode *)childWithName:(NSString *)name;
- (XMLNode *)childWithName:(NSString *)name atIndex:(NSInteger)index;

/** 
 * Return the number of child nodes.
 * @return The number of child nodes.
 */
- (int)childElementsCount;
- (int)childWithNameElementsCount:(NSString *)name;

/** 
 * Return the node content.
 * @return The node content as string.
 */
- (NSString *)content;

/**
 * Get the name of the xml element.
 * @return The name of the element.
 */
- (NSString *)name;

- (void)dealloc;

@end

-

//
//  XMLNode.m
//

#import "XMLNode.h"


@implementation XMLNode
- (id)initWithName:(NSString *)name
{
    self = [super init];
    elementName = [name copy];
    children = [[NSMutableArray alloc] init];
    return self;
}


- (void)setAttributes:(NSDictionary *)dictionary
{
    if (attributes != nil) {
        [attributes release];
    }
    attributes = [dictionary retain];
}


- (void)setContent:(NSString *)string
{
    if (content != nil) {
        [content release];
    }
    content = [string retain];
}

- (void)addChild:(XMLNode *)xmlElement
{
    [children addObject:xmlElement];
}


- (NSString *)attributeForName:(NSString *)name
{
    return [attributes objectForKey:name];
}


- (XMLNode *)childElementAtIndex:(int)index
{
    return [children objectAtIndex:index];
}


- (XMLNode *)childWithName:(NSString *)name
{
    int i;
    for (i=0; i < [children count]; ++i) {
        XMLNode *child = [children objectAtIndex:i];
        if ([[[child name] lowercaseString] isEqualToString:[name lowercaseString]]) {
            return child;
        }
    }

    return nil;
}

- (XMLNode *)childWithName:(NSString *)name atIndex:(NSInteger)index
{
    int i;
    int count = 0;
    for (i = 0; i < [children count]; ++i) {
        XMLNode *child = [children objectAtIndex:i];
        if ([[[child name] lowercaseString] isEqualToString:[name lowercaseString]]) {
            if (count == index)
                return child;
            count ++;
        }
    }

    return nil;
}

- (int)childElementsCount
{
    return [children count];
}

- (int)childWithNameElementsCount:(NSString *)name
{
    int i;
    int count = 0;
    for (i = 0; i < [children count]; ++i) {
        XMLNode *child = [children objectAtIndex:i];
        if ([[[child name] lowercaseString] isEqualToString:[name lowercaseString]]) {
            count ++;
        }
    }

    return count;
}


- (NSString *)content
{
    return content;
}


- (NSString *)name
{
    return elementName;
}


- (void)dealloc
{
    if (content != nil) {
        [content release];
    }
    if (children != nil) {
        [children release];
    }
    if (attributes != nil) {
        [attributes release];
    }

    [super dealloc];
}

@end

use this as following (this sample is to analyze a rss feed :

XMLParser *parser = [[XMLParser alloc] init];
NSObject *rss = [parser parseData:rssFeed];
feedItems = [(XMLNode *)rss childWithName:@"channel"];
if (feedItems) {
    NSLog(@"%@",[[feedItems childWithName:@"title"] content]);
}
[parser release];

Check XMLNode.h for more methods if you need a more complex analysis

Comments