XML分析器(目标C)

时间:2011-01-02 14:01:12

标签: xml cocoa nsxmlparser

我有一个xml文件,如下所示。我正在使用NSXMLParser,但是我无法解析我的作者和摘要。由于访问权限,我无法编辑xml文件。

任何解决方案?

XML文件:

<book>
<title>Book 1</title>
<author>
<subfield id="a"> Jason </subfield>
<subfield id="b"> Alfonso. </subfield>
</author>
<summary>
<subfield id="a"> Milano </subfield>
<subfield id="b"> Italy </subfield>
</summary>
</book>

我的代码:

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

    if ([elementName isEqualToString:@"book"]) {
        item = [[NSMutableDictionary alloc] init];
    } else if ([elementName isEqualToString:@"title"]) {
        self.title = [[NSMutableString alloc] init];
    } else if ([elementName isEqualToString:@"subfield"]) {
        if ([[attributeDict valueForKey:@"id"] isEqualToString:@"a"]) {
            self.authorName1 = [[NSMutableString alloc] init];
        }
    } else if ([elementName isEqualToString:@"subfield"]) {
        if ([[attributeDict valueForKey:@"id"] isEqualToString:@"b"]) {
                        self.authorName2 = [[NSMutableString alloc] init];
        }
    }
}

我能抓住子场。然而,甚至摘要的子字段是在作者期间抓取的,我不知道该怎么做。我需要两者分开

2 个答案:

答案 0 :(得分:4)

请注意,可能更容易将您的应用基于Core DataNSXML(使用适当的init method of NSXMLDocument),而不是负责解析XML文件。如果您希望这样做,请继续阅读。

为子字段元素使用不同的元素名称将解决clobbering问题。

<book>
  <title>Book 1</title>
  <author>
    <first> Jason </first>
    <last> Alfonso. </last>
  </author>
  <summary>
    <city> Milano </city>
    <country> Italy </country>
  </summary>
</book>

然而,还有更好的方法。

一般来说,要正确解析XML文件,您需要在进程中维护一堆元素。解析元素时,您将创建一个新元素并将其添加到堆栈中。解析元素完成后,从堆栈中弹出一个元素并将其提供给堆栈顶部的元素。您可以使用工厂方法(下面的-nodeWithTag:attributes:parser:)和将元素名称映射到类(下面是elementClasses)的字典,根据元素名称创建不同类的元素。

/* category to return a default object (rather than nil)
   when a key isn't present in a dictionary.
 */
@interface NSDictionary (defaultObject)
-(id)objectForKey:(NSString*)key default:(id)default;
@end

@implementation NSDictionary (defaultObject)
-(id)objectForKey:(NSString*)key default:(id)default {
    id object = [self objectForKey:key];
    if (nil == object) {
        return default;
    }
    return object;
}
@end

/* category to add aliases for stack operations
   to NSMutableArray
 */
@interface NSMutableArray (stack)
-(void)push:(id)object;
-(id)pop;
-(id)top;
@end

@implementation NSMutableArray (stack)
// could also use class_addMethod to alias push & top
-(void)push:(id)object {
    [self addObject:object];
}
-(id)pop {
    id last = [self lastObject];
    [self removeLastObject];
    return last;
}
-(id)top {
    return [self lastObject];
}
@end


// the parser delegate.
@interface ... <NSXMLParserDelegate> {
    NSMutableArray activeElements;
    id item;
   ...
@property (nonatomic,retain) item;
@end

@implementation ...
@synthesize item;

#pragma mark Class members
// map element names to classes
static NSDictionary *elementClasses;

+(void)initialize {
    nodeTypes=[[NSDictionary alloc] initWithObjectsAndKeys:
      // Just an illustrative example of a custom class.
      // You don't necessarily need a Book class.
      [Book class],@"book",
      nil];
}

// if you have other init methods, make sure activeElements is created.
-(id)init {
    if ((self = [super init])) {
        activeElements = [[NSMutableArray alloc] init];
        ...
    }
    return self;
}

-(void)parserDidStartDocument:(NSXMLParser *)parser {
    // add sentinel element so stack isn't empty at start.
    [activeElements push:[self nodeWithTag:@"root" attributes:nil parser:parser]];
}

-(void)parserDidEndDocument:(NSXMLParser *)parser {
    // The parser should ensure only case 1 is reachable, but still...
    switch ([activeElements count]) {
    case 0:
        NSLog(@"Root element removed from stack early.");
        break;
    default:
        NSLog(@"Extra elements in stack at parse end.");
        [activeElements removeObjectsInRange:NSMakeRange(1, activeElements.count-1)];
        // FALLTHRU
    case 1:
        // top item should be the sentinel
        self.item = [activeElements pop];
        if ([item.children count] == 1) {
            // sentinel can safely be discarded if 
            self.item = [item.children objectAtIndex:0];    
        }
        break;
    }
}

#pragma mark Instance methods    
-(void)  parser:(NSXMLParser *)parser 
didStartElement:(NSString *)elementName
   namespaceURI:(NSString *)namespaceURI 
  qualifiedName:(NSString *)qName 
     attributes:(NSDictionary *)attributeDict
{
    [activeElements push:[self nodeWithTag:elementName 
                               attributes:attributeDict 
                                   parser:parser]];
}

- (void)parser:(NSXMLParser *)parser 
 didEndElement:(NSString *)elementName
  namespaceURI:(NSString *)namespaceURI 
 qualifiedName:(NSString *)qName
{
    id element = [activeElements pop];
    if (element.attributes.count == 0 && element.children.count == 0) {
        // simple leafs don't need to be Nodes.
        [activeElements.top setValue:element.value forKey:elementName];
    } else {
        [activeElements.top setValue:element forKey:elementName];
    }
}

-(void)parser:(NSXMLParser*)parser foundCharacters:(NSString*)string {
    activeElements.top.value = string;
}

/* Factory method. Depending on elementName,  create an 
   object of the appropriate type.
 */
-(id)nodeWithTag:elementName attributes:attrs parser:(NSXMLParser*)parser {
    id node =[[[elementClasses objectForKey:elementName 
                                    default:[NSMutableDictionary class]] 
                        alloc] init];
    for (id key in attrs) {
        @try {
            [node setValue:[attrs objectForKey:key] forKey:key];
        }
        @catch (NSException *exc) {
            // TODO: warn user of invalid attribute(s) when parsing is finished
    if ([exc name] == NSUndefinedKeyException) {
                NSLog(@"%d,%d: Set attribute '%@' on a %@, but it doesn't have that property.", 
                      [parser columnNumber], [parser lineNumber],
                      key, elementName);
    } else {
                NSLog(@"%d,%d: Caught %@ when setting %@ on a %@.",
                      [parser columnNumber], [parser lineNumber],
                      [exc name], key, elementName);
            }
        }
    }
    return [node autorelease];
}

-(void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError {
    NSLog(@"parse error: %@", parseError);
    [self abort];
}
-(void)parser:(NSXMLParser *)parser validationErrorOccurred:(NSError *)validError {
    NSLog(@"validation error: %@", validError);
    [self abort];
}

-(void)abort {
    [activeElements removeAllObjects];
}

另一个错误

仔细看看:

} else if ([elementName isEqualToString:@"subfield"]) {
    if ([[attributeDict valueForKey:@"id"] isEqualToString:@"a"]) {
        self.authorName1 = [[NSMutableString alloc] init];
    }
} else if ([elementName isEqualToString:@"subfield"]) {
    if ([[attributeDict valueForKey:@"id"] isEqualToString:@"b"]) {
                    self.authorName2 = [[NSMutableString alloc] init];
    }
}

如果第一次测试成功,你将永远不会达到第二次。这里简单的解决方法是组合块,尽管这仍然会有问题:

} else if ([elementName isEqualToString:@"subfield"]) {
    if ([[attributeDict valueForKey:@"id"] isEqualToString:@"a"]) {
        self.authorName1 = [[NSMutableString alloc] init];
    } else if ([[attributeDict valueForKey:@"id"] isEqualToString:@"b"]) {
        self.authorName2 = [[NSMutableString alloc] init];
    }
}

答案 1 :(得分:2)

下载: https://github.com/Insert-Witty-Name/XML-to-NSDictionary

然后你只需:

NSDictionary *dic = [XMLReader dictionaryForPath:filepath error:nil];

结果是一个NSDictionary * dic,其中包含字典,数组和字符串,具体取决于XML:

{
    book =     {
        author =         {
            first = Jason;
            last = "Alfonso.";
        };
        summary =         {
            city = Milano;
            country = Italy;
        };
        title = "Book 1";
    };
}