iOS html/xml parsing google shopping results with TFHpple - ios

is there any way to parsing google shopping results using TFHpple without using google API (deprecated) but simple using url like for example this: https://www.google.com/search?hl=en&tbm=shop&q=AudiR8 ?
I've tried many types of tags:
...
myCar = #"Audi R8";
myURL = [NSString stringWithFormat:#"https://www.google.com/search?hl=en&tbm=shop&q=%#",myCar];
NSData *htmlData = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:myURL]];
TFHpple *xpath = [[TFHpple alloc] initWithHTMLData:htmlData];
//use xpath to search element
NSArray *elements = [NSArray new];
elements = [xpath searchWithXPathQuery:#"//html//body"]; // <-- tags
...
but nothing to do, always the same output console message: UNABLE TO PARSE.

I've found various problem and finally i've solved all.
First of all it's necessary to encoding URL adding:
myURL = [myURL stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
Then, inside original (and actual) TFHPPLE code (for exactly XPathQuery.m) parsing phase going to crash 'cause any time nodeContent and Raw are NIL.
So, to solve this crash I've changed
[resultForNode setObject:currentNodeContent forKey:#"nodeContent"];
with (ATTENTION FOR BOTH ROWS [resultForNode...:
if (currentNodeContent != nil)
[resultForNode setObject:currentNodeContent forKey:#"nodeContent"];
and:
[resultForNode setObject:rawContent forKey:#"raw"];
with:
if (rawContent != nil)
[resultForNode setObject:rawContent forKey:#"raw"];
I want to remember that, 'cause the harder html code used by google, i decide to use these xpathqueries:
...
NSArray *elementsImages = [NSArray new];
NSArray *elementsPrices = [NSArray new];
elementsImages = [xpath searchWithXPathQuery:#"//html//*[#class=\"psliimg\"]"];
elementsPrices = [xpath searchWithXPathQuery:#"//html//*[#class=\"psliprice\"]"];
...
Another inconvenience is when you decide to use a for or while cycle to retrieve various html pages, in fact if you use:
NSData *htmlData = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:myURL]];
initWithContenctsOfURL many times during the cycle cannot get correctly page (and debug console write the famous UNABLE TO PARSE )so I've decide to change it with:
// Send a synchronous request
NSURLRequest * urlRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:myURL]];
NSURLResponse * response = nil;
NSError * error = nil;
NSData * data = [NSURLConnection sendSynchronousRequest:urlRequest
returningResponse:&response
error:&error];
if (error == nil)
{
// Parse data here
}
And if you don't want to waiting this cycle 'cause it's maded by syncronous NSURLRequests try to call parent method with (and your viewcontroller don't freeze waiting for parser):
_dispatch_queue_t *queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async( _queue, // now i call my google shopping parser cycle
^{
[self GShoppingParser];
});

Can you try changing the below line
NSData *htmlData = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:myURL]];
to
NSData *Data = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:myURL]];
and also the below line
TFHpple *xpath = [[TFHpple alloc] initWithHTMLData:htmlData];
to
TFHpple *xpathParser = [[TFHpple alloc] initWithHTMLData:data];
Let me know if this helps else there is one more line that you may need to change in your code.
happy coding!

Related

NSURL with JSON returns null

I'm keep getting a (null) error when I try to build my NSURL to open another app.
The URL should be
ms-test-app://eventSourceId=evtSrcId&eventID=13675016&eventType=0&json={"meterresults":[{"clean":"2","raw":"2","status":"0"}]}
but when I try to build my URL it's always null.
At first I thought it has something to do with the URL itself, but it's the same as I got it from the example here.
Another thought was that IOS got some problems with the double quotes in the JSON, but I replaced them with %22, but this doesn't work either.
Here is the code, where I build the URL:
NSString *jsonString = [NSString stringWithFormat:#"{%22meterresults%22:[{%22clean%22:%22%#%22,%22raw%22:%22%#%22,%22status%22:%22%#%22}]}", cleanReadingString, rawReadingString, status];
NSLog(#"JSON= %#",jsonString);
//Send the result JSON back to the movilizer app
NSString *eventSourceId = #"evtSrcId";
NSString *encodedQueryString = [NSString stringWithFormat:#"?eventSourceId=%#&eventID=%d&eventType=0&json=%#",
eventSourceId, _eventId, jsonString];[NSCharacterSet URLQueryAllowedCharacterSet]]
NSString *urlStr = [NSString stringWithFormat:#"%#%#",
[_endpointUrls objectForKey:[NSNumber numberWithInt:(int)_selectedEndpoint]],
encodedQueryString];
NSURL *url = [NSURL URLWithString:urlStr];
I don't know where I'm wrong and I would be glad if someone got any idea.
Thanks in advance.
You should really be using NSURLComponents to create URLs rather than trying to format them into a string.
NSDictionary* jsonDict = #{#"clean": #"2", #"raw": #"2", #"status": #"0"};
NSData* jsonData = [NSJSONSerialization dataWithJSONObject:jsonDict options:0 error:NULL];
NSString* jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
NSURLComponents* components = [[NSURLComponents alloc] init];
components.scheme = #"ms-test-app";
components.queryItems = #[
[[NSURLQueryItem alloc] initWithName:#"eventSourceId" value:eventSourceId],
[[NSURLQueryItem alloc] initWithName:#"eventID" value:#(_eventId).stringValue],
[[NSURLQueryItem alloc] initWithName:#"json" value:jsonString]
];
NSURL* url = components.URL;
Once you build the URL that way, it becomes apparent that your string doesn't have a host portion (or more accurately, one of your parameters is being used as the host portion).
The other comments about not being able to send JSON as an URL parameter are incorrect. As long as the system on the other side that is parsing the query string can handle it, you can send anything you want as an URL parameter.

Worklight Adapter Call in IOS Native Passing JSON Parameter

When using JSON.parse(parameter-value) in JavaScript, the adapter invocation is working correctly, however when doing similarly in a native iOS app, it is failing with the following error.
Javascript Adapter Call:
var invocationData = {
adapter : 'TEST_ADAP',
procedure : 'PROC1',
parameters : [JSON.parse(A)],
};
Native Call:
json= // some json value will be come
MyConnect *connectListener = [[MyConnect alloc] initWithController:self];
[[WLClient sharedInstance] wlConnectWithDelegate:connectListener];
WLProcedureInvocationData *myInvocationData = [[WLProcedureInvocationData alloc] initWithAdapterName:#"TEST" procedureName:#"test"];
myInvocationData.parameters = [NSArray arrayWithObjects:json, nil];
for (NSString *str in myInvocationData.parameters) {
NSLog(#"values of account test %#",str);
}
PasswardPage *invokeListener = [[PasswardPage alloc] initWithController:self];
[[WLClient sharedInstance] invokeProcedure:myInvocationData withDelegate:invokeListener];
Your line
myInvocationData.parameters = [NSArray arrayWithObjects:json, nil];
is almost right.
The parameters property should be an NSArray (as you did) but the array must be made of string values - NOT a JSON object.
myInvocationData.parameters = [NSArray arrayWithObjects:#"myValue1", #"myValue2", #"myValue3", nil];
If the data you received is not in this format, you need to first convert it to this format. This is out of the scope of this question.
If you are not sure how to convert your existing format into a valid NSArray, please open a new question (tagged with Objective-C, not worklight).
We can pass JSON as NSString in iOS Native code to invoke adapter
Example
//Created Dictionary
NSMutablec *dict = [[NSMutableDictionary alloc]init];
[dict setObject:#"xyz" forKey:#"Name"];
[dict setObject:#"iOS" forKey:#"Platform"];
//Convert it to JSON Data
NSError *error;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:dict
options:nil
error:&error];
//JSON Data To NSString
NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
WLProcedureInvocationData * invocationData = [[WLProcedureInvocationData alloc] initWithAdapterName:#"XYZAdapter" procedureName:#"FunctionXYZ"];
//Passing jsonString (NSString Created out of JSON Data) as array to set Parameters.
[invocationData setParameters:[NSArray arrayWithObject:jsonString]];
[[WLClient sharedInstance] invokeProcedure:invocationData withDelegate:self];

Shopify Create order

I'm trying to create an order from my IOS app to my Shopify site.
This is what the documentation says I should do.
Create a simple order with only a product variant id.
POST /admin/orders.json
{
"order": {
"line_items": [
{
"variant_id": 447654529,
"quantity": 1
}
]
}
}
It does not say much more.
Here is what I got.
<code>
NSMutableDictionary *lineItem1=[[NSMutableDictionary alloc]init];
[lineItem1 setObject:#"1125533997" forKeyedSubscript:#"variant_id"];
[lineItem1 setObject:#"1" forKeyedSubscript:#"quantity"];
NSMutableArray *lineItems=[[NSMutableArray alloc]init];
[lineItems addObject:lineItem1];
NSMutableDictionary *orders=[[NSMutableDictionary alloc]init];
[orders setObject:lineItems forKeyedSubscript:#"line_items"];
NSError *error;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:orders options:NSJSONWritingPrettyPrinted error:&error];
NSString *myString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
myString=[JuicyApi md5HexDigest:myString];
//Set parameter
NSMutableDictionary *params = [[NSMutableDictionary alloc]init];
[params setObject:myString forKeyedSubscript:#"order"];
//Generate the request with the give settings
NSMutableURLRequest *req = [self getRequestWithFunction:#"admin/orders.json" requestType:#"POST" params:params ssl:true];
</code>
The server is giving me a response that says.
<code>
{"errors":{"order":"expected String to be a Hash"}}
</code>
I tried hashing all of it, only the values, in this example everything in order, can't get it to work. Am I hashing it incorrectly?
What am I missing here?
I also had the same issue. I got fixed by setting the proper header before making the request
setHeader("Content-Type", "application/json")

iOS exec function in background

I am new to multithreading and was wondering how I could run this function in the background? The function simply returns a NSURL that is used for XML parsing and is called from another function. Or is it even worth it to run in the background since the function that calls it does not continue until this function returns its NSURL. Basically, I am just trying to figure out how to speed this up because it is taking a little time to finish!
+ (NSURL *)parserURL {
NSURL *theURL = [NSURL URLWithString:#"http://www.wccca.com/PITS/"];
NSData *data = [[NSData alloc] initWithContentsOfURL:theURL];
TFHpple *xpathParser = [[TFHpple alloc] initWithHTMLData:data];
NSArray *elements = [xpathParser searchWithXPathQuery:#"//input[#id='hidXMLID']//#value"];
if (elements.count >= 1) {
TFHppleElement *element = [elements objectAtIndex:0];
TFHppleElement *child = [element.children objectAtIndex:0];
NSString *idValue = [child content];
NSString *stg = [NSString stringWithFormat:#"http://www.wccca.com/PITS/xml/fire_data_%#.xml", idValue];
NSURL *url = [NSURL URLWithString:stg];
return url;
}
return nil;
}
The main issue with your code is that you are using a blocking operation to get the data from the website. You definitely want to execute this in the background thread. However, I would recommend you to have a look at networking frameworks that help you do these kinds of operations very easily, i.e., AFNetworking,
In any case, the strategy that I would follow to multithread that operation, or a similar one is the following:
It breaks down to dispatching it with GDC, and then executing a receiving completion block back in the main thread with the results.
Here is the code:
Description
First start by declaring your function to receive a block. The block will be executed in the end, once you've finished retrieving and parsing the data. The next thing the code does it asking GDC to execute a block of code in a background queue. When it is done, we ask the code to execute the completion block that was provided as parameter of the function in the main thread, supplying the parsed string to it.
+(void) parserURL:(NSURL *) theURL completion:(void (^) (NSURL *finalURL))completionBlock{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSData *data = [[NSData alloc] initWithContentsOfURL:theURL];
TFHpple *xpathParser = [[TFHpple alloc] initWithHTMLData:data];
NSArray *elements = [xpathParser searchWithXPathQuery:#"//input[#id='hidXMLID']//#value"];
NSURL *url;
if (elements.count >= 1) {
TFHppleElement *element = [elements objectAtIndex:0];
TFHppleElement *child = [element.children objectAtIndex:0];
NSString *idValue = [child content];
NSString *stg = [NSString stringWithFormat:#"http://www.wccca.com/PITS/xml/fire_data_%#.xml", idValue];
url = [NSURL URLWithString:stg];
}else{
url = nil;
}
dispatch_async(dispatch_get_main_queue(), ^{
completionBlock(url);
});
});
}
You call the method the following way:
[URLParser parserURL:[NSURL URLWithString:#"http://www.wccca.com/PITS/"] completion:^(NSURL *finalURL) {
NSLog(#"Parsed string %#", [finalURL absoluteString]);
}];

RNCryptor not working with JSON string

Here are my method's to use RNCryptor to encrypt/decrypt a JSON string that I am sending to the web service. I am using a static IV variable which may be bad practice but please don't focus on that. Here is how I'm doing it:
Note: I'm using Matt Gallagher's NSData+Base64 category found here (at bottom of page)
-(NSString*)encryptString:(NSString*)plaintext withKey:(NSString*)key error:(NSError**)error{
NSData *data = [plaintext dataUsingEncoding:NSUTF8StringEncoding];
NSData *encryptionKey = [NSData dataFromBase64String:key];
NSData *IV = [NSData dataFromBase64String:ENCRYPTION_IV];
RNCryptorEngine *engine = [[RNCryptorEngine alloc] initWithOperation:kCCEncrypt settings:kRNCryptorAES256Settings key:encryptionKey IV:IV error:error];
[engine addData:data error:error];
NSData *encryptedData = [engine finishWithError:error];
NSString *based64Encrypted = [encryptedData base64EncodedString];
NSLog(#"Encrytped: %#", based64Encrypted);
return based64Encrypted;
}
-(NSString*) decryptString:(NSString*)cipherText withKey:(NSString*)key error:(NSError**)error;{
NSData *data = [NSData dataFromBase64String:cipherText];
NSData *encryptionKey = [NSData dataFromBase64String:key];
NSData *IV = [NSData dataFromBase64String:ENCRYPTION_IV];
RNCryptorEngine *engine = [[RNCryptorEngine alloc] initWithOperation:kCCDecrypt settings:kRNCryptorAES256Settings key:encryptionKey IV:IV error:error];
[engine addData:data error:error];
NSData *decryptedData = [engine finishWithError:error];
NSString *decryptedString = [[NSString alloc] initWithData:decryptedData encoding:NSUTF8StringEncoding];
NSLog(#"Decrypted: %#", decryptedString);
return decryptedString;
}
When I use a string like hello world it works fine. When I use a string like {"username":"developer","password":"abcdefG*12"} I imagine it hase something to do with the encoding but I really know what to use.
when I encrypt that string I get a base64 string and when I try to decrypt that I get an empty string.
UPDATE
It looks like it's failing because of the : in the json string.
What's weirder is it only fails with the string is in json format, I thought it was the : cause I tried that first but upon further investigation if I broke any of the JSON requirements ,'s {'s }'s it stopped working. It works with the RNEncryptor however so I'm not sure what I'm doing wrong. Either way, I think we may redesign the current flow
UPDATE 2
Here is where I am calling these methods:
NSDictionary *credentials = #{#"username":#"developer",#"password":#"abcdefG*12"};
NSString *jsonString = [ credentials JSONStringWithOptions:JKSerializeOptionNone error:&error];
NSLog(#"json string: %#", jsonString); //OUTPUTS: {"username":"developer","password":"abcdefG*12"}
CCGEncryption *encryptionObject = [[CCGEncryption alloc] init]; //THIS IS THE OBJECT WHERE THE encrypt/decrypt methods are
NSString *encrypted = [encryptionObject encryptString:jsonString withKey:ENCRYPTION_KEY error:&error];
if(error){
NSLog(#"Error:%#", error); //NO ERROR
}
NSString *decrypted = [encryptionObject decryptString:encrypted withKey:ENCRYPTION_KEY error:&error];
if(error){
NSLog(#"Error:%#", error); //NO ERROR
}
NSLog(#"decrypted: %#", decrypted); //OUTPUT: decrypted:
You're not collecting the data returned by addData:. The engine encrypts/decrypts as you go so that you don't have to hold the entire plaintext and ciphertext in memory. It doesn't accumulate the data unless it has to (for padding reasons). I suspect that the tests that are working are of different lengths than the ones that aren't.
You are correct that using a fixed IV is bad practice. If you use the same IV and key in multiple messages, then it is possible for attackers to recover parts of your messages by comparing the ciphertexts. If you are using AES-CBC without a random IV and an HMAC, then your AES is insecure in several ways. That is the problem RNCryptor was built to address and why the data format looks the way it does.
#jbtule is correct that I didn't particularly mean for people to use the engine directly and haven't heavily documented it, but there's no problem using it, and I can document it better to support that. That said, the engine itself is insanely simple; I just created it as a way to share code between the encryptor and decryptor. There's not much reason to use RNCryptor if you're going to bypass most of the security it provides. For the above code, it'd be a lot simpler to just call the one-shot CCCrypt().

Resources