How to parse website text and displaying in a view - ios

I'd like to load a web page and parse the article text and title only, then display this in a view. Is it possible to do this without using UIWebVIew? If its possible, I'd prefer to use only built in functions and no plugins.
Thank you.

You can create a HTTP reqest to get the html content string, then parse the string.
For example, to get the title of html:
NSError *error = nil;
NSString *html = [NSString stringWithContentsOfURL:[NSURL URLWithString:#"http://www.google.com"] encoding:NSASCIIStringEncoding error:&error];
if(html) {
NSLog(#"HTML %#", html);
NSRange r = [html rangeOfString:#"<title>"];
if (r.location != NSNotFound) {
NSRange r1 = [html rangeOfString:#"</title>"];
if (r1.location != NSNotFound) {
if (r1.location > r.location) {
NSString *title = [html substringWithRange:NSMakeRange(NSMaxRange(r), r1.location - NSMaxRange(r))];
NSLog(#"title %#", title);
}
}
}
} else {
NSLog(#"Error %#", error);
}

If you need to parse a webpage, and get what is relevant as an "article", and its title, without all the other stuff, you need something like Diffbot to retrieve article title, author, text, and content-relative image(s). Diffbot is paid for applications which make more than 10.000 API calls a month (which is not much). Applications like Readability, Pocket (formerly Read It Later) and Instapaper, which allow their users to save parsed articles for later reading across multiple devices, have public APIs. Pocket does not allow its API to be used only for parsing, though; Readability supposedly provides access to its parser ("Content API") upon request (I haven't made that request, though I might need to in future); and Instapaper, which, unlike the other two, I have never used as an application, doesn't really make it clear whether it allows this kind of use.
I hope my answer, though many months late, can still be useful; please reply briefly what solution you have found (if you have).
P.S.: Apparently, as a new user I am only allowed to give you two links; I have removed all the subsequent ones, but the first two are the most useful anyway.

Related

iOS multi-threading response

I have an app that requires data from a web server. I have servers in different countries in order to ensure fast response for local request. At the start of the app I would like to decide which server I should use, so my plan is to send a request to different servers and check which responses first.
I have a check.php on each server and return "ok" as response, and I'm able to call it in different threads, but I have no idea how to set serverURL to the server that first response. Can anyone help? Or is there a better way to achieve what I want?
NSURL *url = [NSURL URLWithString:#"http://server_de/check.php"];
NSError *e = nil;
NSString* result = [NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:&e];
if (result == nil || ![result isEqualToString:#"ok"]) {
serverURL=ServerDE;
}
EDIT: all servers will respond, but the ones on the other side of the world will be slower. I need to set the fastest server, not the slowest one.
Let's say you sent a number of requests and every request overrides your serverURL value when it's finished, that's why you get the latest one in the end. To avoid that you could make serverURL as a property or global variable and then check if serverURL has already value before the assignment and if it has, then do not update it.
if ((result == nil || ![result isEqualToString:#"ok"]) && serverURL != nil) {
serverURL = aServer;
}
Actually as soon as you get the first response you can cancel other request because you don't need their answers anymore.

Gigya phone registration

I'm using Gigya as a single-sign-on system for my iOS app.
It is integrated and I can log in with both Twitter, Facebook and manual email registration.
As both Facebook and Twitter do not return mobile phone numbers, I'm appending this information after successful registration/login along with some other information like e-mail. I am able to successfully update fields in the profile like username, nickname etc, but not phones.
A description of the profile structure can be found here:
http://developers.gigya.com/020_Client_API/020_Accounts/010_Objects/Profile
So I figure to post:
{#"phones": #[#{#"number" : _phoneNumberTextfield.text}]}
as the profile content. Which is apparently alright, since the response has statusReason OK.
All good, and if I add other fields, they get updated. But when I retrieve the profile, there is no phone number there. I tried to append the field "type" as per the definition, but then I get: 400025 Write access validation error.
So the update call tells me everything is OK, but it isn't appending the number to the profile. Adding the type to each number entry in the array of #"phones" gives an access violation.
I've been through Gigya's API spec and can't find any working examples or even JSON examples of this situation; does anyone have a solution for this?
If you're retrieving the profile using accounts.getAccountInfo, make sure you include the "extraProfileFields = phones" parameter. The phones array will not be returned by default.
http://developers.gigya.com/037_API_reference/020_Accounts/accounts.getAccountInfo
The Gigya SDK on the server-side represents data as JSON objects, which have the ability to represent nested objects under a key or arrays.
In the case of the "profile.phone" property on the account, this is stored as an array of objects, as detailed below:
{
"profile": {
"phones": [
{ "type": "phone", "number": "8005551234" },
{ "type": "cell", "number": "8885551234" }
]
}
}
Typically, the when using Gigya's iOS API, it is common to maps these JSON concepts to the the NSMutableDictionary and NSMutableArray classes respectively and then to serialize the data using the NSJSONSerialization class.
So, for example, if we wanted to set the phone numbers on an account with Gigya like shown above, then you would need to accomplish this using something like the following code:
NSMutableDictionary *phone1 = [NSMutableDictionary dictionary];
[phone1 setObject:#"phone" forKey:#"type"];
[phone1 setObject:#"8005551234" forKey:#"number"];
NSMutableDictionary *phone2 = [NSMutableDictionary dictionary];
[phone2 setObject:#"cell" forKey:#"type"];
[phone2 setObject:#"8885551234" forKey:#"number"];
NSMutableArray *phones = [NSMutableArray array];
[phones addObject:phone1];
[phones addObject:phone2];
NSMutableDictionary *profile = [NSMutableDictionary dictionary];
[profile setObject:phones forKey:#"phones"];
NSError *error;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:profile
options:0
error:&error];
GSRequest *request = [GSRequest requestForMethod:#"accounts.setAccountInfo"];
[request.parameters setObject:jsonString forKey:#"profile"];
[request sendWithResponseHandler:^(GSResponse *response, NSError *error) {
if (!error) {
NSLog(#"Success");
// Success! Use the response object.
}
else {
NSLog(#"error");
// Check the error code according to the GSErrorCode enum, and handle it.
}
}];
Alternatively, you could construct a JSON string directly; but the above strategy tends to be much more flexible to any changes that need to be done when adding new properties.

URL encoding iOS NSURL error

URL which opens in Firefox,Chrome browsers on desktop, doesn't open in WebView on iPhone.
This URL is supposedly accessing a GET request.
When creating the NSURL without percentescaping the url doesn't get generated.
When using percentescape the url redirects to a Bad url content.
Is there a different encoding used on desktop browsers and not on the iPhone? or mobile Safari?
Are there different ways to encode the URL in iOS other than using
-stringByAddingPercentEscapesUsingEncoding
-CFURLCreateStringByAddingPercentEscapes
which generates bad request content pages from server.
Any help would be really great, Thanks.
EDIT:
The URL been generated is as below http://something.test.com/iostest/index.html?{"a":"b"}
Managed to figure that not encoding the curly brackets is causing the issue in iOS.
as in
NSString *tempUrlSting = (NSString *)CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)tempURLA,CFSTR("{}"), CFSTR("\""), CFStringConvertNSStringEncodingToEncoding(NSUTF8StringEncoding)));
NSURL *tempUrl=[NSURL URLWithString:tempUrlSting];
If not encoding the braces in the URL but encoding the rest using [Rob's answer][1] as above. When creating the NSURL, the url is empty.
If encoding the braces the URL gets generated fine, but the server throws an exception.
This Question suggests to use CFNetworking.
EDIT
Used CFNetworking as below
-(void)getDataFromUrl{
CFStringRef tempURLA = CFSTR("http://my.test.server/iostest/index.html?{\"a\":\"b\"}");
CFStringRef tempUrlSting = CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)tempURLA,CFSTR("{}"), CFSTR("\""), CFStringConvertNSStringEncodingToEncoding(NSUTF8StringEncoding));
CFURLRef myURL = CFURLCreateWithString(kCFAllocatorDefault, tempUrlSting, NULL);
CFStringRef requestMethod = CFSTR("GET");
CFHTTPMessageRef myRequest = CFHTTPMessageCreateRequest(kCFAllocatorDefault, requestMethod, myURL,kCFHTTPVersion1_1);
CFStringRef headerFieldName = CFSTR("Accept");
CFStringRef headerFieldValue = CFSTR("text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8");
CFHTTPMessageSetHeaderFieldValue(myRequest, headerFieldName, headerFieldValue);
[self performHTTPRequest:myRequest];
}
-(void)performHTTPRequest:(CFHTTPMessageRef)request {
CFURLRef gotdatab = (__bridge CFURLRef)(CFBridgingRelease(CFHTTPMessageCopyRequestURL(request)));
// NSLog(#"(CFHTTPMessageRef request %#",gotdatab);
CFReadStreamRef requestStream = CFReadStreamCreateForHTTPRequest(NULL, request);
CFReadStreamOpen(requestStream);
NSMutableData *responseBytes = [NSMutableData data];
NSError *error;
while (TRUE) {
if (CFReadStreamHasBytesAvailable(requestStream)) {
UInt8 streambuffer[1024];
int readBytes = CFReadStreamRead (requestStream,streambuffer,sizeof(streambuffer));
NSLog(#"Read: %d",readBytes);
[responseBytes appendBytes:streambuffer length:readBytes];
}
if (CFReadStreamGetStatus(requestStream) == kCFStreamStatusError) {
error = (NSError*)CFBridgingRelease(CFReadStreamCopyError (requestStream));
if ([error code] == 61) {
// connection refused
NSLog(#"Error occured: %d",[error code]);
}
break;
}
if (CFReadStreamGetStatus(requestStream) == kCFStreamStatusAtEnd) {
NSLog(#"Stream reached end!");
error = nil;
break;
}
}//
CFHTTPMessageRef response = (CFHTTPMessageRef)CFReadStreamCopyProperty(requestStream, kCFStreamPropertyHTTPResponseHeader);
if (response==NULL) {
NSLog(#"response is null");
return;
}
}
The above was done using examples from here and here
Above method still has the same issue. That is: if {} are not encoded the URL doesn't get generated. If the {} are encoded the server doesn't return a proper value.
Any suggestions pls?
Sometimes URL encoded format already except for the é-character which should probably be encoded as %c3%a9. Desktop browser is quite liberal with invalid URLs, thats why it works in Safari etc.
So if you have a NSString and you want to convert it into a proper URL encoding then use the below method of NSString class.
NSURL* url = [NSURL URLWithString:[strURL stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
You should edit your question showing us an example of your URL and your GET parameters. If you're percent escaping, for example, some reserved character in the domain name or the URL path, that suggests one solution (e.g. stringByAddingPercentEscapesUsingEncoding is fine). If you're percent escaping the broader array of reserved characters in the parameters of a GET request (notably = or +), then stringByAddingPercentEscapesUsingEncoding is simply not up to the job and you'd have to use CFURLCreateStringByAddingPercentEscapes (but only on the parameter keys and their values, not on the full URL string). I use a method like the following on the parameters as I append them to the URL:
- (NSString *)percentEscapeURLParameter:(NSString *)string
{
return CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault,
(CFStringRef)string,
NULL,
(CFStringRef)#":/?#!$&'()*+,;=",
kCFStringEncodingUTF8));
}
If you're saying that CFURLCreateStringByAddingPercentEscapes is not working for you, you'd have to show us how you're using it. Make sure you are doing it just on the GET parameter values, that you're supplying the necessary "legal characters to escape" parameter, that you're not escaping something that shouldn't be, etc.

Quickblox user search from multiple sources

I have a requirement to search QuickBlox users whose IDs match their
Facebook IDs OR
Twitter IDs OR
Login IDs
So I am firing multiple QBUser queries at once, using following code:
[QBUsers usersWithLogins:[NSArray arrayWithObject:m_searchString] delegate:self];
[QBUsers usersWithFacebookIDs:[NSArray arrayWithObject:m_searchString] delegate:self];
[QBUsers usersWithTwitterIDs:[NSArray arrayWithObject:m_searchString] delegate:self];
So for example, if I give search string as "Testuser" - it should search all users having login = Testuser, FB login = Testuser, and Twitter login = Testuser.
Based on source of request (FB/Twitter/My own app), I need to put results in different UI parts.
The problem is, I can't differentiate which result comes back for which request.
-(void)completedWithResult:(Result*)result
{
[self showActivityIndicator:NO];
// QuickBlox User creation result
if([result isKindOfClass:[QBUUserPagedResult class]])
{
// Success result
if(result.success)
{
}
}
}
I can see that above code is hit 3 times. But I don't see anything in QBUUserPagedResult class that tells me from which request this result has come.
Something like a tag for request should suffice, but I am not sure what thing it is, looking at the documentation.
Is there anything I can use?
Alternately, is there another approach to what I am trying to achieve (instead of multiple requests)?
I figured it out that context option which is an NSString works like a tag for each Quickblox request:
[QBUsers usersWithLogins:[NSArray arrayWithObject:m_searchString] delegate:self context:MY_STRING_CONSTANT];
Then in delegate function:
- (void)completedWithResult:(Result *)result context:(void *)contextInfo
{
if(result.success && [result isKindOfClass:QBUUserPagedResult.class])
{
NSString * _context = (__bridge NSString *) contextInfo;
if([_context isEqualToString:MY_STRING_CONSTANT])
{
}
}
}
As simple as that, but the documentation doesn't say much about it, or it's not visible as it should be. I had to dig into their forums to figure it out.

iOS Parse Stripe Integration

I'm fairly new to programming and I created an app to charge customers and would like to store their CC information and charge it at a later time. I've been going through all the tutorials and documentation and I am unable to follow how I can integrate this into my app. Do I need to know other technical skills such as Rest API, Curl, Ruby, etc to get this set up? All the guides and documentation is pointing to that direction. I don't really understand what GET/POST is for and how that fits into iOS Objective-C programming.
Any guidance on how to set this up would be tremendously appreciated. I've been stuck on this for some time now.
Parse's stripe API is not as complete as it could and should be. There are many features it does not include natively, but can be accomplished VIA an HTTP Request. I had to learn a little bit of Javascript, and HTTP request to get many features working. Of course your first instinct should tell you do NOT store a CC number on any device ever! Anytime you have a user input a CC number, immediately get a token and then that is all you will need to use.
Luckily stripe gives you the ability to save customers, and attached CC to customers, and then charge that customer in the future without getting the CC number again. Parse's api does not handle adding a CC to a customer so I added the feature myself.
So Step 1 and 2 Generate a Customer using Parse's API, and generate a Token from the CC information they enter again using Parse's API. If you need help with this, and the cloud code required let me know.
Step 3 Add a CC to a customer. I'm using a custom Customer object, but the main thing you really need is the stripe customerId which is customer.identifier in my code, and tokenID from your CC which in my case is token.tokenId. The response back will be a JSON string with the card information, I turn this into a Dictionary, and then create a STPCard from the dictionary. Also I show how to remove a card from a customer.
iOS Code:
+(void)addToken:(STPToken *)token toCustomerId:(NSString *)customerId completionHandler:(PFIdResultBlock)block
{
[PFCloud callFunctionInBackground:#"stripeUpdateCustomer" withParameters:#{#"customerId":customerId,#"data":#{#"card":token.tokenId}} block:block];
}
+ (void)removeCard:(STPCard *)card FromCustomer:(ELCustomer *)customer completion:(STPCardDeletionBlock)handler
{
if (!customer ||!customer.identifier || !card || !card.identifier || !handler) [NSException raise:#"RequiredParameter" format:#"Required Parameter Missing for deleting card from customer"];
[PFCloud callFunctionInBackground:#"stripeDeleteCardFromCustomer" withParameters:#{#"cardId":card.identifier,#"customerId":customer.identifier} block:^(id object, NSError *error)
{
NSDictionary *dict = nil;
NSError *jsonError = nil;
if (object && [object isKindOfClass:[NSString class]] && !error) {
dict = [NSJSONSerialization JSONObjectWithData:[object dataUsingEncoding:NSUTF8StringEncoding] options:kNilOptions error:&jsonError];
}
if (!jsonError && dict) {
handler(dict[#"id"],[dict[#"deleted"] boolValue],error);
}
else if(jsonError) handler(nil,NO,jsonError);
else handler(nil,NO,error);
}];
}
Cloud Code Required:
Parse.Cloud.define("stripeUpdateCustomer", function(request, response)
{
Stripe.Customers.update
(
request.params["customerId"],
request.params["data"],
{
success:function(results)
{
console.log(results["id"]);
response.success(results);
},
error:function(error)
{
response.error("Error:" +error);
}
}
);
});
Parse.Cloud.define("stripeDeleteCardFromCustomer", function(request, response)
{
Stripe.initialize(STRIPE_SECRET_KEY);
Parse.Cloud.httpRequest({
method:"DELETE",
//STRIPE_SECRET_KEY will be your stripe secrect key obviously, this is different from the public key that you will use in your iOS/Android side.
// STRIPE_API_BASE_URL = 'api.stripe.com/v1'
url: "https://" + STRIPE_SECRET_KEY + ':#' + STRIPE_API_BASE_URL + "/customers/" + request.params.customerId + "/cards/" + request.params.cardId,
success: function(httpResponse) {
response.success(httpResponse.text);
},
error: function(httpResponse) {
response.error('Request failed with response code ' + httpResponse.status);
}
});
});
iOS Code for applying a charge to a customer or token notice the required parameters in the dictionary are an amount in cents not dollars, a currency, and then either a customer or a tokenId. Note a customer can have many credit cards, but one of them is the active credit card. The active card is the card that will be charged when you charge a customer:
//Will attempt to charge customer, if no customer exists, or it fails to charge the custoemr it will attempt to charge a card token directly;
//*********Warning: This is the final step it will APPLY A CHARGE TO THE ACCOUNT.***************
-(void)processChargeThroughStripeWithCompletionHandler:(STPChargeCompletionHandler)handler
{
if (![self validForCardProcessing] && ![self validForCustomerProcessing]) {
handler(nil,[NSError errorWithDomain:MY_ERROR_DOMAIN code:elErrorCodeNoCustomerOrTokenID userInfo:[NSDictionary dictionary]]);
return;
}
[self processChargeThroughStripeUsingCustomerWithCompletionHandler:^(STPCharge *charge, NSError *error)
{
if (!error) handler(charge,error);
else{
[self processChargeThroughStripeUsingCardWithCompletionHandler:^(STPCharge *charge, NSError *error) {
handler(charge, error);
}];
}
}];
}
//Process payment using a customer to their active card. No token is required if customer exists with a card on record.
//*********Warning: This is the final step it will APPLY A CHARGE TO THE ACCOUNT.***************
-(void)processChargeThroughStripeUsingCustomerWithCompletionHandler:(STPChargeCompletionHandler)handler
{
if (!self.validForCustomerProcessing)
{
handler(self,[NSError errorWithDomain:MY_ERROR_DOMAIN code:elErrorCodeNoCustomerID userInfo:[NSDictionary dictionary]]);
return;
}
[PFCloud callFunctionInBackground:#"chargeToken" withParameters:[STPCharge dictionaryFromSTPChargeForProccessingUsingCustomer:self] block:^(id object, NSError *error)
{
if (!error)
{
[self initSelfWithDictionary:object];
NSLog(#"object:%#",object);
}
handler(self,error);
}];
}
//Process payment using a token that is attached to the charge, when complete self will be updated with the new charge information
//*********Warning: This is the final step it will APPLY A CHARGE TO THE ACCOUNT.***************
-(void)processChargeThroughStripeUsingCardWithCompletionHandler:(STPChargeCompletionHandler)handler
{
if (!self.validForCardProcessing)
{
handler(self,[NSError errorWithDomain:MY_ERROR_DOMAIN code:elErrorCodeNoTokenID userInfo:[NSDictionary dictionary]]);
return;
}
[PFCloud callFunctionInBackground:#"chargeToken" withParameters:[STPCharge dictionaryFromSTPChargeForProccessingUsingCard:self] block:^(id object, NSError *error)
{
if (!error)
{
[self initSelfWithDictionary:object];
}
handler(self,error);
}];
}
+ (NSDictionary *)dictionaryFromSTPChargeForProccessingUsingCard:(STPCharge *)charge
{
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
dictionary[#"amount"] = charge.amountInCents;
dictionary[#"currency"] = charge.currency;
dictionary[#"card"] = charge.token.tokenId;
return dictionary;
}
+ (NSDictionary *)dictionaryFromSTPChargeForProccessingUsingCustomer:(STPCharge *)charge
{
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
dictionary[#"amount"] = charge.amountInCents;
dictionary[#"currency"] = charge.currency;
dictionary[#"customer"] = charge.customer.identifier;
return dictionary;
}
Cloud code for charging a customer/token:
Parse.Cloud.define("chargeToken",function(request,response)
{
Stripe.initialize(STRIPE_SECRET_KEY);
Stripe.Charges.create
(
request.params,
{
success:function(results)
{
response.success(results);
},
error:function(error)
{
response.error("Error:" +error);
}
}
);
});
How are you storing their CC information to charge it at a later time? Before proceeding, you need to know if it is PCI compliant or not. At most, the only things you should be looking to store is the expiration date, last 4 digits, and an associated record object that Parse Stripe gives you that corresponds to that CC. Do not try to store the full CC.
As to your other questions:
Generally you need to know a web language to do something like this. Here is an example of a possible stack that I've seen in a situation like this:
iOS App -> sends request to Server (rails, python, php, etc) -> Will send request to 3rd party site
3rd party site response -> Server -> iOS app.
The point of the server is to intercept the call from the mobile App to Parse, and the response from Parse back to the mobile app. The reason for this is so you can have a "master" db of the transactions/states and can recover if the app is ever reinstalled on the user's phone. It also will let you store an identifier that points to the user's CC on parse stripe (I'm assuming).
You should really understand GET/POST as they are becoming a very basic feature of any iOS app. They are simply how you get/insert records from a server. Considering almost all of the popular apps have some kind of network connectivity embedded in them, it really is a core part of iOS programming IMO.

Resources