How to display Xpath on the iPhone - iphone

I'm trying to extract the weather information from here using Xpath on the iPhone. As of now it parses all the data but I'm stuck on how to extract the content and display it in a table.
This is what I have so far:
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:[ #"http://aviationweather.gov/adds/metars/?station_ids=1234&std_trans=translated&chk_metars=on&hoursStr=most+recent+only&submitmet=Submit"stringByReplacingOccurrencesOfString:#"1234" withString:self.title]]];
TFHpple * doc = [[TFHpple alloc] initWithHTMLData:data];
NSArray * elements = [doc searchWithXPathQuery:#"//table[1]//tr"];
NSLog(#"%#", elements);
TFHppleElement * element = [elements objectAtIndex:0];
[element content]; // Tag's innerHTML
[element tagName]; // "a"
[element attributes]; // NSDictionary of href, class, id, etc.
[element objectForKey:#"href"]; // Easy access to single attribute
If anybody needs to see what its outputting so far, let me know.
Thanks,
Andrew

I had the same issue I got to the point your at and didn't no where to go but I end up implementing this code. Hope it helps there is still little bits need to make it work correctly but do to the nature of the app I have developed this is all I can give you. its not much more its just the actual implementation into your code that you need really.
#import "XPathQuery.h"
NSMutableArray *weatherArray = [[NSMutableArray arrayWithArray:0]retain]; // Initilize the NSMutableArray can also be done with just an NSArray but you will have to change the populateArray method.
NSString *xPathLookupQuery = #"//table[1]//tr"; // Path in xml
nodes = PerformXMLXPathQuery(data, xPathLookupQuery); // Pass the data in that you need to search through
[self populateArray:weatherArray fromNodes:nodes]; // To populate multiple values into array.
session = [[self fetchContent:nodes] retain]; // To populate a single value and returns value.
- (void)populateArray:(NSMutableArray *)array fromNodes:(NSArray *)nodes
{
for (NSDictionary *node in nodes) {
for (id key in node) {
if ([key isEqualToString:#"nodeContent"]) {
[array addObject:[node objectForKey:key]];
}
}
}
}
You only need either the above code or below code unless you want both.
- (NSString *)fetchContent:(NSArray *)nodes
{
NSString *result = #"";
for (NSDictionary *node in nodes) {
for (id key in node) {
if([key isEqualToString:#"nodeContent"]) {
result = [node objectForKey:key];
}
}
}
return result;
}

Related

Modifying content from new dictionary also modifies the parent dictionary data

As I have a requirement to add similar objects into the array, I have created new dictionary in such a way.
NSMutableDictionary* existingStepDict = [[[arrayForSteps objectAtIndex:0] mutableCopy] autorelease];
[arrayForSteps addObject:existingStepDict];
[existingStepDict release];
Now, what happens here is that later when I change something in any one of the dictionary, the other one also gets updated. I require both these dictionaries to behave independently.
For that I went through Deep-copy of dictionaries whose code is like this.
NSMutableDictionary* existingStepDict = [[[arrayForSteps objectAtIndex:0] mutableCopy] autorelease];
NSMutableDictionary* destination = [NSMutableDictionary dictionaryWithCapacity:0];
NSDictionary *deepCopy = [[NSDictionary alloc] initWithDictionary:existingStepDict copyItems: YES];
if (deepCopy) {
[destination addEntriesFromDictionary: deepCopy];
[deepCopy release];
}
//add Properties array to Steps Dictionary
[arrayForSteps addObject:destination];
But this too didn't reflect the difference. I know I am making some minor mistake here.
But could some one help me getting my result?
Thanks a lot!
There's an easy way to get a full deepcopy of an NSDictionary o NSArray using the NSCoding (serialization) protocol.
- (id) deepCopy:(id)mutableObject
{
NSData *buffer = [NSKeyedArchiver archivedDataWithRootObject:mutableObject];
return [NSKeyedUnarchiver unarchiveObjectWithData: buffer];
}
In this way you can duplicate any object plus all the obects it contains in a single step.
when I need a mutable deep copy of a NSDictionary I create a Category with this method:
- (NSMutableDictionary *)mutableDeepCopy
{
NSMutableDictionary *returnDict = [[NSMutableDictionary alloc] initWithCapacity:[self count]];
NSArray *keys = [self allKeys];
for (id key in keys) {
id oneValue = [self valueForKey:key];
id oneCopy = nil;
if ([oneValue respondsToSelector:#selector(mutableDeepCopy)]) {
oneCopy = [oneValue mutableDeepCopy];
} else if ([oneValue respondsToSelector:#selector(mutableCopy)]) {
oneCopy = [oneValue mutableCopy];
}
if (oneCopy == nil) {
oneCopy = [oneValue copy];
}
[returnDict setValue:oneCopy forKey:key];
}
return returnDict;
}
EDIT
and searching the web I found this, I haven't tested
NSMutableDictionary *mutableCopy = (NSMutableDictionary *)CFPropertyListCreateDeepCopy(kCFAllocatorDefault, (CFDictionaryRef)originalDictionary, kCFPropertyListMutableContainers);

When I use the TFHpple parser HTML on iPhone , and already find the node ,but the content return is NULL?

the Code:
NSString *linkStr=#"http://www.voanews.com/content/obama_pledges_aid_to_drought_stricken_farmers/1484380.html";
NSData *data = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:linkStr]];
// Create parser
TFHpple *xpathParser = [[TFHpple alloc] initWithHTMLData:data];
//Get all the cells of the 2nd row of the 3rd table
NSArray *elements = [xpathParser searchWithXPathQuery:#"//p[#class='article_date']"];
// Access the first cell
if ([elements count] > 0)
{
TFHppleElement *element = [elements objectAtIndex:0];
// Get the text within the cell tag
NSString *content = [element content];
NSLog(#"VOA = %#",content); //Result : print NULL
}
[xpathParser release];
[data release];
but I use the XPath Helper query the "//p[#class='article_date']" ,it's ok, but in my code the content is null
Running your code example, if I change [element content] for [element text], my output is:
VOA = August 11, 2012
In its Github repo, they mention (at USAGE section):
[e text]; // The text inside the HTML element (the content of the
first text node)
And looking at the source code of the CONTENT method it uses objectForKey, where TFHppleContentKey = "nodeContent". See:
static NSString * const TFHppleNodeContentKey = #"nodeContent"
// Returns this tag's innerHTML content.
- (NSString *) content
{
return [node objectForKey:TFHppleNodeContentKey];
}
It seems that it's safe to use [element text] instead of [element content] in your example.
I hope it helps.

Property 'jsonData' not found on object of type 'MapViewController *'

I am trying to draw the route on the graphic layer of my app application and i do not know is this the right approach to do it, or is there another way to do it? Where i am trying to use NSArray with ArgGIS to draw out the map am i have problem with it.
*edit I tried to change the NSArray back to a JSON string and try to draw it using ArcGIS with a JSON string
This is what i have done:
NSArray *BusRoute=[jsonResult objectForKey:#"BusRoute"];
int i;
int count = [BusRoute count];
for (i = 0; i < count; i++)
{
NSDictionary *dic = [BusRoute objectAtIndex: i];
NSString *Duration = [dic valueForKey:#"Duration"];
//---PATH---
NSArray *PATH = [dic valueForKey:#"PATH"];
NSLog(#"PATH = %#", PATH);
self.path = PATH;
}
NSError *writeError = nil;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:path options:NSJSONWritingPrettyPrinted error:&writeError];
NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
NSLog(#"JSON Output: %#", jsonString);
if (self.jsonString) {
// symbolize the returned route graphic
self.jsonString.routeGraphic.symbol = [self routeSymbol];
// add the route graphic to the graphic's layer
[self._graphicsLayer addGraphic:self.jsonString.routeGraphic];
// tell the graphics layer to redraw
[self._graphicsLayer dataChanged];
}
*Output for JSON string
JSON Output: [
[
"38909,35576;38872,35589;38861,35593;38848,35597;38697,35650;38695,35651;38695,35651;38609,35681;38583,35689;38553,35697;38508,35700;38476...;29560,40043"
]
]
This is a portion of the path that i have to draw on the map:
PATH = (( "38909,35576;38872,35589;38861,35593;38848,35597;38697,35650;38695,35651;38695,35651;38609,35681;38583,35689;38553,35697;38508,35700;38476,35696;38476,35696;....))
for this line self.jsonData.routeGraphic.symbol = [self routeSymbol]; i am getting an error Property 'Property 'jsonData' not found on object of type 'MapViewController *'
what should i do to solve? pls help
*How can i draw the line of the path using the NSArray and using ArcGIS?
routeGraphic property does not have any association with NSArray Class. NSArray is the Collection.
Your code is a little strange.
what is routeGrphic???
Are not you forgotten the like code below?
YourObject *object = [path objectAtIndex:index];
object.routeGraphic.symbol = [self routeSymbol];

most efficent way to parse XML With NSXMLParser

I am trying to figure out the best approach to parsing large datasets of xml with the xmlparser delegate.. (basically what the heck do i put the data into?)
the xml data will look like this.
<Rows>
<Row ID="76" MANF="JARD" ISMANUF="F" ISSER="F"/>
<Row ID="38" MANF ="SANBIN" ISMANUF ="F" ISSER ="T"/>
<Rows>
I am looking for a high level responses so I can go away and do more research as their is obviously several different ways of going about this.. I would like to know the best/most efficient to store the data coming back from NSXMLParser and also would like something that I will have the ability to cache...
Thus far I have been looking at NSMutabledictionarys however have heard this might not be sutible so I have now started to look at creating my own object.. but the data coming back from my parsing delegate is only compatible with strings.. so if i have a bool value I cannot put it into my object.. any help would be greatly appreciated as I am at a bit of a loss.
I've implemented an efficient enough XML-to-NSDictionary parser. You can return it as an NSMutableDictionary if you want to. I don't currently have a github or anything up, so I'll post the code inline here. It makes use of the TBXML XML parsing library (no XPath support, and read-only parsing, but fairly efficient and high-level).
EDIT: I just realised, my parser was made to parse element names and text inside the elements, but not element attributes, which is how your XML dataset is laid out. Unfortunately, the below code won't parse the attributes. You've got a starting point though, you can change the code which uses ->firstChild and ->nextSibling to read attributes.
XML.h
#interface XML : NSObject
/**
* Constructs an NSDictionary from the provided XML tree.
*
* Uses the default prefix of 'config'.
*/
+ (NSDictionary *)dictionaryForXMLTree:(TBXMLElement *)tree;
/**
* Constructs an NSDictionary from the provided XML tree.
*
* The format of the dictionary keys is:
* section/[subsection/.../]optionName
*/
+ (NSDictionary *)dictionaryForXMLTree:(TBXMLElement *)tree
withPrefix:(NSString *)keyPrefix;
/**
* Iteratively parses configuration areas from the provided XML document.
*
* If an 'list' area is encountered, its immediate children are added to
* the dictionary as a numbered list (i.e list/1/..., list/2/...).
*/
+ (NSDictionary *)dictionaryFromXML:(TBXML *)xmlDoc;
#end
XML.m
NSString *stripHTML(const char* xmlString);
NSString* stripHTML(const char* xmlString)
{
return [[[NSString stringWithUTF8String:xmlString]
stringByReplacingOccurrencesOfString:#"&"
withString:#"&"]
stringByReplacingOccurrencesOfString:#"
"
withString:#""];
}
#implementation XML
#synthesize configDict;
#pragma mark - XML parsing
+ (NSDictionary *)itemisedDictionaryForXMLTree:(TBXMLElement *)tree
withPrefix:(NSString *)keyPrefix
{
NSMutableDictionary *returnValues =
[[NSMutableDictionary alloc] init];
NSUInteger itemNumber = 1;
for (TBXMLElement *option = tree->firstChild;
option != nil;
option = option->nextSibling)
{
if(option->text == NULL)
option->text = "";
NSString *childPrefix = [NSString stringWithFormat:#"%#/%u",
keyPrefix, itemNumber++];
[returnValues setObject:stripHTML(option->text)
forKey:childPrefix];
[returnValues addEntriesFromDictionary:
[self dictionaryForXMLTree:option withPrefix:childPrefix]];
}
return [returnValues autorelease];
}
+ (NSDictionary *)dictionaryForXMLTree:(TBXMLElement *)tree
withPrefix:(NSString *)keyPrefix
{
NSMutableDictionary *returnValues =
[[NSMutableDictionary alloc] init];
for (TBXMLElement *option = tree->firstChild;
option != nil;
option = option->nextSibling)
{
if(option->text == NULL)
option->text = "";
NSString *childPrefix = [NSString stringWithFormat:#"%#/%s",
keyPrefix,
option->name];
[returnValues setObject:stripHTML(option->text)
forKey:childPrefix];
[returnValues addEntriesFromDictionary:
[self dictionaryForXMLTree:option withPrefix:childPrefix]];
}
return [returnValues autorelease];
}
+ (NSDictionary *)dictionaryForXMLTree:(TBXMLElement *)tree
{
return [self dictionaryForXMLTree:tree withPrefix:#"config"];
}
+ (NSDictionary *)dictionaryFromXML:(TBXML *)xmlDoc
{
NSMutableDictionary *config = [[NSMutableDictionary alloc] init];
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
TBXMLElement *rootElement = [xmlDoc rootXMLElement];
if(rootElement != nil)
{
for(TBXMLElement *configArea = rootElement->firstChild;
configArea != nil;
configArea = configArea->nextSibling)
{
NSString *areaName = [NSString stringWithFormat:#"%s",
configArea->name];
if([areaName
isEqualToString:#"list"]) // multiple children with the same name
{
[config addEntriesFromDictionary:
[self itemisedDictionaryForXMLTree:configArea
withPrefix:areaName]];
} else {
[config addEntriesFromDictionary:
[self dictionaryForXMLTree:configArea
withPrefix:areaName]];
}
}
}
[pool release];
return [config autorelease];
}
+ (NSDictionary *)fetchConfig:(NSURL *)atURL
{
TBXML *xmlDoc = [TBXML tbxmlWithURL:atURL];
return [XML dictionaryFromXML:xmlDoc];
}
+ (NSDictionary *)parseConfigFromXMLString:(NSString *)xmlString
{
TBXML *xmlDoc = [TBXML tbxmlWithXMLString:xmlString];
return [XML dictionaryFromXML:xmlDoc];
}

method with 2 return values

I want to call a method which returns two values
basically lets say my method is like the below (want to return 2 values)
NSString* myfunc
{
NSString *myString = #"MYDATA";
NSString *myString2 = #"MYDATA2";
return myString;
return myString2;
}
So when i call it, i would use??
NSString* Value1 = [self myfunc:mystring];
NSString* Value2 = [self myfunc:mystring2];
I guess im doing something wrong with it, can anyone help me out?
Thanks
You can only return 1 value. That value can be a struct or an object or a simple type. If you return a struct or object it can contain multiple values.
The other way to return multiple values is with out parameters. Pass by reference or pointer in C.
Here is a code snippet showing how you could return a struct containing two NSStrings:
typedef struct {
NSString* str1;
NSString* str2;
} TwoStrings;
TwoStrings myfunc(void) {
TwoStrings result;
result.str1 = #"data";
result.str2 = #"more";
return result;
}
And call it like this:
TwoStrings twoStrs = myfunc();
NSLog(#"str1 = %#, str2 = %#", twoStrs.str1, twoStrs.str2);
You need to be careful with memory management when returning pointers even if they are wrapped inside a struct. In Objective-C the convention is that functions return autoreleased objects (unless the method name starts with create/new/alloc/copy).
You have a few options:
NSArray: Just return an array. Pretty simple.
Pointers: Pass in two pointers, and write to them instead of returning anything. Make sure to check for NULL!
Structure: Create a struct that has two fields, one for each thing you want to return, and return one of that struct.
Object: Same a structure, but create a full NSObject subclass.
NSDictionary: Similar to NSArray, but removes the need to use magic ordering of the values.
As you can only return one value/object, maybe wrap them up in an array:
-(NSArray*) arrayFromMyFunc
{
NSString *myString = #"MYDATA";
NSString *myString2 = #"MYDATA2";
return [NSArray arrayWithObjects:myString,myString2,nil];
}
You can then use it like this:
NSArray *arr = [self arrayFromMyFunc];
NSString *value1 = [arr objectAtIndex:0];
NSString *value2 = [arr objectAtIndex:1];
You could pass results back by reference, but this is easy to get wrong (syntactically, semantically, and from memory management point of view).
Edit One more thing: Make sure that you really need two return values. If they are quite independent, two separate function are often the better choice - better reusabilty and mentainable. Just in case you are making this as a matter of premature optimization. :-)
You can only directly return one value from a function. But there is a way of doing it.
-(void) myfuncWithVal1:(NSString**)val1 andVal2:(NSString**)val2
{
*val1 = #"MYDATA";
*val2 = #"MYDATA2";
}
Then to call it outside the method you'd use:
NSString* a;
NSString* b;
[self myfuncWithVal1:&a andVal2:&b];
void myfunc(NSString **string1, NSString **string2)
{
*string1 = #"MYDATA";
*string2 = #"MYDATA2";
}
...
NSString *value1, *value2;
myfunc(&value1, &value2);
Remember that you need to pass a pointer to a pointer when working with strings and other objects.
Wrap the two strings in an NSArray:
- (NSArray*)myFunc
{
NSString *myString = #"MYDATA";
NSString *myString2 = #"MYDATA2";
return [NSArray arrayWithObjects:myString, myString2, nil];
}
NSArray *theArray = [self myFunc]
NSString *value1 = [theArray objectAtIndex:0];
NSString *value2 = [theArray] objectAtIndex:1];
I see everyone has mentioned an NSArray but I'd go with an NSDictionary so the values don't have to be added in order or even at all. This means it is able to handle a situation where you only want to return the second string.
- (NSDictionary*)myFunction {
NSString *myString1 = #"string1";
NSString *myString2 = #"string2";
return [NSDictionary dictionaryWithObjectsAndKeys: myString1, #"key1", myString2, #"key2", nil];
}
NSDictionary *myDictionary = [self myFunction]
NSString *string1 = [myDictionary objectForKey:#"key1"];
NSString *string2 = [myDictionary objectForKey:#"key2"];