Generate Iphone XML Parser


There is no question that Iphone is the mobile platform of choice for many users and by extension to many developers. I’ve been learning Objective C and as with Java (many moons ago), i’ve found it very intimidating, then fun and then as a tool in my tool chest.

After doing some googling i found a now defunct company AppLoop (http://www.apploop.com/) that had an application to generate Iphone applications, basically a custom rss reader, with your own logo and a few customization options

Having a fixation with code generators, i was wondering how easy it would be to enable our TypeWriter project to generate native code for the Iphone.

While writing an offline reader (http://www.ngeosone.com/?page_id=38) for a news paper, a task i’ve found to be very tedious, is to write the code you would need to parse an xml file (sax parser) and all of the value objects you would need to store the information.

So here’s is the challenge, can i make TypeWriter go to an URL address pointing to an xml document and and make it write a custom parser and all value objects?

Well it seems very possible, and here’s is what we are going to do:

1) Write a java class that implements the Explorer interface, this class will read the xml document and store information on the attributes and elements even their cardinality.

2) Write Velocity templates that will use the info generated in step one to create source code

Below you will see how do the templates look, I will work on making this available via a REST api, please send your questions to iphone@ngeosone.com or follow us on twitter as ngeosone

Examples

We need the url of the xml document you want to use as a model for the generator, a name you want to use for this model, a devkey, in this case we are using the one of our demo user, and email to send the results

REST url Sample

http://typewriter.ngeosone.com/api/generate/?devkey=hfu0xIhUgS1uXIF/DbZPnwB6LniwUNkf20iuwSpvUxY=&modelName=NgeosOneRssFeed&description=This+Is+Rest+Test&url=http://www.ngeosone.com/?feed=rss2&solutionId=1&email=youremail@email.com

Test It!

XML Url
Model Name
This model description
Email to send the files

Here are the templates

To generate the header file for the parser

#set($filename=”${baseDir}/${project}/Classes/${feedClassName}XMLParser.h”)
#import “${root.getCleanName()}.h”
#foreach($element1 in $allElements)
#if($element1.needsClass() == true)
#import “${element1.getCleanName()}.h”
#end
#end

@interface ${feedClassName}XMLParser : NSObject {
NSMutableString *currentElementValue;

//Temporary objects
#foreach($element1 in $allElements)
#if($element1.parent.useArray(${element1.getCleanName()}) == true)
//Parent: ${element1.parent.getCleanName()}.h
NSMutableArray *${element1.plural()};
#else
#if($element1.needsClass() == true)
${element1.getCleanName()} *${element1.getCleanName()};
#end
#end
#end
//End of temporary

//This is Root
${root.getCleanName()} *${root.getCleanName()};
}
@property (nonatomic, retain) ${root.getCleanName()} *${root.getCleanName()};
- (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 ;
- (BOOL)loadData;
@end

This is to generate the implementation

#set($filename=”${baseDir}/${project}/Classes/${feedClassName}XMLParser.m”)
#import “${feedClassName}XMLParser.h”

@implementation ${feedClassName}XMLParser
@synthesize ${root.getCleanName()};
- (${feedClassName}XMLParser *) initXMLParser {
[super init];
${root.getCleanName()} = [ [${root.getCleanName()} alloc] init];
//NSLog(@”Initializing”);
return self;
}

- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedName attributes:(NSDictionary *)attributeDict {
//NSLog(@”Starting Element: %@”, elementName);
#foreach($element1 in $allElements)
#if($element1.needsClass() == true || $element1.parent.useArray(${element1.getCleanName()}) == true)

if([elementName isEqualToString:@"${element1.name}"]) {
#if($element1.parent.useArray(${element1.getCleanName()}) == true)
//Parent: ${element1.parent.getCleanName()}.h
${element1.plural()} = [[NSMutableArray alloc] init];
#else
#if($element1.needsClass() == true)
${element1.getCleanName()} = [[${element1.getCleanName()} alloc] init];
#end
#end
#foreach($att in $element1.attributes)
${element1.getCleanName()}.${att.getCleanName()} = [attributeDict valueForKey:@"${att.name}"];
#end
}
#end
#end

}

- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
@try{
[currentElementValue setString : [currentElementValue stringByReplacingOccurrencesOfString:@"\n" withString:@""]];
//NSLog(@”Ending %@ with %@”,elementName,currentElementValue);
#foreach($child in $allElements)
#if($child.getCleanName() != $root.getCleanName())

if([elementName isEqualToString:@"${child.name}"]) {
#if($child.parent.useArray(${child.getCleanName()}) == true)
[${child.parent.getCleanName()} addObject: ${child.getCleanName()}];
[${child.getCleanName()} release];
#else
#if($child.needsClass() == true)
${child.parent.getCleanName()}.${child.getCleanName()} = ${child.getCleanName()};
[${child.getCleanName()} release];
#else
${child.parent.getCleanName()}.${child.getCleanName()} = currentElementValue;
#end
#end
}
#end
#end
//NSLog(@”Ending %@ with %@”,elementName,currentElementValue);
}@catch (NSException *exception) {
//NSLog(@”${feedClassName}: Caught %@: %@”, [exception name], [exception reason]);
}
[currentElementValue release];
currentElementValue = nil;
}

- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
//NSLog(@”found characters %@”,string);
if(!currentElementValue)
currentElementValue = [[NSMutableString alloc] initWithString:string];
else
[currentElementValue appendString:string];
//NSLog(@”currentElementValue %@”,currentElementValue);
}

/*
- (NSString *) extractName: (NSString *)value {
NSRange srange = [value rangeOfString : @"("];
NSRange erange = [value rangeOfString : @")"];
//NSLog(@”Name contains no %d”,srange.location);
if( srange.length == 0 || erange.length == 0){
//NSLog(@”Name contains no (—)”);
}else{
NSRange deleteRange = NSMakeRange(srange.location, (erange.location – srange.location)+1);
[value deleteCharactersInRange : deleteRange];
}
return value;
}
*/

- (BOOL) loadData {
@try{
NSURL *url = [[NSURL alloc] initWithString:@”${feedUrl}”];
NSXMLParser *xmlParser = [[NSXMLParser alloc] initWithContentsOfURL:url];

//Set delegate
[xmlParser setDelegate:self];
//Start parsing the XML file
BOOL success = [xmlParser parse];
if(success){
NSLog(@”${feedClassName}XMLParser: Data Loaded from ${feedURL}”);
}else{
NSLog(@”${feedClassName}XMLParser: Error parsing ${feedURL}”);
}
return success;
}@catch (NSException *exception) {
NSLog(@”SB${feedClassName}: Caught %@: %@”, [exception name], [exception reason]);
}
}

- (void) dealloc {
[currentElementValue release];
[super dealloc];
}

@end

Trackbacks

Leave a Reply