iPhone XMLParser help - iphone

I am needing to parse an XML file for my app and I dont have any clue how to do it. I went through one XMLParser tutorial, and it worked fine but the XML file in the tutorial was very simple and my XML file is quite a bit more complex.
here is a snippet of the xml file:
<?xml version="1.0" encoding="UTF-8"?>
<digital_tpp cycle="1003" from_edate="0901Z 03/11/10" to_edate="0901Z 04/08/10">
<state_code ID="AK" state_fullname="Alaska">
<city_name ID="ADAK ISLAND" volume="AK-1">
<airport_name ID="ADAK" military="N" apt_ident="ADK" icao_ident="PADK" alnum="1244">
<record>
<chartseq>10100</chartseq>
<chart_code>MIN</chart_code>
<chart_name>TAKE-OFF MINIMUMS</chart_name>
<useraction></useraction>
<pdf_name>AKTO.PDF</pdf_name>
<cn_flg>N</cn_flg>
<cnsection></cnsection>
<cnpage></cnpage>
<bvsection>C</bvsection>
<bvpage></bvpage>
<procuid></procuid>
<two_colored>N</two_colored>
<civil> </civil>
<faanfd15></faanfd15>
<faanfd18></faanfd18>
<copter></copter>
</record>
<record>
<chartseq>10200</chartseq>
<chart_code>MIN</chart_code>
<chart_name>ALTERNATE MINIMUMS</chart_name>
<useraction></useraction>
<pdf_name>AKALT.PDF</pdf_name>
<cn_flg>N</cn_flg>
<cnsection></cnsection>
<cnpage></cnpage>
<bvsection>E</bvsection>
<bvpage></bvpage>
<procuid></procuid>
<two_colored>N</two_colored>
<civil> </civil>
<faanfd15></faanfd15>
<faanfd18></faanfd18>
<copter></copter>
</record>
</airport_name>
</city_name>
</state_code>
</digital_tpp>
What I'm needing to do is search the XML file for the <...icao_ident> that the user specifies, then create a dictionary containing the <pdf_name> and <chart_name> for each <record> . I will then create a UI that displays the pdf files.
Can someone direct me to a good tutorial or explanation of how XML parser works? Or if I'm going about this the wrong way I'd be open to suggestions too.
(the XML file is about 8MB)

You might find that my blog post about wrapping NSXMLParser gives you what you need - and possibly a higher level alternative (my wrapper).
For example, using my technique, you'd write methods like:
-(void) handleElement_chartname: (NSDictionary*) attributes;

I suggest you to read the Event-Driven XML Programming Guide for Cocoa. In your specific case, what you need to do is:
In the parser:didStartElement: check for the element name: "airport_name", initialize a new array to store all the record elements (or you can define your own data structure to store the record element), a dictionary to store all element in the record, one string variable to store the current text
In the parser:foundCharacters: append the string to the current text
In the parser:didEndElement: save the dictionary to the array, release the array, save the results.
UPDATED
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict {
if ( [elementName isEqualToString:#"airport_name"]) {
if (!airports)
airports = [[NSMutableArray alloc] init];
NSString *str_icao_ident = [attributeDict objectForKey:#"icao_ident"];
//do something
return;
// ... continued ...
}}

i know this isn't the best way...
I struggled for 2 days trying to adapt the XML parser to my situation. I couldnt grasp it, probably because I'm just so used to doing this in C# and obj-c is new to me...
So what I did was parsed the whole thing as a string.
I converted the entire XML file to a NSString, then used substringToIndex and substringFromIndex to isolate the section I needed (the airport). I then used the </record> tag to create an array of <records>, then wrote a for loop that took the values I needed out of the each array object just by getting the range of the tags.
Like I said, it was a crazy solution, but I did it all in 26 lines of code and it works great.
NSString *path = [[NSBundle mainBundle] pathForResource:#"dttps" ofType:#"xml"];
NSData *data = [[NSData alloc]initWithContentsOfFile:path];
NSString *xmlString = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
NSRange range = [xmlString rangeOfString:searchedAirport];
xmlString = [xmlString substringFromIndex:range.location];
range = [xmlString rangeOfString:#"/airport_name"];
xmlString = [xmlString substringToIndex:range.location];
NSMutableArray *chartNames = [[NSMutableArray alloc]initWithCapacity:100] ;
NSMutableArray *pdfNames = [[NSMutableArray alloc]initWithCapacity:100] ;
NSArray *charts = [xmlString componentsSeparatedByString:#"</record>"];
NSString *tempString = [[NSString alloc]initWithFormat:#""];
int chartsCount = [charts count]-1;
int x;
for (x=0; x < chartsCount; x=x+1) {
tempString = [charts objectAtIndex:x];
range = [tempString rangeOfString:#"<chart_name>"];
tempString = [tempString substringFromIndex:range.location+12];
range = [tempString rangeOfString:#"</chart_name>"];
tempString = [tempString substringToIndex:range.location];
[chartNames addObject:tempString];
tempString = [charts objectAtIndex:x];
range = [tempString rangeOfString:#"<pdf_name>"];
tempString = [tempString substringFromIndex:range.location+10];
range = [tempString rangeOfString:#"</pdf_name>"];
tempString = [tempString substringToIndex:range.location-4];
[pdfNames addObject:tempString];
}
followed by cleanup...

The default XML parsing on the iPhone is pretty tragic compared with contemporary scripting languages. If you are doing serious parsing with complex objects with multiple levels of child objects, then god help you with NSXMLParser.
I would recommend checking out TouchXML which offers a much more civilized solution that is closer to what you might see in a language like Python or Actionscript in terms of its implementation within your own source.

Related

Youtube API auto-complete search

I'm using Youtube API, I'd like to have a search auto-complete feature, just like int the site, when you type into the search input box for iPhone App, it gives you terms suggestions. I've read the docs, but still missing, Is this possible using the API?
Well, i know it's too late to answer here, but i will post this answer because it's something that drove me crazy for a couple of days!!! and hope it will save to others...
So... i'm using this API :
http://suggestqueries.google.com/complete/search?client=youtube&ds=yt&alt=json&q=%#
(q is the query for the autocomplete search).
Now, if you try to open a browser, paste this API and change q=%# to (lets say): q=br, you will notice that some file with the suffix .js is downloaded to your computer.
For some reason, i couldn't parse the JSON like that, so i did that trick:
#property(strong, nonatomic) NSMutableArray *ParsingArray // Put that in .h file or after #interface in your .m file
-(void)autocompleteSegesstions : (NSString *)searchWish{
//searchWish is the text from your search bar (self.searchBar.text)
NSString *jsonString = [NSString stringWithFormat:#"http://suggestqueries.google.com/complete/search?client=youtube&ds=yt&alt=json&q=%#", searchWish];
NSString *URLString = [jsonString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; // Encoding to identify where, for example, there are spaces in your query.
NSLog(#"%#", URLString);
NSData *allVideosData = [[NSData alloc]initWithContentsOfURL:[[NSURL alloc]initWithString:URLString]];
NSString *str = [[NSString alloc]initWithData:allVideosData encoding:NSUTF8StringEncoding];
NSLog(#"%#", str); //Now you have NSString contain JSON.
NSString *json = nil;
NSScanner *scanner = [NSScanner scannerWithString:str];
[scanner scanUpToString:#"[[" intoString:NULL]; // Scan to where the JSON begins
[scanner scanUpToString:#"]]" intoString:&json];
//The idea is to identify where the "real" JSON begins and ends.
json = [NSString stringWithFormat:#"%#%#", json, #"]]"];
NSLog(#"json = %#", json);
NSArray *jsonObject = [NSJSONSerialization JSONObjectWithData:[json dataUsingEncoding:NSUTF8StringEncoding] //Push all the JSON autocomplete detail in to jsonObject array.
options:0 error:NULL];
self.ParsingArray = [[NSMutableArray alloc]init]; //array that contains the objects.
for (int i=0; i != [jsonObject count]; i++) {
for (int j=0; j != 1; j++) {
NSLog(#"%#", [[jsonObject objectAtIndex:i] objectAtIndex:j]);
[self.ParsingArray addObject:[[jsonObject objectAtIndex:i] objectAtIndex:j]];
//Parse the JSON here...
}
}}
That's it. now ParsingArray is the array that contains all autocomplete information from youTube! to be able to change it every time the user clicks another character on the searchBar, use this function:
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText{
[self autocompleteSegesstions:self.searchBar.text];}
Now, this is the main code you should have. to make this code faster (because you can now see that you have a writing delay on the keyboard), use another thread to download ParsingArray or use Asynchronous block. (just insert the content of the first method to Async block...)
Remember- maybe there is another way to implement autocomplete youTube search much better then this, but i just did not found it, and i searched a lot! if anyone know a better way, i'll be more then glad if he post it here.
Have fun!!!
Not the Youtube API -- but you can use the Google Suggest API. Make calls to this URL:
http://suggestqueries.google.com/complete/search?client=youtube&ds=yt&q=QUERY
Which will return a json response of the suggest terms that your app can parse and display. If you prefer XML to json, change
client=youtube
to
output=toolbar
(leave the rest of the parameters the same).

Extracting HTML Attribute from string iPhone

I have an html string that I get from the response of a website. Everything I do there works awesome and I have no difficulty. What I need to go is grab the only href attribute within the html. What is the best approach for getting this URL that is contained within that attribute. I am open to any external libraries if that is necessary, I just want the most efficient way possible. Thanks.
Use this API to parse the HTML code and pick the elements you want.
ElementParser is lightweight framework to provide easy access to xml and html content. Rather than get lost in the complexities of the HTML and XML specifications, it aspires to not obscure their essential simplicity. It doesn’t do everything, it aspires to do “just enough”.
Source: http://touchtank.wordpress.com/element-parser/
Here is an example of how to use the ElementParser with your own example. I hope this is helpful.
Merry Xmas! Ho-Ho-Ho
// Here you create the parser, don't forget to #import "Element.h" and #import "ElementParser.h"
ElementParser * parser = [[ElementParser alloc] init];
// This is the HTML source code that you want to parse
DocumentRoot* document = [parser parseHTML:#"<html>Google Link</html>"];
// Create an array where you will put all the <a></a> elements
NSArray* elements = [document selectElements: #"a"];
// Iterate though the array, for each element pick the "href" attribute
NSMutableArray* results = [NSMutableArray array];
for (Element* element in elements){
NSString* snipet = [element attribute:#"href"];
// Add the result for each element to the "results" array
[results addObject: snipet];
}
// Print the results on the screen
NSLog(#"%#",[results componentsJoinedByString: #"\n"]);
You could use NSRegularExpresion for extracting the url of the html tag.
NSString *regexStr = #"http?://([-\\w\\.]+)+(:\\d+)?(/([\\w/_\\.]*(\\?\\S+)?)?)?";
NSString * url = #"stackoverflow";
NSError *error;
NSRegularExpression *testRegex = [NSRegularExpression regularExpressionWithPattern:regexStr options:0 error:&error];
if( testRegex == nil ) NSLog( #"Error making regex: %#", error );
NSRange range = [testRegex rangeOfFirstMatchInString:url options:0 range:NSMakeRange(0, [url length])];
NSString * href = [url substringWithRange:range];
Bear in mind that NSRegularExpression needs IOS 4 or 5.

iPhone Development: Get images from RSS feed

I am using the NSXMLParser to get new RSS stories from a feed and am displaying them in a UITableView. However now I want to take ONLY the images, and display them in a UIScrollView/UIImageView (3 images side-by side). I am completely lost. I am using the following code to obtain 1 image from a URL.
NSURL *theUrl1=[NSURL URLWithString:#"http://farm3.static.flickr.com/2586/4072164719_0fa5695f59.jpg"];
JImage *photoImage1=[[JImage alloc] init];
[photoImage1 setContentMode:UIViewContentModeScaleAspectFill];
[photoImage1 setFrame:CGRectMake(0, 0, 320, 170)];
[photoImage1 initWithImageAtURL:theUrl1];
[imageView1 addSubview:photoImage1];
[photoImage1 release];
This is all I have accomplished, and it works, for one image, and I have to specify the exact URL. What would you recommend I do to accomplish this?
Further to my other answer, which uses some helper classes and kinda assumes you're storing stuff with Core Data, here's a pure NSXMLParser way to do it.
In this example I'm assuming you have three UIImageViews setup with tags (100,101,102) so we can access them. First off, the code that starts the parser:
// Set the URL with the images, and escape it for creating NSURL
NSString *rssURLString = #"http://feeds.gettyimages.com/channels/RecentEditorialEntertainment.rss";
NSString *escapedURL = [rssURLString stringByAddingPercentEscapesUsingEncoding:NSASCIIStringEncoding];
NSURL *rssURL = [NSURL URLWithString:escapedURL];
// rssParser is an NSXMLParser instance variable
if (rssParser) [rssParser release];
rssParser = [[NSXMLParser alloc] initWithContentsOfURL:rssURL];
[rssParser setDelegate:self];
success = [rssParser parse]; // return value not used
At this point the parsing starts and NSXMLParser will fire off calls to it's delegate methods as it finds different start and end elements in the XML.
In this example I am only writing the didStartElement method:
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict
{
// look for an attribute called url
if ([attributeDict objectForKey:#"url"]) {
currentString = [attributeDict objectForKey:#"url"];
NSLog(#"Image URL: %#", currentString);
NSString* escapedURL = [currentString stringByAddingPercentEscapesUsingEncoding:NSASCIIStringEncoding];
UIImage *image = [[UIImage alloc] initWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:escapedURL]]];
UIImageView * tmpImageView = (UIImageView*)[scrollView viewWithTag:100+imageCount];
[tmpImageView setImage:image];
NSLog(#"images found: %d", imageCount);
imageCount++;
if (imageCount>2) [rssParser abortParsing];
}
}
Here we look to see if the attributeDict (an NSDictionary object) contains a url attribute. If so, we grab it into currentString and then escape it, just incase it has characters that NSURL will barf on. Then we create an image from that URL and set the appropriate UIImageView image based on the tag numbers. imageCount is a counter; once we've done three images we tell the NSXMLParser to abort parsing the XML.
If your XML puts the URL inside element tags like:
<image>http://example.com/image.jpg</image>
You'll need to do a bit more work with didEndElement and foundCharacters. See the quite excellent Introduction to Event-Driven XML Programming Guide for Cocoa.
I knocked together a quick and dirty app to demo this, you can grab it here.
it sounds like you need to first identify the xml tag that identifies the images in your xml document. you should be able to do this by typing whatever API call you're using into a browser address bar.
once you've done that you can make an array of image urls from the nsxmlparser delegate method that receives new data.
once you have the array of image url's you can do something similar to what you are doing above except that you would use NSURL *theUrl1=[myArray objectAtIndex:...
you can arrange the images just by changing their centre location: image.center = CGPointMake(160,240)..
hope that helps. there are apple docs for nsxmlparser.
You can also try dictionary implementation while fetching data from API call. First you have to identify xml tag that identifies images in xml document, then you can assign each image with its corresponding story as image's key into a dictionary. It will make sure that for particular story only its associated image will be displayed. Then u can use this information later in your application as the requirement varies.
NSMutableDictionary *mutDict = [[NSMutableDictionary allloc]init];
if([elementName isEqualToString:#"story_image"])
{
[mutDict setObject:currentImage forKey:currentStory];
}
I want to suggest you to use JSON instead of xml as it is lightweight data-interchange
format. It would save lot of formatting and effort also.
You can visit this page
http://code.google.com/p/json-frame
It is definitely going to help you.
You have to just download the framework and then use in your application.
To get the live data you have to do same thing as in XMLParsing
NSString *jsonString = [NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:nil];
NSDictionary *JDict = [jsonString JSONValue];
or
NSArray * JArr = [jsonString JSONValue];
depending upon what your data-feed contains.
I've been using NSXMLParser myself and storing the results using CoreData. I use a version of Björn Sållarp's Parser class from his CoreData example code.
My images end up as NSData/Binary in a SQLite database, but they might just as well get put into an array of UIImage for immediate display.
Extract from Parser.m: (from Björn Sållarp)
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
{
if ([elementName isEqualToString:#"imagetag"])
{
UIImage *newImage = [[UIImage alloc] initWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:currentString]]];
NSData *imageData = UIImagePNGRepresentation(newImage);
[currentSearchResult setImage:imageData];
currentString = nil;
return;
}
Called from my view with:
NSString *searchURL = #"http://www.feedurl.com/feed/address/feed.xml";
NSURL *xmlURL = [NSURL URLWithString:searchURL];
Parser *xmlParse = [[Parser alloc] initWithContext:managedObjectContext];
[xmlParse parseXMLFileAtURL:xmlURL parseError:&parseError];
That code assumes your XML document contains image URLs with the tag format:
<imagetag>http://example.com/path/to/image.png</imagetag>
Even if you're not using CoreData, working through the example at that link would be instructional for processing XML with NSXMLParser.

Excel Sheet to iPhone Data -- Point A to Point B

As simple as possible: I have a very simple Excel spreadsheet with just over 1k records. I want to use this as a static datasource for an iPhone application.
What's the best plan of attack?
Possibilities in my mind:
1) Reading the XLS directly as a data source: is there an Obj-C lib for this?
2) Converting the XLS to a format that Obj-C has a lib for... CSV? XML? some native CoreData format?
3) Importing it into something MySQLish and just getting an XML feed from a server.
I would need some help figuring these approaches out. I've never worked with Excel.
1 would be nice, 2 would be probably the best solution for what I am doing right now, and 3 I pretty much know how to do but I am not actually sure if MySQL has an XLS import (I believe MSSQL does).
I had a similar problem at one point. What I ended up doing was writing a simple application that took a CSV file and converted it into a Plist. Then I used the Plist as needed in the app. This code uses cCSVParse. It will use the headers in the first row as the key names to create an array of dictionaries for each successive row. The output is a tidy plist file with all of your data. Use [NSArray arrayWithContentsOfFile:] to pop the data into memory in your app.
CSVParser *parser = [CSVParser new];
[parser openFileWithPath:pathAsString];
NSMutableArray *csvContent = [parser parseFile];
[parser closeFile];
if (pathAsString != nil)
{
NSArray *keyArray = [csvContent objectAtIndex:0];
NSMutableArray *plistOutputArray = [NSMutableArray array];
NSInteger i = 0;
for (NSArray *array in csvContent)
{
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
NSInteger keyNumber = 0;
for (NSString *string in array)
{
[dictionary setObject:string forKey:[keyArray objectAtIndex:keyNumber]];
keyNumber++;
}
if (i > 0)
{
[plistOutputArray addObject:dictionary];
}
i++;
}
NSMutableString *mutableString = [NSMutableString stringWithString:pathAsString];
[mutableString replaceOccurrencesOfString:#".csv" withString:#".plist" options:nil range:NSMakeRange([mutableString length]-4, 4)];
NSURL *url = [NSURL fileURLWithPath:mutableString];
[plistOutputArray writeToURL:url atomically:YES];
I also built a pretty simple UI for this. Maybe I'll clean up the whole project and post it on Google Code.
You might look into rendering the Excel file with a UIWebView instance, and seeing if you can access the DOM within the web view.
If you can access the DOM, you could perhaps use libxml2 or a similar library to write a parser that retrieves data from it.

Convert Excel document (xls) to a plist

I have a pretty straightforward Excel spreadsheet, and I need to use the data in an iPhone app. The xls document has 6 columns, and > 200 rows.
I would like to create a plist from the xls document. How can I convert one to the other, programmatically?
I'm late to the party but I built a desktop utility that will convert CSV to a plist. You can download the binary or use this code, which requires cCSVParse. It uses whatever is in row 0 to create key names, then generates dictionaries for each successive row.
CSVParser *parser = [CSVParser new];
[parser openFileWithPath:pathAsString];
NSMutableArray *csvContent = [parser parseFile];
[parser closeFile];
if (pathAsString != nil)
{
NSArray *keyArray = [csvContent objectAtIndex:0];
NSMutableArray *plistOutputArray = [NSMutableArray array];
NSInteger i = 0;
for (NSArray *array in csvContent)
{
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
NSInteger keyNumber = 0;
for (NSString *string in array)
{
[dictionary setObject:string forKey:[keyArray objectAtIndex:keyNumber]];
keyNumber++;
}
if (i > 0)
{
[plistOutputArray addObject:dictionary];
}
i++;
}
NSMutableString *mutableString = [NSMutableString stringWithString:pathAsString];
[mutableString replaceOccurrencesOfString:#".csv" withString:#".plist" options:nil range:NSMakeRange([mutableString length]-4, 4)];
NSURL *url = [NSURL fileURLWithPath:mutableString];
[plistOutputArray writeToURL:url atomically:YES];
You could do this using a simple formula that you copy and pasted down a column beside each of your 200+ rows.
For example, assuming colum A contains a list of names, and column B contains a matching set of ages you could use a formula such as the following to end up with most of the XML for a plist based dictionary.
=CONCATENATE("<key>Name</key><string>", A1,"</string><key>Age</key><integer>",B1,"</integer>")
You then select all the cells within this new column you can copy and paste into notepad or another text editor to save it as a plist file (you may want to put some hardcoded text into a cell above and below your 200+ rows, in order to get the required tags etc as well...
Ladies and gentlemen,
I tried any other recommended solutions above but because of Unicode characters in my language (Turkish) none of them worked out for me... All unicode characters were all broken. Then I decided to make a tool for this.
I proudly present the simplest way to convert any XLS or XLSX or CVS file to a plist:
http://exceltoplist.herokuapp.com/
Just upload your XLS, XLSX or CSV and download your Apple Plist!
Enjoy!
Note: Because of Heroku's free dyno policy it might take a few moments to browse the page. Just keep waiting for 5-10 seconds to open page.
For OpenOffice, use this formula
=CONCATENATE("<key>number</key><integer>"; A2;"</integer><key>MyString</key><string>";B2;"</string>")
I found the CONCATENATE to work the best for this.
For my purpose I just need to convert CSV with two columns to plist file.
First column is keys and second are values. So, I slightly change Danilo Campos code as following:
CSVParser *parser = [CSVParser new];
[parser openFileWithPath:pathAsString];
NSMutableArray *csvContent = [parser parseFile];
[parser closeFile];
if (pathAsString != nil)
{
NSMutableDictionary *plistOutputArray = [NSMutableDictionary dictionary];
for (NSArray *array in csvContent)
{
NSString *key = (NSString *)([array objectAtIndex:0]);
NSString *value = (NSString *)([array objectAtIndex:1]);
[plistOutputArray setObject:value forKey:key];
}
NSMutableString *mutableString = [NSMutableString stringWithString:pathAsString];
[mutableString replaceOccurrencesOfString:#".csv" withString:#".plist" options:nil range:NSMakeRange([mutableString length]-4, 4)];
NSURL *url = [NSURL fileURLWithPath:mutableString];
[plistOutputArray writeToURL:url atomically:YES];
}
P.S. You can find his initial source code here - http://code.google.com/p/danilobits/source/checkout
Please note that to get his code work now you need to change "Base SDK" to "Latest OS X"
Use http://shancarter.github.io/mr-data-converter/ to convert xls to a Json(just copy & paste)(can re format it by remove white space in http://jsonviewer.stack.hu/). save json to text file named: in.json.
Use plutil command to format json to plist
plutil -convert xml1 in.json -o out.plist