iPhone loading xml file - iphone

what is the best way to load a xml file as an parsed object in objective c?

Check out TouchXML. It's the easiest library I've found so far. Supports xpath on a basic level. http://code.google.com/p/touchcode/wiki/TouchXML. Here's a sample (code removed) from a recent project:
CXMLDocument *parser = [[CXMLDocument alloc] initWithData:theData encoding:NSUTF8StringEncoding options:0 error:nil];
NSArray *nodes = [parser nodesForXPath:#"//item" error:nil];
for (CXMLElement *resultElement in nodes)
{
NSMutableDictionary *data = [[NSMutableDictionary alloc] initWithCapacity:0];
// Create a counter variable as type "int"
int counter;
// Loop through the children of the current node and add to dictionary
for (counter = 0; counter < [resultElement childCount]; counter++)
{
// Add each field to the dictionary with the node name as
// key and node value as the value
[data setObject:[[resultElement childAtIndex:counter] stringValue]
forKey:[[resultElement childAtIndex:counter] name]];
}
// do stuff with the dictionary by named keys
// ...
// release dict
[data release];
}
[parser release];

If you know some C, the best (i.e. fastest parsing, lowest memory footprint) way to parse XML into Objective-C objects is probably via the SAX event parser in libxml2. Apple has a sample project called XMLPerformance which demonstrates how this is done.

I recommend using TouchXML. Great documentation and relatively easy to use compared with the NSXMLParser.

Look into the NSXmlParser. It can read xml and gives you methods you can override to process the contents.

Related

TouchXML problem

I have got following xml which I need to parse using TouchXML.
<?xml version="1.0"?>
<categories>
<category0>
<title>Alcoholic Drinks</title>
<description>Buy beers, wines, sprits and champagne from the top online alocholic drink stores.
Whatever your tipple you are sure to find a drinks supplier from our top shops below:
</description>
<status>1</status>
<popularStatus></popularStatus>
<order></order>
<link>alcoholic-drinks</link>
<id>1</id>
</category0>
<category1>
<title>Art and Collectibles</title>
<description>Are you looking to buy contemporary or fine art, or do you prefer to make your own artwork?&#
Whether type of artwork or craft materials you are looking for, you are certain to find one of the shops below more than helpful:
</description>
<status>1</status>
<popularStatus></popularStatus>
<order></order>
<link>art-and-collectibles</link>
<id>2</id>
</category1>
<category2>
<title>Auctions</title>
<description>Are you looking for the UK's biggest and best Auction Sites?
The team at safebuyer.co.uk have scoured the web to find the UK's favourite auctions, so why wait, start your bidding now!
</description>
...
...
...
I am thinking to create two loops from root node in order to fetch title and link but coudnt figure out how to do it. Can anybody help please.
If you can change your XML file and make all the category tag same. You can put all ... Instead of ... and ....
So that would be pretty easy to parse. You just need to make category class and all the tag would be parse automatically if you have correct xml parsing code.
CXMLNode *node;
for(i=0; i<10 ; i++){
NSString *xpath = [NSString stringWithFormat:#"//category%d/title", i];
NSArray *title = [[node nodesForXPath:xpath] stringValue];
}
Use the above code..
CXMLElement *element;
NSArray *titleItems = [[NSArray alloc] initWithArray:[element nodesForXPath:#"//category" error:nil]];
for(CXMLElement *item in titleItems){
NSString *title = [[item selectSingleNode:#"title"] stringValue];
}
Note: category node should be repeating.....
The code below gives you a dictionary where keys are titles and data are the links. Of course, if your XML document is "big", this is not the best way to do it.
CXMLDocument *doc = [[[CXMLDocument alloc] initWithXMLString:theXML options:0 error:nil] autorelease];
NSArray *categories = nil;
NSMutableDictionary* results = nil;
categories = [doc nodesForXPath:#"/categories/*[starts-with(name(), 'category')]" error:nil];
if (categories != nil && [categories count] > 0)
{
results = [NSMutableDictionary dictionaryWithCapacity:[categories count]];
for (CXMLElement *category in categories)
{
NSArray* titles = [category elementsForName:#"title"];
if ([titles count] >0)
{
NSArray* links = [category elementsForName:#"link"];
[result setObject:([links count]>0?[[links objectAtIndex:0] stringValue]:nil;
forKey:[[titles objectAtIndex:0] stringValue]];
}
}
}

What should the return value of a JSON GET or POST look like?

I'm new to JSON and just starting to wrap my head around it's functionality.
I'm trying to see if I can get print some data from some JSON methods. I've been alternating between the first one and the one that is commented out. The ideas is to see if I can get anything printing:
id newConnection = [scAPI performMethod:#"GET" onResource:#"me/connections.json" withParameters:nil context:nil userInfo:nil];
// id newConnection = [scAPI performMethod:#"POST"
// onResource:#"connections"
// withParameters:[NSDictionary dictionaryWithObjectsAndKeys:
// #"facebook_profile", #"service",
// #"imc://connection", #"redirect_uri",
// #"touch", #"display", //optional, forces services to use the mobile auth page if available
// nil]
// context:nil
// userInfo:nil];
NSLog(#"newConnection %#", newConnection);
NSLog(#"Is of type: %#", [newConnection class]);
NSDictionary *dict = [newConnection objectFromJSONString];
for (id key in dict) {
NSLog(#"key: %#, value: %#", key, [dict objectForKey:key]);
}
The above code doesn't err and I get logs such as:
Does this look right? How do I properly use these JSON methods to get a dictionary of values?
EDIT 1
To be clear I'm using JSONKit :)
I would personally recommend using the SBJSON library. Getting a dictionary with it is straightforward.
Get a response back from a connection, and then use the following code (where response is an NSString containing the response from the server):
SBJsonParser *parser = [[SBJsonParser alloc] init];
NSArray *returnData = [parser objectWithString:[response stringByReplacingOccurrencesOfString:#"\\\\" withString:#"\\"]];
[parser release];
NSDictionary *returnDict = (NSDictionary *)returnData;
This is unrelated, but for examining JSON data, I would also recommend Online JSON Viewer. You can paste in your JSON strings and view it with a collapsable array structure. Very convenient.

Releasing 2 times?

I am confused with releasing the object that I first alloc/initialized and then copied. As far as I understood the memory management manual I should release the object 2 times because by allocating it and then later copying it I should have retain value of 2?
So first release would lower it to one and second to 0?
I get message sent to dealloc object if I release it twice. If I do it once there are no problems in application, but there is one in my understanding of Objetive C mem. management :)))
The only explanation that I can think of is when you release an object from soecific context it will release it instantly no matter the value of retain count ?
In the snippet bellow xmlElement is the confusing one....
// LSnippet of code for TouchXML
for (CXMLElement *resultElement in resultNodes) {
NSMutableDictionary *xmlElement = [[NSMutableDictionary alloc] init];
// Create a counter variable as type "int"
int counter;
// Loop through the children of the current node
for(counter = 0; counter < [resultElement childCount]; counter++) {
// Add each field to the blogItem Dictionary with the node name as key and node value as the value
[xmlElement setObject:[[resultElement childAtIndex:counter] stringValue] forKey:[[resultElement childAtIndex:counter] name]];
}
// Add the blogItem to the global blogEntries Array so that the view can access it.
[tempReturnedElements addObject:[xmlElement copy]];
[xmlElement release];
//[xmlElement release]; but NOT!
}
UPDATE: whole method code:
+(void) runXPath:(NSString *)xPathExpression{
CXMLDocument *rssParser = [[[CXMLDocument alloc] initWithXMLString:xmlStringContent options:0 error:nil] autorelease];
// Create a new Array object to be used with the looping of the results from the rssParser
NSArray *resultNodes = NULL;
// Set the resultNodes Array to contain an object for every instance of an node in our RSS feed
resultNodes = [rssParser nodesForXPath:xPathExpression error:nil];
NSMutableArray *tempReturnedElements = [[NSMutableArray alloc]init];
// Loop through the resultNodes to access each items actual data
for (CXMLElement *resultElement in resultNodes) {
// Create a temporary MutableDictionary to store the items fields in, which will eventually end up in blogEntries
NSMutableDictionary *xmlElement = [[NSMutableDictionary alloc] init];
// Create a counter variable as type "int"
int counter;
// Loop through the children of the current node
for(counter = 0; counter < [resultElement childCount]; counter++) {
// Add each field to the blogItem Dictionary with the node name as key and node value as the value
[xmlElement setObject:[[resultElement childAtIndex:counter] stringValue] forKey:[[resultElement childAtIndex:counter] name]];
}
// Add the blogItem to the global blogEntries Array so that the view can access it.
[tempReturnedElements addObject:[xmlElement copy]];
//***** Crushes if I use:
//***** [tempReturnedElements addObject:[[xmlElement copy] autorelease]];
[xmlElement release];
}
[lotojuegosAppDelegate setMyReturnedXmlElements:[tempReturnedElements copy]];
[tempReturnedElements release];
}
Thanks in advance,
Luka
In [xmlElement copy], only the returned object (i.e. the copy) needs to be released. The receiver (xmlElement)'s ownership (retain count) is not changed.
So the correct way should be
NSDictionary* xmlElemCopy = [xmlElement copy];
[tempReturnedElements addObject:xmlElemCopy];
[xmlElemCopy release];
[xmlElement release];
The rule is pretty simple: for each alloc/init, copy or retain you must call exactly one release or autorelease when you want to give up the ownership. You've only called one alloc/init, so you may only call release once.
This line:
[tempReturnedElements addObject:[xmlElement copy]];
should be written as:
[tempReturnedElements addObject:[[xmlElement copy] autorelease]];
The reason is: copy gives you a new object. The retain count/ownership of the object pointed to by xmlElement is unchanged, but the new object now belongs to you. You're responsible for releasing it now. See the first paragraph in this answer: you called copy, you need to call release on the resulting object.

Search for XML feeds usind Iphone

hi can anyone give me hint of how to start coding for an app to search the internet and find the rss xml url's using a string provided by the user.
thanks upfront.
You need to use XML parsing to implement this.I suggest use touchXML
-(void)callwebservice{
NSString *path = #"YOUR URL";
[self grabRSSFeed:path];
}
pragma mark -
pragma mark Touch XML
pragma mark -
-(void) grabRSSFeed:(NSString *)blogAddress {
// Initialize the blogEntries MutableArray that we declared in the header
blogEntries = [[NSMutableArray alloc] init];
// Convert the supplied URL string into a usable URL object
NSURL *url = [NSURL URLWithString: blogAddress];
// Create a new rssParser object based on the TouchXML "CXMLDocument" class, this is the
// object that actually grabs and processes the RSS data
CXMLDocument *rssParser = [[[CXMLDocument alloc] initWithContentsOfURL:url options:0 error:nil] autorelease];
// Create a new Array object to be used with the looping of the results from the rssParser
NSArray *resultNodes = NULL;
// Set the resultNodes Array to contain an object for every instance of an node in our RSS feed
resultNodes = [rssParser nodesForXPath:#"//Node you want to parse" error:nil];
// Loop through the resultNodes to access each items actual data
for (CXMLElement *resultElement in resultNodes) {
// Create a temporary MutableDictionary to store the items fields in, which will eventually end up in blogEntries
NSMutableDictionary *blogItem = [[NSMutableDictionary alloc] init];
// Create a counter variable as type "int"
int counter;
// Loop through the children of the current node
for(counter = 0; counter < [resultElement childCount]; counter++) {
// Add each field to the blogItem Dictionary with the node name as key and node value as the value
[blogItem setObject:[[resultElement childAtIndex:counter] stringValue] forKey:[[resultElement childAtIndex:counter] name]];
NSLog(#"Data = %#",[[resultElement childAtIndex:counter] stringValue]);
}
// Add the blogItem to the global blogEntries Array so that the view can access it.
[blogEntries addObject:[blogItem copy]];
}
[YourTable reloadData];
}
Import touchXML library in you header file.
Thanks

Parsing multiple attributes in TouchXML

I'm in the making of an application where I'm using TouchXML to parse an XML containing airport flight information.
The XML looks like this:
<?xml version="1.0" encoding="iso-8859-1"?>
<airport xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://flydata.avinor.no/XmlFeed.xsd" name="OSL">
<flights lastUpdate="2010-10-03T12:29:43">
<flight uniqueID="1273306">
<airline>DY</airline>
<flight_id>DY246</flight_id>
<dom_int>D</dom_int>
<schedule_time>2010-10-03T10:45:00</schedule_time>
<arr_dep>D</arr_dep>
<airport>TOS</airport>
<check_in>D</check_in>
<gate>18</gate>
<status code="D" time="2010-10-03T10:42:00"/>
</flight>
<flight uniqueID="1273799">
<airline>SK</airline>
<flight_id>SK263</flight_id>
<dom_int>D</dom_int>
<schedule_time>2010-10-03T10:50:00</schedule_time>
<arr_dep>D</arr_dep>
<airport>BGO</airport>
<check_in>EF</check_in>
<gate>23</gate>
</flight>
</flights>
</airport>
The TouchXML documentation tells me how to fetch attributes, which works for flights's lastUpdate attribute, but not for status's code and time attribute.
In addition, not all flight XML entries contain the status element, but I'm doing a check for this.
Currently, the code I have is the following:
-(void)grabXML {
flightEntries = [[NSMutableArray alloc] init];
NSURL *url = [NSURL URLWithString:#"http://flydata.avinor.no/XmlFeed.asp?TimeFrom=0&TimeTo=2&airport=OSL&direction=D"];
CXMLDocument *xmlParser = [[[CXMLDocument alloc] initWithContentsOfURL:url options:0 error:nil] autorelease];
NSArray *resultNodes = NULL;
resultNodes = [xmlParser nodesForXPath:#"//flight" error:nil];
for (CXMLElement *resultElement in resultNodes) {
NSMutableDictionary *flightItem = [[NSMutableDictionary alloc] init];
int counter;
for (counter = 0; counter < [resultElement childCount]; counter++) {
[flightItem setObject:[[resultElement childAtIndex:counter] stringValue] forKey:[[resultElement childAtIndex:counter] name]];
// Check if the node has the <status> element
if ([[[resultElement childAtIndex:counter] name] isEqualToString:#"status"]) {
// Fetch the code and time attribute here
}
}
// This gives me <flight uniqueID="*">
[flightItem setObject:[[resultElement attributeForName:#"uniqueID"] stringValue] forKey:#"uniqueID"];
[flightEntries addObject:[flightItem copy]];
}
}
The documentation does not tell how to parse multiple attributes, so I was hoping someone had a clue on how to do it?
First off, I have to say that your XML will be difficult to work with because you are trying to use a specific tag for each item in a list. For example:
<airline>DY</airline>
<flight_id>DY246</flight_id>
<dom_int>D</dom_int>
<schedule_time>2010-10-03T10:45:00</schedule_time>
<arr_dep>D</arr_dep>
<airport>TOS</airport>
<check_in>D</check_in>
<gate>18</gate>
the tags named , don't make sense as XML is intended to describe your data, not define it. If you have an entity called an item, you should probably just call it item and make the XML look like this:
<level1_items>
<level1_item>
<item index="1">text</item_1>
<item index="2">text</item_2>
</level1_item>
<level1_item>
<item index="1">some text</item_1>
<item index="2">some more text</item_2>
</level1_items>
Where each item has an index. For that matter, you could just load the document in your TouchXML CXMLDocument and grab the nodes you need with XPath and assume they're in the correct order ignoring the index= parameter I specified.
The next issue is that converting XML to an NSDictioanry or NSArray of dictionaries is a pretty involved task and what you gain isn't worth the effort. Just load your XML into a CXMLDocument and then start obtaining nodes using XPath. Something like this:
CXMLDocument *doc = [[CXMLDocument alloc] initWithXMLString:xmlString options:0 error:nil];
// Returns all 'level1_item' nodes in an array
NSArray *nodes = [[doc rootElement] nodesForXPath:#"//response/level1_items/level1_item" error:nil];
for (CXMLNode *itemNode in nodes)
{
for (CXMLNode *childNode in [itemNode children])
{
NSString *nodeName = [childNode name]; // should contain item_1 in first iteration
NSString *nodeValue = [childNode stringValue]; // should contain 'text' in first iteration
// Do something with the node data.
}
}
suggest trying to use the XML this way and then come back here and ask specific questions if you have problems.
hope that helps.
PK