Feeding XML into custom objects? - objective-c

I have some XML being returned from a web query, with multiple parameters wrapped up inside a tag, like so:
<game>
<name>First game title</name>
<id>12345</id>
<desc>A game..</desc>
</game>
<game>
<name>Second game title</name>
<id>67890</id>
<desc>Another game..</desc>
</game>
I'm using NSXMLParser to parse it, and it's spitting out each line one by one into my console as I NSLog them. I'm trying to feed each <game> into one of my Game objects, with name as an NSString, ID as an NSNumber, etc. However, I'm struggling to work out how I'd tell it to begin a new object, since the <game> tag isn't being returned in any of my NSLog statements, only those with actual data are (such as each name, id, etc.)
If I want to get all of the data within each <game> </game> tag into a separate object, how can I do so? Here's the parser code:
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedName attributes:(NSDictionary *)attributeDict {
element = [NSMutableString string];
}
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
NSLog(#"ELEMENT TYPE: %# VALUE: %#", elementName, element);
}

First make yourself a Game class. We will parse the XML into Games objects.
Game.h like so:
#interface Game : NSObject
#property (nonatomic, retain) NSString *name;
#property (nonatomic, retain) NSNumber *gameID;
#property (nonatomic, retain) NSString *gameDescription;
#end
Now in the class you are parsing the XML in (in this example ViewController), create a NSMutableArray property to store the Game objects as we parse them, a Game property to use as we create new Game objects, a NSString property to store the current element we are parsing in the XML, and a property for the NSXMLParser instance we are using. Also make sure it conforms to the NSXMLParserDelegate protocol.
So the header ViewController.h:
#interface ViewController : UIViewController <NSXMLParserDelegate>
#property (nonatomic, retain) NSString *currentElement;
#property (nonatomic, retain) NSMutableArray *games;
#property (nonatomic, retain) Game *gameBeingParsed;
#property (nonatomic, retain) NSXMLParser *xmlParser;
#end
Now in the implementation ViewController.m we parse the XML:
#import "ViewController.h"
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Make an NSMutableArray to put the parsed Game objects in
self.games = [NSMutableArray array];
// Get the XML data to parse
// We need it in an NSdata object
NSString *xmlString = #"<?xml version=\"1.0\" encoding=\"utf-8\"?><xml><game><name>First game title</name><id>12345</id><desc>A game..</desc></game><game><name>Second game title</name><id>67890</id><desc>Another game..</desc></game></xml>";
NSData *xmlData = [xmlString dataUsingEncoding:NSStringEncodingConversionAllowLossy];
// Set up an NSXMLParser to use
// Set the delegate and start parsing!
self.xmlParser = [[[NSXMLParser alloc] initWithData:xmlData] autorelease];
_xmlParser.delegate = self;
[_xmlParser parse];
}
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedName attributes:(NSDictionary *)attributeDict {
// If we have a <game> tag then we are starting to parse a new Game object
if ([elementName isEqualToString:#"game"]) {
self.gameBeingParsed = [[[Game alloc] init] autorelease];
}
// If not then we need to keep track of the element name so we know which property to set on the Game object
else {
self.currentElement = elementName;
}
}
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
// If we have a closing </game> tag we are done parsing a Game so add it to the array
if ([elementName isEqualToString:#"game"]) {
[_games addObject:_gameBeingParsed];
}
}
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
// Work out which element we have the characters for
// Then set the property of the Game object
if ([_currentElement isEqualToString:#"name"]){
_gameBeingParsed.name = string;
}
if ([_currentElement isEqualToString:#"id"]){
_gameBeingParsed.gameID = [NSNumber numberWithInt:[string intValue]];
}
if ([_currentElement isEqualToString:#"name"]){
_gameBeingParsed.gameDescription = string;
}
}
- (void)parserDidEndDocument:(NSXMLParser *)parser{
// We are done parsing XML
NSLog(#"Parsed %d Games", _games.count);
for (Game *game in _games) {
NSLog(#"%# : %# : %#", game.name, game.gameID, game.gameDescription);
}
}
After parsing has finished and we get a call back in parserDidEndDocument: At this point the _games property will be populated we instances of Games.

Related

Accessing properties of objects in array to display in UITableViewCell

I am using an XML parser to get information from a blog to create a feed reader app. I created an object with properties that are the data for each blog entry (title, published, author...). I'm storing the data in the object, then using a pointer to put the object in an array of parsed data. When I go to access the properties to display them in my UITableView, every cell is the same, with the last blog entry's data for every one.
parser .m file
#interface Parser()
//This property holds the blog objects that were parsed
#property (nonatomic, strong) NSMutableArray *parsedResults;
//This property holds the current element content being parsed
#property (nonatomic, strong) NSString *currentElement;
#property (nonatomic, strong) FRFeedItem *blogEntry;
#end
#implementation SolsticeParser
#synthesize parsedResults = _parsedResults;
#synthesize currentElement = _currentElement;
// Will be used to truncate data parsed from publish tag so that it will only store the YYYY-MM-DD to self.blogEntry.datepublished
NSRange dateOnly = {0, 10};
//This method initializes the parser, sets the delegate, starts parsing, and returns the results.
- (NSMutableArray *)parseFeedWithResults:(NSURL *)URL
{
NSXMLParser *parser = [[NSXMLParser alloc] initWithContentsOfURL:URL];
parser.delegate = self;
self.parsedResults = [[NSMutableArray alloc] init];
[parser parse]; // Everything parsed here
return self.parsedResults;
}
...Here parsed data is saved to the properties of the BlogEntry object...
#pragma mark - Parser delegate
- (void)parser:(NSXMLParser *)parser
didStartElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qualifiedName
attributes:(NSDictionary *)attributeDict
{
// Custom blog object initialized here
if ([elementName isEqualToString:#"entry"]) {
if (!self.blogEntry) {
self.blogEntry = [[FRFeedItem alloc] init];
}
}
}
...
- (void)parser:(NSXMLParser *)parser
didEndElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qName {
if([elementName isEqualToString:#"title"]) {
self.blogEntry.title = self.currentElement;
} else if([elementName isEqualToString:#"published"]) {
self.blogEntry.datePublished = [self.currentElement substringWithRange:dateOnly];
} else if([elementName isEqualToString:#"entry"]) {
[self.parsedResults addObject:self.blogEntry];
}
}
In MyTableViewController.m:
#interface MyTableViewController ()
#property (nonatomic, strong) Parser* parser;
#property (nonatomic, strong) NSMutableArray* feedDataFromParser;
#end
#implementation MyTableViewController
// synthesize automatically done by Xcode v4.6
- (void)viewDidLoad
{
[super viewDidLoad];
self.parser = [[Parser alloc] init]; // initialize parser by allocating memory on the heap
[self loadItems]; // automatically loads data to be displayed upon opening the app
}
- (void)loadItems
{
// information parsed from blog stored to a mutable array
self.feedDataFromParser = [self.parser parseFeedWithResults:[NSURL URLWithString:kFeedURL]];
}
...
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
//code not included for this question for brevity
// Configure the cell from data stored in mutable array of FRFeedItem objects
// PROBLEM:
cell.textLabel.text = [[self.feedDataFromParser objectAtIndex:indexPath.row] title];
cell.detailTextLabel.text = [[self.feedDataFromParser objectAtIndex:indexPath.row] datePublished];
return cell;
}
#end
As far as I can tell, there is nothing syntactically wrong. I've tried printing out the data parsed and saved to the object in the parser file as well as the value of indexPath.row, and both are correct.
What am I missing??
I think the problem is this line:
if (!self.blogEntry)
After you create the first one, you won't create any more. Try removing that if clause, and see if that fixes it.

iphone xml binding / parsing to objects

I'm new to iphone developing and what I want is an xml parser that I can bind to Objects. I searched a lot but I still need some help. For example I have the following xml structure:
<xml>
<hotels>
<hotel>
<id>1</id>
<name>Hotel Name</name>
</hotel>
<hotel>
....
</hotel>
</hotels>
<beaches>
<beach>
<id>11</id>
<name>Beach Name</name>
</beach>
<beach>
....
</beach>
</beaches>
</xml>
Now my question is...which is the best (or maybe easier) way to read this xml file and parse for example...:
Now I want a list of all hotels...so I want o parse and get that list...etc.
Now I want beach with id = 11 etc.
Can this be done easily in iphone? Which is the best approach? I would be thankful if you could give me examples with source code or such. Thanks in advance.
NOTE: I will support at least ios 4.3 or later.
you can try implementing NSXMLParserDelegate then write your logic in
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:
(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary
*)attributeDict
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:
(NSString *)namespaceURI qualifiedName:(NSString *)qName
Example code:-
Create XMLParser class, then bean classes for Hotel, Beach and arrays for storing these objects.
XMLParser.h
#import <Foundation/Foundation.h>
#interface XMLParser : NSObject <NSXMLParserDelegate>
#property (strong, readonly) NSMutableArray *yourObjects;
-(id) parseXML:(NSString *)url;
#end
XMLParser.m
#import "XMLParser.h"
#import "YourObject.h"
#implementation XMLParser
#synthesize yourObjects =_yourObjects;
NSMutableString *currentNode;
NSXMLParser *parser;
YourObject *YourObject;
-(id) parseXML:(NSString *)url
{
_yourObjects = [[NSMutableArray alloc]init];
NSURL *nsURL = [NSURL URLWithString:url];
NSData *data = [[NSData alloc] initWithContentsOfURL:nsURL];
parser = [[NSXMLParser alloc] initWithData:data];
parser.delegate = self;
[parser parse];
return self;
}
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
currentNode = (NSMutableString *) [string stringByTrimmingCharactersInSet:
[NSCharacterSet whitespaceAndNewlineCharacterSet]];
}
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:
(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary
*)attributeDict
{
if ([elementName isEqualToString:#"hotel"])
{
yourObject = [YourObject alloc]; //eg:- Hotel
//any logic that you want to include.....
}
if ([elementName isEqualToString:#"beach"])
{
yourObject = [YourObject alloc]; //eg:- beach
//any logic that you want to include.....
}
}
#end
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:
(NSString *)namespaceURI qualifiedName:(NSString *)qName
{
if([elementName isEqualToString:#"status"])
{
[self.yourObjects addObject:yourObject];
yourObject = nil;
currentNode = nil;
}
}
call -(id) parseXML:(NSString *)url; to parse the required XML and retrieve the Objects.
Hope this helps.

Return the count of an NSMutableArray from an XML id number in Objective-C?

I am building an NSMutableArray out of an XML file and need to return the count of each resident id in the XML file. Is it possible to do this?
<residents>
<resident id="1">
<name>
<first>Daffy</first>
<last>Duck</last>
</name>
</resident>
<resident id="2">
<name>
<first>Mickey</first>
<last>Mouse</last>
</name>
</resident>
etc...
I will be returning the count using code similar to this:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
NSLog(#"Count = %i", [appDelegate.residents count]);
return [appDelegate.residents count];
Any suggestions?
For the array, in my AppDelegate.h I have:
#interface AppDelegate : UIResponder <UIApplicationDelegate>
{
UIWindow *window;
UINavigationController *navigationController;
NSMutableArray *residents;
}
#property (strong, nonatomic) UIWindow *window;
#property (nonatomic, retain) IBOutlet UINavigationController *navigationController;
#property (nonatomic, retain) NSMutableArray *residents;
In XMLAppDelegate.h I use:
#interface XMLAppDelegate : NSObject <UIApplicationDelegate> {
UIWindow *window;
UINavigationController *navigationController;
NSMutableArray *residents;
}
#property (nonatomic, retain) IBOutlet UIWindow *window;
#property (nonatomic, retain) IBOutlet UINavigationController *navigationController;
#property (nonatomic, retain) NSMutableArray *residents;
#end
You will need to use NSXMLParser (and NSXMLParserDelegate respectively) and correctly parse the XML data into the NSMutableArray before you can count anything. Also, to store everyday person's XML data, you may want to placing the information into NSMutableDictionaries.
In the .h #interface, you will need to create IVARs (example only):
#interface YourObject : UIViewController <NSXMLParserDelegate> {
NSXMLParser *parser;
NSString *currentElement;
NSMutableString *firstName;
NSMutableString *lastName;
NSMutableArray *residents;
}
and somewhere in the .m the code, call this:
NSString *xmlLocationString = #"http://www.website.com/feed.xml";
NSURL *xmlLocationURL = [NSURL URLWithString:xmlLocationString];
parser = [[NSXMLParser alloc] initWithContentsOfURL:xmlLocationURL];
[parser setDelegate:self];
[parser setShouldProcessNamespaces:NO];
[parser setShouldReportNamespacePrefixes:NO];
[parser setShouldResolveExternalEntities:NO];
[parser parse];
And of course, set up the delegate methods which will actually parse the XML document in to the NSMutableArray:
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict{
currentElement = nil;
currentElement = [elementName copy];
if ([elementName isEqualToString:#"day"]) {
firstName = [[NSMutableString alloc]init];
lastName = [[NSMutableString alloc]init];
}
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string{
string = [string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
string = [string stringByTrimmingCharactersInSet:[NSCharacterSet controlCharacterSet]];
if ([currentElement isEqualToString:#"first"]) {
[firstName appendString:string];
} else if ([currentElement isEqualToString:#"last"]) {
[lastName appendString:string];
} else {
...
}
}
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName{
if ([elementName isEqualToString:#"day"]) {
NSMutableDictionary *dictionary = [[NSMutableDictionary alloc] init];
[dictionary setObject:firstName forKey:#"fname"];
[dictionary setObject:lastName forKey:#"lname"];
//And finally
[residents addObject:dictionary];
}
}
Then, anywhere in your code you can call
[residents count];
to get the total count for the residents.
Notice: I just wrote this code as I went, it probably has some bugs
As of I understand you only need the count of . If so you can do following.
NSData *data = // your XML Data either from webservice or local file. Not xml string but the Data.
NSXMLParser *parser = [[NSXMLParser alloc] initWithData:data];
[parser setDelegate:self];
[parser parse];
This code will be where you get your data. In your .h file:
#interface yourViewController : UIViewController {
NSInteger resedintsCount;
}
in your .m File:
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict{
if([elementName isEqualsToString:#"resident"]) {
resedintsCount++;
}
}
- (void)parserDidEndDocument:(NSXMLParser *)parser {
// you can use resedintsCount
}
I assumed that you are getting your XML data in view controller. If you are getting it in AppDelegate or any other class than even the same process will be there.
Still if you have in confusion feel free to ask.

Setting NSString variables with NSXMLParser

I am using NSXMLParser to grab information from an online XML file. My goal is to have one class do the XML parsing and another class to implement the variables. Below is the code for my project:
Current.h & Current.m
#import <Foundation/Foundation.h>
#interface Current : NSObject {
NSString *curTempF;
IBOutlet NSTextField *textField;
}
#property (nonatomic, copy) NSString *curTempF;
- (void)displayOutlets:(id)sender;
#end
and
#import "Current.h"
#implementation Current
#synthesize curTempF;
- (void)awakeFromNib {
[self displayOutlets:self];
}
- (void)displayOutlets:(id)sender {
[textField setStringValue:curTempF];
}
#end
XmlParser.h & XmlParser.m
#import <Foundation/Foundation.h>
#interface XmlParser : NSObject <NSXMLParserDelegate> {
NSString *urlString;
NSURL *url;
NSMutableString *xmlString;
}
- (IBAction)fetchXML:(id)sender;
#end
and
#import "XmlParser.h"
#import "Current.h"
#implementation XmlParser
- (void)awakeFromNib {
[self fetchXML:self];
}
- (IBAction)fetchXML:(id)sender {
urlString = #"http://api.wunderground.com/api/***/conditions/q/28173.xml";
url = [NSURL URLWithString:urlString];
NSXMLParser *parser = [[NSXMLParser alloc] initWithContentsOfURL:url];
[parser setDelegate:self];
[parser parse];
}
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict {
if ([elementName isEqual:#"temp_f"]) {
xmlString = [[NSMutableString alloc] init];
}
}
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
if ([elementName isEqual:#"temp_f"]) {
Current *cTempF = [[Current alloc] init];
[cTempF setCurTempF:xmlString];
}
}
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
[xmlString appendString:string];
}
#end
When I run the program I am receiving an error about "Invalid parameter not satisfying: aString". It looks like the setStringValue for the IBOutlet is not working. Any suggestions?
I suspect that when you initialize the Current class with [[Current alloc] init], it is trying to populate the textField with the curTempF string. However, the curTempF string is not initialized yet at that point until you call [cTempF setCurTempF:xmlString].
One approach would be to not display outlets in the Current class:
- (void)awakeFromNib {
//[self displayOutlets:self];
}
Then, call the displayOutlets function in your parser:
if ([elementName isEqual:#"temp_f"]) {
Current *cTempF = [[Current alloc] init];
[cTempF setCurTempF:xmlString];
[cTempF displayOutlets:cTempF];
}
Alternately, you could keep your awakeFromNib code the same, but create an initWith method in your Current class, which may be cleaner:
- (id) initWithCurTemp:(NSString *)curTemp {
self = [super init];
if (self) {
self.curTempF = curTemp;
}
return self;
}
- (void)awakeFromNib {
[self displayOutlets:self];
}
Then, your parser code would look like:
if ([elementName isEqual:#"temp_f"]) {
Current *cTempF = [[Current alloc] initWithCurTemp:xmlString];
}
You should create your Current in the nib, as you were doing, and not in code.
As for getting the parser (which I assume is in the same nib) to talk to the Current, create an outlet in the parser to point to the Current, and connect that outlet in the nib.

How to parse this XML file using Objective C?

I have an XML file of the following structure:
<xmlDocument version="1">
<subject id="1">
<maths marks="65"/>
<science marks="80"/>
<tamil marks="90"/>
<social marks="79"/>
<English marks="70"/>
</subject>
</xmlDocument>
How to parse and get this data using Objective C?
Create an instance of NSXMLParser and assign a delegate to the parser.
In your delegate class, implement the relevant methods of the NSXMLParserDelegate protocol.
Call the parser's parse method.
Ask more specific questions if you encounter problems.
Since you don't have any text inside your tags you can use the parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName attributes:(NSDictionary *)attributeDict method on your delegate. Than you can store the values inside a dictionary or object. If you have multiple subject tags you can use the parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName to change the context of your parser. The official documentation should give you more details on which methods are available.
You could do something like that (incomplete implementation):
/*
* Incomplete implementation just to give some pointers
*/
#implementation MyDelegate
-(void) init {
if((self = [super init])) {
_subjects = [NSMutableArray new];
}
}
-(void) parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName attributes:(NSDictionary *)attributeDict {
if([elementName equalsIgnoreCase:#"subject"]) {
_context = [NSMutableDictionary new];
} else {
[_context setObject:[attributeDict valueForKey:#"mark"] forKey:elementName];
}
}
- (void) parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName {
[_subjects addObject:_context]
[_context release]; _context = nil;
}
#end