Reputation: 11
I have an xml file, shown below. I'm using NSXMLParser, however I'm not able to parse my author and summary. Because of access rights, I cannot edit the xml file.
Any solution?
XML File:
<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>
My Code:
- (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];
}
}
}
i'm able to grab subfield. however even summary's subfield is grab during the author which i do not what to. i need both to be separated
Upvotes: 1
Views: 6416
Reputation: 77400
Note that it might be easier to base your app on Core Data or NSXML (using an appropriate init method of NSXMLDocument
) rather than taking responsibility for parsing the XML files. If you wish to do so, read on.
Using different element names for the subfield elements will fix the clobbering problem.
<book>
<title>Book 1</title>
<author>
<first> Jason </first>
<last> Alfonso. </last>
</author>
<summary>
<city> Milano </city>
<country> Italy </country>
</summary>
</book>
However, there are better ways still.
Generally speaking, to properly parse an XML file you'll need to maintain a stack of elements in-process. When parsing of an element starts, you create a new element and add it to the stack. When parsing of an element finishes, you pop an element off the the stack and give it to the element now on the top of the stack. You can create elements of different classes based on the element name by using a factory method (below, -nodeWithTag:attributes:parser:
) and a dictionary that maps element names to classes (below, 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];
}
Take a close look at:
} 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];
}
}
If the first test succeeds, you'll never reach the second. The trivial fix here is to combine the blocks, though this will still have the clobber issue:
} 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];
}
}
Upvotes: 4
Reputation: 161
Download: https://github.com/Insert-Witty-Name/XML-to-NSDictionary
Then you simply do :
NSDictionary *dic = [XMLReader dictionaryForPath:filepath error:nil];
Result is a NSDictionary *dic with dictionaries, arrays and strings inside, depending of the XML:
{
book = {
author = {
first = Jason;
last = "Alfonso.";
};
summary = {
city = Milano;
country = Italy;
};
title = "Book 1";
};
}
Upvotes: 2