Hoang Pham
Hoang Pham

Reputation: 6949

How to use NSXMLParser to parse parent-child elements that have the same name

Does anyone have some idea how to parse the following xml using event-driven model NSXMLParser class?

<Node>
  <name> Main </name>
  <Node>
    <name> Child 1 </name>
  </Node>

  <Node>
    <name> Child 2 </name>
  </Node>
</Node>

I want to collect all three names from this xml file, is it possible, or I have to change to Tree-based parsing?

Upvotes: 8

Views: 29729

Answers (3)

brbgyn
brbgyn

Reputation: 421

A very easier way to solve it than the accepted answer, for short XML files:

XML example:

<cotacoes>
    <bovespa>
            <cotacao>50058</cotacao>
            <variacao>-0.16</variacao>
    </bovespa>
    <dolar>
            <cotacao>3.4610</cotacao>
            <variacao>+0.29</variacao>
    </dolar>
    <euro>
            <cotacao>3.7673</cotacao>
            <variacao>-0.25</variacao>
    </euro>
    <atualizacao>04/08/15 - 18:14</atualizacao>
</cotacoes>

Implement a counter:

@implementation className{
    int parserCounter;
}

- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
    parserCounter++;

    NSLog(@"%d: %@",contadorParser,string);

    if (parserCounter == 3) doAnythingFor3;
    if (parserCounter == 5) doAnythingFor5;
    if (parserCounter == 9) doAnythingFor9;
    if (parserCounter == 11) doAnythingFor11;
    if (parserCounter == 15) doAnythingFor15;
    if (parserCounter == 17) doAnythingFor17;
    if (parserCounter == 20) doAnythingFor20;

}

How will i know witch number is witch node ? By seeing the log. It will be something like:

2015-08-04 23:36:08.070 Tesouro Direto[7252:1554402] 1:     
2015-08-04 23:36:08.070 Tesouro Direto[7252:1554402] 2:     
2015-08-04 23:36:08.071 Tesouro Direto[7252:1554402] 3:     50058
2015-08-04 23:36:08.105 Tesouro Direto[7252:1554402] 4:     
2015-08-04 23:36:08.105 Tesouro Direto[7252:1554402] 5:     -0.16
2015-08-04 23:36:08.131 Tesouro Direto[7252:1554402] 6:     
2015-08-04 23:36:08.132 Tesouro Direto[7252:1554402] 7:     
2015-08-04 23:36:08.132 Tesouro Direto[7252:1554402] 8:     
2015-08-04 23:36:08.132 Tesouro Direto[7252:1554402] 9:     3.4610
2015-08-04 23:36:08.156 Tesouro Direto[7252:1554402] 10:    
2015-08-04 23:36:08.156 Tesouro Direto[7252:1554402] 11:    +0.29   
2015-08-04 23:36:08.180 Tesouro Direto[7252:1554402] 12:    
2015-08-04 23:36:08.180 Tesouro Direto[7252:1554402] 13:    
2015-08-04 23:36:08.180 Tesouro Direto[7252:1554402] 14:    
2015-08-04 23:36:08.180 Tesouro Direto[7252:1554402] 15:    3.7673
2015-08-04 23:36:08.203 Tesouro Direto[7252:1554402] 16:    
2015-08-04 23:36:08.203 Tesouro Direto[7252:1554402] 17:    -0.25
2015-08-04 23:36:08.226 Tesouro Direto[7252:1554402] 18:    
2015-08-04 23:36:08.227 Tesouro Direto[7252:1554402] 19:    
2015-08-04 23:36:08.227 Tesouro Direto[7252:1554402] 20:    04/08/15   - 18:14
2015-08-04 23:36:08.227 Tesouro Direto[7252:1554402] 21: 

The log will show you witch number is each node.

Upvotes: -1

yasirmturk
yasirmturk

Reputation: 1954

- (NSInteger)columnNumber

gives you the nesting level of NSXMLParser

Upvotes: 7

Adrian Kosmaczewski
Adrian Kosmaczewski

Reputation: 7956

This is a common problem with parsers like this one, of "type SAX", where you have to manually keep track of the current depth of the XML tree you're in. The problem, as always, is that loading the entire tree in a DOM structure in memory can be impossible, depending on the size of the data you want to manipulate.

The following code shows a class that does this job:

#import <Foundation/Foundation.h>

@interface Test : NSObject <NSXMLParserDelegate> 
{
@private
    NSXMLParser *xmlParser;
    NSInteger depth;
    NSMutableString *currentName;
    NSString *currentElement;
}

- (void)start;

@end

This is the implementation:

#import "Test.h"

@interface Test ()
- (void)showCurrentDepth;
@end


@implementation Test

- (void)dealloc
{
    [currentElement release];
    [currentName release];
    [xmlParser release];
    [super dealloc];
}

- (void)start
{
    NSString *xml = @"<?xml version=\"1.0\" encoding=\"UTF-8\" ?><Node><name>Main</name><Node><name>Child 1</name></Node><Node><name>Child 2</name></Node></Node>";
    xmlParser = [[NSXMLParser alloc] initWithData:[xml dataUsingEncoding:NSUTF8StringEncoding]];
    [xmlParser setDelegate:self];
    [xmlParser setShouldProcessNamespaces:NO];
    [xmlParser setShouldReportNamespacePrefixes:NO];
    [xmlParser setShouldResolveExternalEntities:NO];
    [xmlParser parse];

}

#pragma mark -
#pragma mark NSXMLParserDelegate methods

- (void)parserDidStartDocument:(NSXMLParser *)parser 
{
    NSLog(@"Document started", nil);
    depth = 0;
    currentElement = nil;
}

- (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError 
{
    NSLog(@"Error: %@", [parseError localizedDescription]);
}

- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName 
  namespaceURI:(NSString *)namespaceURI 
 qualifiedName:(NSString *)qName 
    attributes:(NSDictionary *)attributeDict
{
    [currentElement release];
    currentElement = [elementName copy];

    if ([currentElement isEqualToString:@"Node"])
    {
        ++depth;
        [self showCurrentDepth];
    }
    else if ([currentElement isEqualToString:@"name"])
    {
        [currentName release];
        currentName = [[NSMutableString alloc] init];
    }
}

- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName 
  namespaceURI:(NSString *)namespaceURI 
 qualifiedName:(NSString *)qName
{

    if ([elementName isEqualToString:@"Node"]) 
    {
        --depth;
        [self showCurrentDepth];
    }
    else if ([elementName isEqualToString:@"name"])
    {
        if (depth == 1)
        {
            NSLog(@"Outer name tag: %@", currentName);
        }
        else 
        {
            NSLog(@"Inner name tag: %@", currentName);
        }
    }
}        

- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
    if ([currentElement isEqualToString:@"name"]) 
    {
        [currentName appendString:string];
    } 
}

- (void)parserDidEndDocument:(NSXMLParser *)parser 
{
    NSLog(@"Document finished", nil);
}

#pragma mark -
#pragma mark Private methods

- (void)showCurrentDepth
{
    NSLog(@"Current depth: %d", depth);
}

@end

This is the result of running a command line tool that triggers the "start" method above:

Document started
Current depth: 1
Outer name tag: Main
Current depth: 2
Inner name tag: Child 1
Current depth: 1
Current depth: 2
Inner name tag: Child 2
Current depth: 1
Current depth: 0
Document finished

Upvotes: 32

Related Questions