I am building an app that parses an rss feed. In the app there are two different types of feeds with different names for the elements in the feed, so I have created an NSXMLParser NSObject that takes the name of the elements of each feed before parsing. Here is my code:
NewsFeedParser.h
#import
#interface NewsFeedParser : NSObject {
NSInteger NewsSelectedCategory;
NSXMLParser *NSXMLNewsParser;
NSMutableArray *newsCategories;
NSMutableDictionary *NewsItem;
NSMutableString *NewsCurrentElement, *NewsCurrentElement1, *NewsCurrentElement2, *NewsCurrentElement3;
NSString *NewsItemType, *NewsElement1, *NewsElement2, *NewsElement3;
NSInteger NewsNumElements;
}
- (void) parseXMLFileAtURL:(NSString *)URL;
#property(nonatomic, retain) NSString *NewsItemType;
#property(nonatomic, retain) NSString *NewsElement1;
#property(nonatomic, retain) NSString *NewsElement2;
#property(nonatomic, retain) NSString *NewsElement3;
#property(nonatomic, retain) NSMutableArray *newsCategories;
#property(assign, nonatomic) NSInteger NewsNumElements;
#end
NewsFeedParser.m
#import "NewsFeedParser.h"
#implementation NewsFeedParser
#synthesize NewsItemType;
#synthesize NewsElement1;
#synthesize NewsElement2;
#synthesize NewsElement3;
#synthesize newsCategories;
#synthesize NewsNumElements;
- (void)parserDidStartDocument:(NSXMLParser *)parser{
}
- (void)parseXMLFileAtURL:(NSString *)URL
{
newsCategories = [[NSMutableArray alloc] init];
URL = [URL stringByReplacingOccurrencesOfString:#" " withString:#""];
URL = [URL stringByReplacingOccurrencesOfString:#"\n" withString:#""];
URL = [URL stringByReplacingOccurrencesOfString:#" " withString:#""];
//you must then convert the path to a proper NSURL or it won't work
NSURL *xmlURL = [NSURL URLWithString:URL];
// here, for some reason you have to use NSClassFromString when trying to alloc NSXMLParser, otherwise you will get an object not found error
// this may be necessary only for the toolchain
[[NSURLCache sharedURLCache] setMemoryCapacity:0];
[[NSURLCache sharedURLCache] setDiskCapacity:0];
NSXMLNewsParser = [[NSXMLParser alloc] initWithContentsOfURL:xmlURL];
// Set self as the delegate of the parser so that it will receive the parser delegate methods callbacks.
[NSXMLNewsParser setDelegate:self];
// Depending on the XML document you're parsing, you may want to enable these features of NSXMLParser.
[NSXMLNewsParser setShouldProcessNamespaces:NO];
[NSXMLNewsParser setShouldReportNamespacePrefixes:NO];
[NSXMLNewsParser setShouldResolveExternalEntities:NO];
[NSXMLNewsParser parse];
[NSXMLNewsParser release];
}
- (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError {
NSString * errorString = [NSString stringWithFormat:#"Unable to download story feed from web site (Error code %i )", [parseError code]];
NSLog(#"error parsing XML: %#", errorString);
UIAlertView * errorAlert = [[UIAlertView alloc] initWithTitle:#"Error loading content" message:errorString delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil];
[errorAlert show];
[errorAlert release];
[errorString release];
}
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict{
NewsCurrentElement = [elementName copy];
if ([elementName isEqualToString:NewsItemType])
{
// clear out our story item caches...
NewsItem = [[NSMutableDictionary alloc] init];
NewsCurrentElement1 = [[NSMutableString alloc] init];
NewsCurrentElement2 = [[NSMutableString alloc] init];
if(NewsNumElements == 3)
{
NewsCurrentElement3 = [[NSMutableString alloc] init];
}
}
}
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName{
if ([elementName isEqualToString:NewsItemType])
{
// save values to an item, then store that item into the array...
[NewsItem setObject:NewsCurrentElement1 forKey:NewsElement1];
[NewsItem setObject:NewsCurrentElement2 forKey:NewsElement2];
if(NewsNumElements == 3)
{
[NewsItem setObject:NewsCurrentElement3 forKey:NewsElement3];
}
[newsCategories addObject:[[NewsItem copy] autorelease]];
[NewsCurrentElement release];
[NewsCurrentElement1 release];
[NewsCurrentElement2 release];
if(NewsNumElements == 3)
{
[NewsCurrentElement3 release];
}
[NewsItem release];
}
}
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
//NSLog(#"found characters: %#", string);
// save the characters for the current item...
if ([NewsCurrentElement isEqualToString:NewsElement1]) {
[NewsCurrentElement1 appendString:string];
} else if ([NewsCurrentElement isEqualToString:NewsElement2]) {
[NewsCurrentElement2 appendString:string];
} else if (NewsNumElements == 3 && [NewsCurrentElement isEqualToString:NewsElement3])
{
[NewsCurrentElement3 appendString:string];
}
}
- (void)dealloc {
[super dealloc];
[newsCategories release];
[NewsItemType release];
[NewsElement1 release];
[NewsElement2 release];
[NewsElement3 release];
}
When I create an instance of the class I do like so:
NewsFeedParser *categoriesParser = [[NewsFeedParser alloc] init];
if(newsCat == 0)
{
categoriesParser.NewsItemType = #"article";
categoriesParser.NewsElement1 = #"category";
categoriesParser.NewsElement2 = #"catid";
}
else
{
categoriesParser.NewsItemType = #"article";
categoriesParser.NewsElement1 = #"category";
categoriesParser.NewsElement2 = #"feedUrl";
}
[categoriesParser parseXMLFileAtURL:feedUrl];
newsCategories = [[NSMutableArray alloc] initWithArray:categoriesParser.newsCategories copyItems:YES];
[self.tableView reloadData];
[categoriesParser release];
If I run the app with the leaks instrument, the leaks point to the [NSXMLNewsParser parse] call in the NewsFeedParser.m.
Here is a screen shot of the Leaks instrument with the NSCFStrings leaking:
http://img139.imageshack.us/img139/3997/leaks.png
For the life of me I can't figure out where these leaks are coming from. Any help would be greatly appreciated.
The leak occurred in the didStartElement method. I was copying elementName without releasing it.
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict{
NewsCurrentElement = [[elementName copy] autorelease];
if ([elementName isEqualToString:NewsItemType])
{
// clear out our story item caches...
NewsItem = [[NSMutableDictionary alloc] init];
NewsCurrentElement1 = [[NSMutableString alloc] init];
NewsCurrentElement2 = [[NSMutableString alloc] init];
if(NewsNumElements == 3)
{
NewsCurrentElement3 = [[NSMutableString alloc] init];
}
}
}
You might also want to release (if necessary) the allocated NSMutableString properties before allocating another NSMutableString into the property like so:
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict {
if (NewsCurrentElement) {
[NewsCurrentElement release], NewsCurrentElement = nil;
}
NewsCurrentElement = [[elementName copy] autorelease];
if ([elementName isEqualToString:NewsItemType]) {
// clear out our story item caches...
if (NewsItem) {
[NewsItem release], NewsItem = nil;
}
NewsItem = [[NSMutableDictionary alloc] init];
if (NewsCurrentElement1) {
[NewsCurrentElement1 release], NewsCurrentElement1 = nil;
}
NewsCurrentElement1 = [[NSMutableString alloc] init];
if (NewsCurrentElement2) {
[NewsCurrentElement2 release], NewsCurrentElement2 = nil;
}
NewsCurrentElement2 = [[NSMutableString alloc] init];
if(NewsNumElements == 3) {
if (NewsCurrentElement3) {
[NewsCurrentElement3 release], NewsCurrentElement3 = nil;
}
NewsCurrentElement3 = [[NSMutableString alloc] init];
}
}
}
Related
I'm using three20 to create image viewer. First i'm creating list of albums from the sql db and when user selects any album, its url string is passed to the this code that creates thums of available pics on network using XML Parser. everything works fine but when user goes back to the album list and selects another album. app crashes with 'Thread 1: Program received singal: "EXC+BAD_ACCESS" in main.m. plus XCode Product Analyze gives potential memory leak where i'm creating photoSource in the viewDidLoad. Here is the code
#import "AlbumController.h"
#import "PhotoSource.h"
#import "Photo.h"
#import "AlbumInfo.h"
#import "AlbumDatabase.h"
#implementation AlbumController
#synthesize albumName;
#synthesize urlAddress;
#synthesize images;
- (void)viewDidLoad
{
[super viewDidLoad];
// NSLog(#"%#", self.urlAddress);
[self createPhotos]; // method to set up the photos array
self.photoSource = [[PhotoSource alloc]
initWithType:PhotoSourceNormal
title:self.albumName
photos:images
photos2:nil];
self.navigationController.navigationBar.tintColor = [UIColor blackColor];
}
- (void)viewDidUnload
{
[super viewDidUnload];
// release and set to nil
}
-(void)createPhotos
{
if ([stories count] == 0)
{
NSString *path = self.urlAddress;
[self parseXMLFileAtURL:path];
}
images = [NSMutableArray arrayWithCapacity:[stories count]]; // needs to be mutable
for (int i = 0; i < [stories count]; i++)
{
NSString *img = [[stories objectAtIndex:i] objectForKey:#"image"];
img = [img stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]];
//NSString * caption = [[stories objectAtIndex:i] objectForKey:#"caption"];
//caption = [caption stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]];
[images addObject:[[[Photo alloc] initWithURL:img smallURL:img size:CGSizeMake(320, 212)] autorelease]];
}
}
#pragma mark -
#pragma mark XML Parser Implementation
- (void)parserDidStartDocument:(NSXMLParser *)parser{
//NSLog(#"found file and started parsing");
}
- (void)parseXMLFileAtURL:(NSString *)URL
{
stories = [[NSMutableArray alloc] init];
//you must then convert the path to a proper NSURL or it won't work
NSURL *xmlURL = [NSURL URLWithString:URL];
// here, for some reason you have to use NSClassFromString when trying to alloc NSXMLParser, otherwise you will get an object not found error
// this may be necessary only for the toolchain
rssParser = [[NSXMLParser alloc] initWithContentsOfURL:xmlURL];
// Set self as the delegate of the parser so that it will receive the parser delegate methods callbacks.
[rssParser setDelegate:self];
// Depending on the XML document you're parsing, you may want to enable these features of NSXMLParser.
[rssParser setShouldProcessNamespaces:NO];
[rssParser setShouldReportNamespacePrefixes:NO];
[rssParser setShouldResolveExternalEntities:NO];
[rssParser parse];
}
- (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError {
NSString * errorString = [NSString stringWithFormat:#"Unfortunately it is not possible to load Pictures. Please check Internet Connection. (Error code %i )", [parseError code]];
//NSLog(#"error parsing XML: %#", errorString);
UIAlertView * errorAlert = [[UIAlertView alloc] initWithTitle:#"Failed to load the feed." message:errorString delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil];
[errorAlert show];
[errorAlert release];
}
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict
{
//NSLog(#"found this element: %#", elementName);
currentElement = [elementName copy];
if ([elementName isEqualToString:#"item"]) {
// clear out our story item caches...
item = [[NSMutableDictionary alloc] init];
currentCaption = [[NSMutableString alloc] init];
//currentThumbnail = [[NSMutableString alloc] init];
currentImage = [[NSMutableString alloc] init];
}
}
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName{
//NSLog(#"ended element: %#", elementName);
if ([elementName isEqualToString:#"item"]) {
// save values to an item, then store that item into the array...
//[item setObject:currentThumbnail forKey:#"thumbnail"];
//[item setObject:currentCaption forKey:#"caption"];
[item setObject:currentImage forKey:#"image"];
[stories addObject:[[item copy] autorelease]];
}
}
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string{
// save the characters for the current item...
if ([currentElement isEqualToString:#"thumbnail"]) {
//[currentThumbnail appendString:string];
}// else if ([currentElement isEqualToString:#"caption"]) {
//[currentCaption appendString:string];
//}
else if ([currentElement isEqualToString:#"image"]) {
[currentImage appendString:string];
}
}
- (void)parserDidEndDocument:(NSXMLParser *)parser {
NSLog(#"all done!");
NSLog(#"stories array has %d items", [stories count]);
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
{
if (toInterfaceOrientation == UIInterfaceOrientationPortrait ||
toInterfaceOrientation == UIInterfaceOrientationLandscapeLeft ||
toInterfaceOrientation == UIInterfaceOrientationLandscapeRight)
{
return YES;
}
else
{
return NO;
}
}
#pragma mark -
#pragma mark Memory Management
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning]; // Releases the view if it doesn't have a superview
// Release anything that's not essential, such as cached data
}
- (void)dealloc {
[currentElement release];
[rssParser release];
[stories release];
[item release];
[currentCaption release];
//[currentThumbnail release];
[currentImage release];
[images release];
[stories release];
[super dealloc];
}
#end
and here is the didSelectRowAtIndexPath thats pushing this view
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
AlbumInfo *info = [_albumInfos objectAtIndex:indexPath.row];
AlbumController *albumController = [[AlbumController alloc] init];
albumController.urlAddress = info.address;
albumController.albumName = info.name;
[self.navigationController pushViewController:albumController animated:YES];
[albumController release];
}
Here is the code for AlbumController.h
#import <Foundation/Foundation.h>
#import "Three20/Three20.h"
#interface AlbumController : TTThumbsViewController <NSXMLParserDelegate>
{
NSString *albumName;
NSString *urlAddress;
// images
NSMutableArray *images;
// parser
NSXMLParser * rssParser;
NSMutableArray * stories;
NSMutableDictionary * item;
NSString * currentElement;
NSMutableString * currentImage;
NSMutableString * currentCaption;
}
#property (nonatomic, strong) NSString *albumName;
#property (nonatomic, strong) NSString *urlAddress;
#property (nonatomic, retain) NSMutableArray *images;
- (void)createPhotos;
- (void)parseXMLFileAtURL:(NSString *)URL;
#end
used this tutorial http://www.raywenderlich.com/1430/how-to-use-the-three20-photo-viewer
Need help solving this memory leak and need to know why its crashing.
Thanks
Simple. In Xcode 4.0+, Just click-hold on the Run icon, and press Profile. It'll open up Instruments, and you'll want Zombies. Then navigate your app to where the crash happened before, and this time, it'll show up in Instruments with the caller, and all the information about it.
The memory leak in viewDidLoad is caused by the following line:
self.photoSource = [[PhotoSource alloc]
initWithType:PhotoSourceNormal
title:self.albumName
photos:images
photos2:nil];
[PhotoSource alloc] returns an object you own (with a retain count of +1).
initWithType:title:photos:photos2: does not change the retain count.
So viewDidLoad is left with an object it owns, but no pointer to it. To balance the alloc you should send an autorelease message:
self.photoSource = [[[PhotoSource alloc]
initWithType:PhotoSourceNormal
title:self.albumName
photos:images
photos2:nil] autorelease];
I am currently having a problem with parsing information from an XML into an NSMutableArray, i have pasted the code below which is for the parser this retrieves the document off the internet and constructs the array. I am then trying to copy the items from the xml parser into an NSMutableArray for searching, but every time i try to copy the array it returns a struct error so i have removed the changes for now.
Here is the code for the parser and the current code for the viewdidload in the main view controller, if anyone can help i would really appreciate it.
#import "BlogRssParser.h"
#import "BlogRss.h"
#implementation BlogRssParser
#synthesize currentItem = _currentItem;
#synthesize currentItemValue = _currentItemValue;
#synthesize rssItems = _rssItems;
#synthesize delegate = _delegate;
#synthesize retrieverQueue = _retrieverQueue;
- (id)init{
if(![super init]){
return nil;
}
_rssItems = [[NSMutableArray alloc]init];
return self;
}
- (NSOperationQueue *)retrieverQueue {
if(nil == _retrieverQueue) {
_retrieverQueue = [[NSOperationQueue alloc] init];
_retrieverQueue.maxConcurrentOperationCount = 1;
}
return _retrieverQueue;
}
- (void)startProcess{
SEL method = #selector(fetchAndParseRss);
[[self rssItems] removeAllObjects];
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self
selector:method
object:nil];
[self.retrieverQueue addOperation:op];
[op release];
}
-(BOOL)fetchAndParseRss{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
//To suppress the leak in NSXMLParser
[[NSURLCache sharedURLCache] setMemoryCapacity:0];
[[NSURLCache sharedURLCache] setDiskCapacity:0];
NSURL *url = [NSURL URLWithString:#"http://xxxxxxxxx.co.uk/rssparser/Data.xml"];
BOOL success = NO;
NSXMLParser *parser = [[NSXMLParser alloc] initWithContentsOfURL:url];
[parser setDelegate:self];
[parser setShouldProcessNamespaces:YES];
[parser setShouldReportNamespacePrefixes:YES];
[parser setShouldResolveExternalEntities:NO];
success = [parser parse];
[parser release];
[pool drain];
return success;
}
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qualifiedName attributes:(NSDictionary *)attributeDict{
if(nil != qualifiedName){
elementName = qualifiedName;
}
if ([elementName isEqualToString:#"item"]) {
self.currentItem = [[[BlogRss alloc]init]autorelease];
} else if([elementName isEqualToString:#"title"] ||
[elementName isEqualToString:#"description"] ||
[elementName isEqualToString:#"mediaUrl"] ||
[elementName isEqualToString:#"link"] ||
[elementName isEqualToString:#"guid"] ||
[elementName isEqualToString:#"pubDate"]) {
self.currentItemValue = [NSMutableString string];
} else {
self.currentItemValue = nil;
}
}
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
if(nil != qName){
elementName = qName;
}
if([elementName isEqualToString:#"title"]){
self.currentItem.title = self.currentItemValue;
}else if([elementName isEqualToString:#"description"]){
self.currentItem.description = self.currentItemValue;
}else if([elementName isEqualToString:#"link"]){
self.currentItem.linkUrl = self.currentItemValue;
}else if([elementName isEqualToString:#"mediaUrl"]){
self.currentItem.mediaUrl = self.currentItemValue;
}else if([elementName isEqualToString:#"guid"]){
self.currentItem.guidUrl = self.currentItemValue;
}else if([elementName isEqualToString:#"pubDate"]){
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:#"yyyy-MM-dd'T'HH:mm:ss'Z'"];
self.currentItem.pubDate = [formatter dateFromString:self.currentItemValue];
[formatter release];
}else if([elementName isEqualToString:#"item"]){
[[self rssItems] addObject:self.currentItem];
}
}
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
if(nil != self.currentItemValue){
[self.currentItemValue appendString:string];
}
}
- (void)parser:(NSXMLParser *)parser foundCDATA:(NSData *)CDATABlock{
//Not needed for now
}
- (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError{
if(parseError.code != NSXMLParserDelegateAbortedParseError) {
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
[(id)[self delegate] performSelectorOnMainThread:#selector(processHasErrors)
withObject:nil
waitUntilDone:NO];
}
}
- (void)parserDidEndDocument:(NSXMLParser *)parser {
[(id)[self delegate] performSelectorOnMainThread:#selector(processCompleted)
withObject:nil
waitUntilDone:NO];
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
}
-(void)dealloc{
self.currentItem = nil;
self.currentItemValue = nil;
self.delegate = nil;
[_rssItems release];
[super dealloc];
}
#end
BlogRSSParser.h
#import <Foundation/Foundation.h>
#class BlogRss;
#protocol BlogRssParserDelegate;
#interface BlogRssParser : NSObject <NSXMLParserDelegate>{
BlogRss * _currentItem;
NSMutableString * _currentItemValue;
NSMutableArray * _rssItems;
id<BlogRssParserDelegate> _delegate;
NSOperationQueue *_retrieverQueue;
}
#property(nonatomic, retain) BlogRss * currentItem;
#property(nonatomic, retain) NSMutableString * currentItemValue;
#property(readonly) NSMutableArray * rssItems;
#property(nonatomic, assign) id<BlogRssParserDelegate> delegate;
#property(nonatomic, retain) NSOperationQueue *retrieverQueue;
- (void)startProcess;
#end
#protocol BlogRssParserDelegate <NSObject>
-(void)processCompleted;
-(void)processHasErrors;
#end
ViewDidLoad
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad {
[super viewDidLoad];
[self toolbarInit];
_rssParser = [[BlogRssParser alloc]init];
self.rssParser.delegate = self;
[[self rssParser]startProcess];
}
You rssItems array is marked "readonly".
Change:
#property(readonly) NSMutableArray * rssItems;
to
#property(nonatomic, retain) NSMutableArray * rssItems;
I am new in xml parsing.I am totaly cofused how many methods we should have to require for xml parsing and what does use of that method.I want to send input to the webservied and by use of xml parsing i want to display result in my text field.
See this questions for resources on how to pick an XML parser for your iOS application:
Choosing the right IOS XML parser
I choose TouchXML for my projects. It is a DOM parser and has XPath support:
https://github.com/TouchCode/TouchXML
You should use the following methods:
- (void)parseXMLFileAtURL:(NSString *)URL { //own method from me, URL could be local file or internet website
itemsOfFeed = [[NSMutableArray alloc] init];
NSURL *xmlURL = [NSURL URLWithString:URL];
feedParser = [[NSXMLParser alloc] initWithContentsOfURL:xmlURL];
[feedParser setDelegate:self];
[feedParser setShouldProcessNamespaces:NO];
[feedParser setShouldReportNamespacePrefixes:NO];
[feedParser setShouldResolveExternalEntities:NO];
[feedParser parse];
}
- (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError {
//in case of an error
}
- (void)parserDidStartDocument:(NSXMLParser *)parser {
// start to parse xml
}
-(void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict {
feedElement = [elementName copy];
if([elementName isEqualToString:#"item"]) { //main xml tag
item = [[NSMutableDictionary alloc] init];
feedTitle = [[NSMutableString alloc] init];
feedDate = [[NSMutableString alloc] init];
feedText = [[NSMutableString alloc] init];
feedLink = [[NSMutableString alloc] init];
}
}
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
if([feedElement isEqualToString:#"title"]) {
[feedTitle appendString:string];
} else if([feedElement isEqualToString:#"link"]) { // some examples of tags
[feedLink appendString:string];
} else if([feedElement isEqualToString:#"content:encoded"]) {
[feedText appendString:string];
} else if([feedElement isEqualToString:#"pubDate"]) {
[feedDate appendString:string];
}
}
- (void) parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
if([elementName isEqualToString:#"item"]) {
[item setObject:feedTitle forKey:#"title"];
[item setObject:feedDate forKey:#"date"];
[item setObject:feedText forKey:#"text"];
[item setObject:feedLink forKey:#"link"];
[itemsOfFeed addObject:item];
}
}
- (void)parserDidEndDocument:(NSXMLParser *)parser {
[self.myTableView reloadData]; // for example reload table view
[self writeArrayToFile]; //or write to a local property list
}
in your header file:
NSMutableArray *itemsOfFeed;
NSXMLParser *feedParser;
NSMutableDictionary *item;
NSMutableString *feedElement;
NSMutableString *feedTitle, feedDate, feedText, feedLink; //strings of your tags
Then you have:
NSArray
NSDictionary
object a
object b
object c
Just access 'object a' and put it in your text field
hopefully this code example helps you
Here is the very helpful tutorial.
http://www.edumobile.org/iphone/iphone-programming-tutorials/parsing-an-xml-file/
Hi I would personally prefer to use NSXMLParser.
Write your own code which uses NSXMLParserDelegate methods implementation.
But still there are some third party libraries available. Here is a nice example to compare how all those parser differs from each other and also having nice explanation. Code is also available over there.
Hope this will be helpful to you.
-Mrunal
i have a demo app for parsing static XML to UITableView, I think this one will surely will help you.
Availiable third party libraries available below, depend on developer choice this, which is best?
NSXMLParser
libxml2
TBXMLParser
TouchXMLParser
KissXMLParser
TinyXMLParser
GDataXMLParser
And you can also get more information from this great tutorial link
http://www.raywenderlich.com/553/how-to-chose-the-best-xml-parser-for-your-iphone-project
Try this:
XMLReader.h
//
// XMLReader.h
//
//
#import <Foundation/Foundation.h>
#interface XMLReader : NSObject <NSXMLParserDelegate>
{
NSMutableArray *dictionaryStack;
NSMutableString *textInProgress;
NSError *errorPointer;
}
+ (NSDictionary *)dictionaryForPath:(NSString *)path error:(NSError **)errorPointer;
+ (NSDictionary *)dictionaryForXMLData:(NSData *)data error:(NSError **)errorPointer;
+ (NSDictionary *)dictionaryForXMLString:(NSString *)string error:(NSError **)errorPointer;
#end
XMLReader.m
//
// XMLReader.m
//
#import "XMLReader.h"
//NSString *const kXMLReaderTextNodeKey = #"text";
#interface XMLReader (Internal)
- (id)initWithError:(NSError **)error;
- (NSDictionary *)objectWithData:(NSData *)data;
#end
#implementation XMLReader
#pragma mark -
#pragma mark Public methods
+ (NSDictionary *)dictionaryForPath:(NSString *)path error:(NSError **)errorPointer
{
NSString *fullpath = [[NSBundle bundleForClass:self] pathForResource:path ofType:#"xml"];
NSData *data = [[NSFileManager defaultManager] contentsAtPath:fullpath];
NSDictionary *rootDictionary = [XMLReader dictionaryForXMLData:data error:errorPointer];
return rootDictionary;
}
+ (NSDictionary *)dictionaryForXMLData:(NSData *)data error:(NSError **)error
{
XMLReader *reader = [[XMLReader alloc] initWithError:error];
NSDictionary *rootDictionary = [reader objectWithData:data];
[reader release];
return rootDictionary;
}
+ (NSDictionary *)dictionaryForXMLString:(NSString *)string error:(NSError **)error
{
NSArray* lines = [string componentsSeparatedByString:#"\n"];
NSMutableString* strData = [NSMutableString stringWithString:#""];
for (int i = 0; i < [lines count]; i++)
{
[strData appendString:[[lines objectAtIndex:i] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]];
}
NSData *data = [strData dataUsingEncoding:NSUTF8StringEncoding];
return [XMLReader dictionaryForXMLData:data error:error];
}
#pragma mark -
#pragma mark Parsing
- (id)initWithError:(NSError **)error
{
if ((self = [super init]))
{
errorPointer = *error;
}
return self;
}
- (void)dealloc
{
[dictionaryStack release];
[textInProgress release];
[super dealloc];
}
- (NSDictionary *)objectWithData:(NSData *)data
{
// Clear out any old data
[dictionaryStack release];
[textInProgress release];
dictionaryStack = [[NSMutableArray alloc] init];
textInProgress = [[NSMutableString alloc] init];
// Initialize the stack with a fresh dictionary
[dictionaryStack addObject:[NSMutableDictionary dictionary]];
// Parse the XML
NSXMLParser *parser = [[NSXMLParser alloc] initWithData:data];
parser.delegate = self;
BOOL success = [parser parse];
[parser release];
// Return the stack's root dictionary on success
if (success){
NSDictionary *resultDict = [dictionaryStack objectAtIndex:0];
return resultDict;
}
return nil;
}
# pragma mark
# pragma mark - NSXMLParserDelegate methods
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict
{
// Get the dictionary for the current level in the stack
NSMutableDictionary *parentDict = [dictionaryStack lastObject];
// Create the child dictionary for the new element
NSMutableDictionary *childDict = [NSMutableDictionary dictionary];
// Initialize child dictionary with the attributes, prefixed with '#'
for (NSString *key in attributeDict) {
[childDict setValue:[attributeDict objectForKey:key]
forKey:key];
}
// If there's already an item for this key, it means we need to create an array
id existingValue = [parentDict objectForKey:elementName];
if (existingValue){
NSMutableArray *array = nil;
if ([existingValue isKindOfClass:[NSMutableArray class]]){
// The array exists, so use it
array = (NSMutableArray *) existingValue;
}
else{
// Create an array if it doesn't exist
array = [NSMutableArray array];
[array addObject:existingValue];
// Replace the child dictionary with an array of children dictionaries
[parentDict setObject:array forKey:elementName];
}
// Add the new child dictionary to the array
[array addObject:childDict];
}
else{
// No existing value, so update the dictionary
[parentDict setObject:childDict forKey:elementName];
}
// Update the stack
[dictionaryStack addObject:childDict];
}
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
{
// Update the parent dict with text info
NSMutableDictionary *dictInProgress = [dictionaryStack lastObject];
// Pop the current dict
[dictionaryStack removeLastObject];
// Set the text property
if ([textInProgress length] > 0){
if ([dictInProgress count] > 0){
[dictInProgress setObject:textInProgress forKey:kXMLReaderTextNodeKey];
}
else{
// Given that there will only ever be a single value in this dictionary, let's replace the dictionary with a simple string.
NSMutableDictionary *parentDict = [dictionaryStack lastObject];
id parentObject = [parentDict objectForKey:elementName];
// Parent is an Array
if ([parentObject isKindOfClass:[NSArray class]]){
[parentObject removeLastObject];
[parentObject addObject:textInProgress];
}
// Parent is a Dictionary
else{
[parentDict removeObjectForKey:elementName];
[parentDict setObject:textInProgress forKey:elementName];
}
}
// Reset the text
[textInProgress release];
textInProgress = [[NSMutableString alloc] init];
}
// If there was no value for the tag, and no attribute, then remove it from the dictionary.
else if ([dictInProgress count] == 0){
NSMutableDictionary *parentDict = [dictionaryStack lastObject];
[parentDict removeObjectForKey:elementName];
}
}
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
// Build the text value
[textInProgress appendString:[string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]];
}
- (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError
{
// Set the error pointer to the parser's error object
if (errorPointer)
errorPointer = parseError;
}
#end
Use it:
NSMutableDictionary *yourDic= (NSMutableDictionary *)[XMLReader dictionaryForXMLData:yourData error:&error];
I have an xml file I am parsing.
NSURL *url = [NSURL URLWithString:#"url.xml"];
NSURLRequest *request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:60];
self.downloadData = [[NSMutableData alloc] initWithLength:0];
self.connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:YES];
I have all the connections working and I thought I had the parsing working but I am having issues and do not know what I am doing wrong.
my didStartElement:
-(void) parser:(NSXMLParser *) parser didStartElement:(NSString *) elementName namespaceURI:(NSString *) namespaceURI qualifiedName:(NSString *) qName attributes:(NSDictionary *) attributeDict {
if ([elementName isEqualToString:kimgurl])
{
elementFound = YES;
}
}
foundCharacter:
-(void)parser:(NSXMLParser *) parser foundCharacters:(NSString *)string {
if (elementFound == YES) {
if(!currentValue)
{
currentValue = [[NSMutableString alloc] init];
}
[currentValue appendString: string];
}
}
then didEndElement:
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
if (elementFound) {
if ([elementName isEqualToString:kimgurl]) {
NSLog(#"imgurl: %#", currentValue);
imageItems.imageURL = currentValue;
NSLog(#"elname: %#", elementName);
NSLog(#"img: %#", kimgurl);
[currentValue setString:#""];
NSLog(#"imageitem: %#", imageItems.imageURL);
}
}
}
I have the NSLogs there because imageItems.imageURL is null. This is a class file that is like this.
ImageItems.h
#interface ImageItems : NSObject {
//parsed data
NSString *imageURL;
}
#property (nonatomic, retain) NSString *imageURL;
#end
ImageItems.m
#import "ImageItems.h"
#implementation ImageItems
#synthesize imageURL;
-(void)dealloc
{
[imageURL release];
[super dealloc];
}
#end
As you can tell by my code I am new to objective-c.
currentValue has the value Im looking for. Why is imageItems null? What am I missing?
I don't see the assignment of "imageItems" anywhere in your code. You probably need to assign it to an instance of ImageItems at some point. Perhaps with self.imageItems = [[[ImageItems alloc] init] autorelease] or imageItems = [[ImageItems alloc] init];?
Greetings,
I have a problem with adding an object from parser to mutable array.
It was working fine until I moved MtableArray from AppDelegate to ViewController. This is where I call parser (MutableArray is inside this View also):
NSURL *url = [[NSURL alloc] initWithString:#"http://example.com"];
NSXMLParser *xmlParser = [[NSXMLParser alloc] initWithContentsOfURL:url];
parser = [[XMLParser alloc] init];
[xmlParser setDelegate:parser];
[xmlParser parse];
...and this is inside parser where objects should be added:
if([elementName isEqualToString:#"Element"]) {
[viewController.marray addObject:parsedObj];
[parsedObj release];
parsedObj = nil;
}
marray is synthesized inside viewController. Parser is doing good job, I tried with NSLog, but marray.count is always (null). Please help!!!
Try like this,
[viewController.marray addObject:[parsedObj copy]];
Have you verified that the marray property is non-nil? If it somehow hasn't been set properly then all of the insertions will be no-ops, and the the result of the count method will be nil.
Now that you've posted more code, this line is your problem:
[marray init];
You need to alloc/init a new NSMutableArray; this line is simply sending the init message to nil.
Can you post more code?
If you are inside view controller when marray is being called, you shouldn't have to call viewController.marray, just marray
One suggestion, from my experience in parsing, is to use an NSMutableDictionary instead of an array..so, for instance:
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict
{
currentElement = [elementName copy];
if([elementName isEqualToString:#"item"])
{
//Clear out story item caches
item = [[NSMutableDictionary alloc] init];
currentTitle = [[NSMutableString alloc] init];
currentAddress = [[NSMutableString alloc] init];
currentCity = [[NSMutableString alloc] init];
currentState = [[NSMutableString alloc] init];
currentZip = [[NSMutableString alloc] init];
currentId = [[NSMutableString alloc] init];
}
}
and then to add everything:
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
{
if([elementName isEqualToString:#"item"])
{
//Save values to an item, then store that item into the array
[item setObject:currentTitle forKey:#"title"];
[item setObject:currentAddress forKey:#"address"];
[item setObject:currentCity forKey:#"city"];
[item setObject:currentState forKey:#"state"];
[item setObject:currentZip forKey:#"zip"];
[item setObject:currentId forKey:#"id"];
// venues is the mutable array
[venues addObject:[item copy]];
} else {
return;
}
}
Now my mutable array has all the elements I need, and I can do various things with it (like reload a table cell). The above code has been tested and verified as working.
Here is some more code:
ViewController.h
#import <UIKit/UIKit.h>
#class XMLParser;
#interface ViewController : UIViewController{
NSMutableArray *marray;
XMLParser *parser;
}
#property (nonatomic, retain) NSMutableArray *marray;
#property (nonatomic, retain) XMLParser *parser;
#end
ViewController.m
#
import "ViewController.h"
#import "ParsedObj.h"
#import "XMLParser.h"
#synthesize marray;
#synthesize parser;
(...)
-(void)search:(id)sender{
[marray init];
NSURL *url = [[NSURL alloc] initWithString:#"http://example.com"];
NSXMLParser *xmlParser = [[NSXMLParser alloc] initWithContentsOfURL:url];
parser = [[XMLParser alloc] init];
[xmlParser setDelegate:parser];
[xmlParser parse];
(...)
#end
XMLparser.h:
#import <UIKit/UIKit.h>
#class ViewController, ParsedObj;
#interface XMLParser : NSObject <NSXMLParserDelegate>{
NSMutableString *currentElementValue;
ViewController *viewController;
ParsedObj *parsedObj;
}
#end
..and XMLparser.m:
#import "XMLParser.h"
#import "ViewController.h"
#import "ParsedObj.h"
#implementation XMLParser
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedName
attributes:(NSDictionary *)attributeDict {
if([elementName isEqualToString:#"Root"]) {
//Initialize the array.
viewController.marray = [[NSMutableArray alloc] init];
}
else if([elementName isEqualToString:#"Element"]) {
//Initialize the hotel.
parsedObj = [[ParsedObj alloc] init];
//Extract the attribute here.
parsedObj.ID = [[attributeDict objectForKey:#"id"] integerValue];
}
}
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
if(!currentElementValue)
currentElementValue = [[NSMutableString alloc] initWithString:string];
else
[currentElementValue appendString:string];
}
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
if([elementName isEqualToString:#"Root"])
return;
//There is nothing to do if we encounter the Books element here.
//If we encounter the Book element howevere, we want to add the book object to the array
// and release the object.
if([elementName isEqualToString:#"Element"]) {
[accomodationController.marray addObject:parsedObj];
parsedObj = nil;
}
else {
NSString *cValue=[currentElementValue stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
[parsedObj setValue:cValue forKey:elementName];
}
[currentElementValue release];
currentElementValue = nil;
}
- (void)dealloc {
[parsedObj release];
[currentElementValue release];
[super dealloc];
}
#end
I tried a different approach and now I have two solutions and a problem with both of them.
I have a XML parser that should add objects to NSMutableArray from where it has been called at first. Parser is working OK, but here is the problem.
First approach:
I changed NSXMLParser's init method inside my XMLParser.m (mArray is for Mutable Array and):
- (XMLParser *) initXMLParser:(id)sender {
[super init];
mArray=(NSMutableArray *)sender;
return self;}
mArray in implementation file XMLParser.h:
#interface XMLParser : NSObject <NSXMLParserDelegate>{
NSMutableString *currentElementValue;
NSMutableArray *mArray;
Object *aObject;}
-(XMLParser *) initXMLParser:(id)sender;
#end
So, let's get to the part where we call XMLParser from ViewController.m:
mArray = [[NSMutableArray alloc] init];
NSURL *url = [[NSURL alloc] initWithString:#"http://www.a.com/some.xml"];
NSXMLParser *xmlParser = [[NSXMLParser alloc] initWithContentsOfURL:url];
XMLParser *parser = [[XMLParser alloc] initXMLParser:self.mArray];
[xmlParser setDelegate:parser];
[xmlParser parse];
...and here is ViewController.h:
#class XMLParser;
#interface AccomodationSecondViewController : UIViewController{
#public NSMutableArray *mArray;}
#property (nonatomic, retain) NSMutableArray *mArray;
I'm not sure because this is first time I'm using public objects so...
Anyway, this is the part inside XMLParser.m that should add objects:
//This is for closing bracket
if([elementName isEqualToString:#"Element"]) {
[mArray addObject:aObject];
[aObject release];
aObject=nil;}
So, the idea is to make mutable array mArray public inside ViewController and to send pointer to it to XMLParser. I know that this may be little unnatural, but I just want to get it to work.
My other idea is this:
To send pointer of ViewController to XMLParser and do the rest. So, I changed initXMLParser:
- (XMLParser *) initXMLParser:(id)sender {
[super init];
viewController=(ViewController *)sender;
return self;}
and this is the part inside ViewController.m where I call my method:
XMLParser *parser = [[XMLParser alloc] initXMLParser:self];
then I add object:
[viewController.mArray addObject:aObject];
Why is this not working??!!