1
votes

I have the below actions in an xml that I want to read. But I want to just to read <goodactions> and <niceactions>. Do I need to place flags in the didStartElement delegate function?

<goodactions>
  <action name=\"play\" id=\"0\"/>
  <action name=\"eat\" id=\"1\" />
  <action name=\"sleep\" id=\"2\" />
  <action name=\"study\" id=\"3\"/>
</goodactions>
<badactions>
  <action name=\"smoke\" id=\"0\"/>
  <action name=\"watch_tv\" id=\"1\"/>
</badactions>
<niceactions>
  <action name=\"cycling\" id=\"0\"/>
  <action name=\"swimming\" id=\"1\"/>
</niceactions>
3

3 Answers

3
votes

Maybe you're asking something else, but a simple boolean might suffice.

BOOL ignoreElement; 

- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict {

    if ([elementName isEqualToString:@"badactions"]) {

        self.ignoreElement = YES; 

    }
}

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

    if (!self.ignoreElement) {
        // data from other elements
    }
}

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

    if ([elementName isEqualToString:@"badactions"]) {

        self.ignoreElement = NO; 

    }

}
1
votes

Yes, that's how I'd do it. When your delegate's -parser:didStartElement:::: method gets called, set a flag to ignore everything if the element name is "badactions". Then in -parser:didEndElement: reset the flag if the element name is "badactions". Something like this:

- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict {
    if ([elementName isEqualToString:@"badactions"])
    {
        _ignoreElement = YES;
        return;
    }
    if (!_ignoreElement)
    {
         // handle other elements here
    }
}

- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
    // Reset if we were ignoring a badactions element
    if (([elementName isEqualToString:@"badactions"]) && (_ignoreElement))
    {
         _ignoreElement = NO;
    }
}
0
votes

The easiest solution to ignore specific nodes or branches is to create your own XPath-like XML path and maintain element stack.

So whenever you parse new element you push node on stack, and update current XML path. Then when you stumble upon closing tag, you simply pop element from stack.

However your stack can be inconsistent because you probably want to ignore some branches, you can always keep and compare against XMLPath of closing tag and last node on stack.

In given example below I store XML path using dot separator. It's cheap and easy to parse and compare.

As a benefit of this approach, you can implement some simple comparison algorithm and ignore or match specific branches in the middle of XML structure but dive and parse out specific nodes deeper.

Having stack of elements is a huge bonus because you can always reference related nodes if you need to fill them up with some information located deep down the hierarchy.

// Base node class
@interface MYNode : NSObject

@property NSString* XMLPath;

@end

// Some custom node subclass
@interface MYRootNode : MYNode @end

// Some other custom node subclass
@interface MYBlockNode : MYNode @end

// NSXMLParserDelegate interface
@interface MYXMLParserDelegate : NSObject<NSXMLParserDelegate>

@property NSMutableArray* elementStack;
@property NSString* XMLPath;
@property MYRootNode* rootNode;

@end

// NSXMLParserDelegate implementation
@implementation MYXMLParserDelegate

#pragma mark - Initializer

- (id)init {
    if(self = [super init]) {
        self.elementStack = [NSMutableArray new];
    }
    return self;
}

#pragma mark - XMLPath manipulation methods

- (void)addXMLPathComponent:(NSString*)component {
    NSString* newXMLPath;

    if(self.XMLPath.length) {
        newXMLPath = [self.XMLPath stringByAppendingFormat:@".%@", component];
    } else {
        newXMLPath = component;
    }

    self.XMLPath = newXMLPath;
}

- (void)removeLastXMLPathComponent {
    NSRange range = [self.XMLPath rangeOfString:@"." options:NSBackwardsSearch];
    if(range.location != NSNotFound) {
        self.XMLPath = [self.XMLPath substringToIndex:range.location];
    }
}

#pragma mark - NSXMLParserDelegate

- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict {
    MYNode* node;

    // Add XML path component
    [self addXMLPathComponent:elementName];

    // Process relevant nodes
    if([self.XMLPath isEqualToString:@"document.page"])
    {
        node = [[MYRootNode alloc] initWithAttributes:attributeDict];

        // Save root node
        self.rootNode = node;
    }
    else if([self.XMLPath isEqualToString:@"document.page.block"])
    {
        node = [[MYBlockNode alloc] initWithAttributes:attributeDict];
    }

    // Push relevant node on stack
    if(node) {
        node.XMLPath = self.XMLPath;
        [self.elementStack addObject:node];

        NSLog(@"-> %@", self.XMLPath);
    }
}

- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
    MYNode* node = [self.elementStack lastObject];

    // Remove node from stack if XML path match found    
    if([node.XMLPath isEqualToString:self.XMLPath]) {
        [self.elementStack removeLastObject];

        NSLog(@"<- %@", self.XMLPath);
    }

    // Pop XML path component
    [self removeLastXMLPathComponent];
}

@end