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
Related
I used to have trouble parsing json, before I found a really good tutorial which I could follow. When I have been building my own webservices to parse json from I have followed the same steps, but now when I'm trying to parse twitters json-feed I'm drawing blanks. Here's my code.
NSError* error;
NSDictionary* json = [NSJSONSerialization
JSONObjectWithData:responseData //1
options:kNilOptions
error:&error];
NSArray* itemNumber = [json objectForKey:#"posts"]; //2
NSUInteger numObjects = [itemNumber count];
arrayTweets = [[NSMutableArray alloc] init];
arrayTimes = [[NSMutableArray alloc] init];
if (numObjects != 0) {
int i = 0;
do
{
NSDictionary* loan = [itemNumber objectAtIndex:i];
NSString* text = [(NSDictionary*)loan objectForKey:#"text"];
[arrayTweets addObject:text];
NSString *time = [(NSDictionary*)loan objectForKey:#"created_at"];
[arrayTweets addObject:time];
i++;
} while (i < numObjects);
} else {
NSLog(#"Nej");
}
[tableTweet performSelectorOnMainThread:#selector(reloadData) withObject:nil waitUntilDone:NO];
Notice the "NSArray* itemNumber = [json objectForKey:#"posts"];". Since my old json-feed used to look something like this...
{"status":"ok","count":4,"count_total":4,"pages":1,"posts":[{"id":58,"type":
it was working since I had the "posts" before :[, but now with the twitter feed it just starts right into:
[{"created_at":"Mon Aug 06 19:16:42 +0000 2012","id":232555817835048961,"...
And I have no idea what to do. I realize this is stupid, but I don't think I'm going to learn unless someone explains it to me.
Any help appreciated!
Aah, there is a small problem with your code. The root object of your previous feed used to be a dictionary. You can see this by the { sign at the beginning. Your new feed instantly gives you an array back [. So you don't have to parse your data as a dictionary but as as NSArray. Without further ado, explanation here: https://stackoverflow.com/a/8356919/341358
I'm working on a project and I want to be able to handle some template type messages. The template would contain something like:
"{{user1}} has just created an account"
I then have a data map that would give you a location within the NSMutableDictionary where the data is located:
"activity.message.status"
I then want to be able to query the NSMutableDictionary by splitting up that string, so that it becomes something like:
[[[myDictionary objectForKey:#"activity"] objectForKey:#"message"] objectForKey:#"status"]
I could make something as long as it was consistant on being just 3 strings, but some may be more or less.
Any help would be extremely appreciated.
It's actually much easier than splitting strings into keys. Apples Key-Value-Coding allows exactly what you want.
[myDictionary valueForKeyPath:#"activity.message.status"];
A key path is a string of dot separated keys that is used to specify a sequence of object properties to traverse. The property of the first key in the sequence is relative to the receiver, and each subsequent key is evaluated relative to the value of the previous property.
For example, the key path address.street would get the value of the address property from the receiving object, and then determine the street property relative to the address object.
Key-Value Coding Programming Guide
You would do something like,
NSArray *array = [#"activity.message.status" componentsSeperatedByString:#"."];
Which will create an array containing {activity,message,status).
Now you have your array you can use for querying your dictionary.
[[[myDictionary objectForKey:[array objectAtIndex:0]] objectForKey:[array objectAtIndex:1]] objectForKey:[array objectAtIndex:2]];
Which is equivalent to:
[[[myDictionary objectForKey:#"activity"] objectForKey:#"message"] objectForKey:#"status"];
Hope this helps !
It's not clear to me from your question how we should map user1 to activity.message.status. For now I'll assume you mean that the template might contain a string like "{{activity.message.status}}" and you want to be able to parse that.
Here's one iteration that operates on an NSMutableString that can be looped until no match is found:
NSError *error = NULL;
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:#"\\{\\{.+?\\}\\}"
options:NSRegularExpressionCaseInsensitive
error:&error];
NSRange matchRange = [regex rangeOfFirstMatchInString:string
options:0 range:NSMakeRange(0, [string length])];
NSRange keyPathRange = NSMakeRange(matchRange.location + 2, matchRange.length - 4);
NSString *keyPath = [string substringWithRange:keyPathRange];
NSString *newSubstring = [myDictionary valueForKeyPath:keyPath];
[string replaceCharactersInRange:matchRange withString:newSubstring];
I haven't tested this code.
How about a (recursive ... cool) category method on NSMutableDictionary like this:
- (void)setObject:(id)object forCompoundKey:(NSString *)compoundKey {
NSArray *keys = [compoundKey componentsSeparatedByString:#"."];
if ([keys count] == 1) {
return [self setObject:object forKey:compoundKey];
}
// get the first component of the key
NSString *key = [keys objectAtIndex:0];
// build the remaining key with the remaining components
NSRange nextKeyRange;
nextKeyRange.location = 1;
nextKeyRange.length = [keys count] - 1;
NSArray nextKeys = [keys subarrayWithRange:nextRange];
NSString *nextKey = [nextKeys componentsJoinedByString:#"."];
NSMutableDictionary *nextDictionary = [NSMutableDictionary dictionary];
[self addObject:nextDictionary forKey:key];
// now the cool part... recursion
[nextDictionary setObject:object forCompoundKey:nextKey];
}
I haven't tested this, but it passes a quick desk check. The objectForCompoundKey: retrieval can be written analogously.
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]];
}
}
}
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
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.