My company has an iOS app that interfaces directly with Salesforce, with one major drawback. When a user attempts to upload an illegal character, the app hangs, returns bad values, or results in some other erratic behavior.
I found a really handy piece of code out in the mystical interwebs (thanks, MailDrop), that filters a string and escapes those illegal characters with a forward slash. It then returns the value in a string, ready for upserting/querying.
One thing I'm really having trouble with is getting this to work with our application, though. Our data is stored in a dictionary and is written to in a variety of ways (querying the server, or manual input), and at various times in the application's life cycle. I'm not quite sure how to invoke this nifty 'escapeSosl' in the most efficient method. Is there a way that I can escape these characters every time I write to Core Data? What I'm most afraid of is going through the app and calling this hundreds of times.
Here is the escaping method:
- (NSString *)escapeSosl:(NSString *)src {
// from docs, these are all reserved
NSArray *reserved = [NSArray arrayWithObjects:#"\\", #"&", #"|", #"!", #"{", #"}", #"[", #"]", #"^", #"~", #"*:", #":", #"'" ,#"\"", #"+", #"-", nil];
NSMutableString *s = [NSMutableString stringWithString:src];
NSString *r;
NSEnumerator *e = [reserved objectEnumerator];
while (r = [e nextObject])
[s replaceOccurrencesOfString:r withString:[NSString stringWithFormat:#"\\%#", r] options:NSLiteralSearch range:NSMakeRange(0, [s length])];
return s;
}
What I find complicated is the timing of these escapings, and how to apply that to each situation. For instance, the app queries Salesforce for an Account using a location that was either manually entered or found via reverse geocoding. Here is an example of the app requesting data from the Salesforce Server:
-(void)checkServerForLeadMatchingAddressOnAccount:(SFAccount *)account {
currentAccount = account;
//Parse the account location data, removing the last word in the `street` object
NSString *street = account.shippingStreetAddress;
NSMutableArray *streetArray = [[street componentsSeparatedByString:#" "] mutableCopy];
[streetArray removeLastObject];
street = [streetArray componentsJoinedByString:#" "];
street = [street stringByAppendingString:#"_%"];
NSString *city = account.shippingCity;
NSString *state = account.shippingState;
NSString *zip = account.shippingZIP;
//Check for a matching lead on the server. User the callback methods to proceed (either create new data on the server or download to the device
NSLog(#"is connected: %i", self.isConnectedToServer);
if (self.isConnectedToServer){
requestCheckForLead = [[SFRestAPI sharedInstance] requestForQuery:[NSString stringWithFormat:#"SELECT ID, Company, Name, Street, City, State, PostalCode, Phone FROM Lead WHERE PostalCode = '%#' AND Street LIKE '%#' AND City = '%#' AND State = '%#'", zip, street, city, state]];
[[SFRestAPI sharedInstance] send:requestCheckForLead delegate:self];
openTasks++;
} else {
NSLog(#"I can't connect");
[self.serverDelegate serverObjectManager:self communicationSuccessful:NO withResponse:#"Could not connect to the server."];
}
Another area where I might escape characters is right before initiating an upload to the server. There are a couple things that get uploaded, including an Account, the Account's Contact (1:1 rel.), the Account's Opportunity (1:1 rel.), and an Opportunity's Current Service Levels (1:many rel).
Here is an example of the Contact associated with an Account - it uses both Strings and dictionary references, but has some additional logic for parsing firstName from lastName. I'd like to escape the data in a way that I do not need to worry about the timing of these manipulations.
NSMutableDictionary *contactDict = [NSMutableDictionary new];
if (myAccount.contact.email) {
[contactDict setObject:myAccount.contact.email forKey:#"Email"];
} else {
[contactDict setObject:#"" forKey:#"Email"];
}
if (myAccount.contact.name) {
//Split name in two
NSArray *nameArray = [myAccount.contact.name componentsSeparatedByString:#" "];
NSString *firstName = [nameArray objectAtIndex:0];
NSString *lastName = [nameArray lastObject];
[contactDict setObject:firstName forKey:#"firstName"];
[contactDict setObject:lastName forKey:#"lastName"];
} else {
[contactDict setObject:#"" forKey:#"firstName"];
[contactDict setObject:#"" forKey:#"lastName"];
}
if (myAccount.contact.phone){
[contactDict setObject:myAccount.contact.phone forKey:#"Phone"];
} else {
[contactDict setObject:#"" forKey:#"Phone"];
}
updateContactRequest = [[SFRestAPI sharedInstance] requestForUpdateWithObjectType:#"Contact" objectId:myAccount.createdContactID fields:contactDict];
[[SFRestAPI sharedInstance] send:updateContactRequest delegate:self];
So this may seem like a bit of a large question, but does anyone have some pointers as to how I could escape characters in these various dictionaries, using something similar to the 'escapeSosl' code I included above, whenever things are written to core data?
In this line you build your query
requestCheckForLead = [[SFRestAPI sharedInstance] requestForQuery:[NSString stringWithFormat:#"SELECT ID, Company, Name, Street, City, State, PostalCode, Phone FROM Lead WHERE PostalCode = '%#' AND Street LIKE '%#' AND City = '%#' AND State = '%#'", zip, street, city, state]];
instead of passing street as the parameter pass [self escapeSosl:street] and so on for the other params.
Related
I've implemented a very simple Google suggestions feature based on what I've found here: http://shreyaschand.com/blog/2013/01/03/google-autocomplete-api/
You don't even have to use an XML parser:
+ (NSArray *)suggestionsForQuery:(NSString *)query language:(NSString *)language
{
NSMutableArray *suggestions;
NSString *URLString = [NSString stringWithFormat:#"http://suggestqueries.google.com/complete/search?q=%#&client=toolbar&hl=%#", query, language];
NSError *error = nil;
NSString *XMLString = [NSString stringWithContentsOfURL:[NSURL URLWithString:URLString] encoding:NSUTF8StringEncoding error:&error];
if (error) {
NSLog(#"ERROR {%#}", error.description);
}
else {
XMLString = [XMLString stringByReplacingOccurrencesOfString:#"<?xml version=\"1.0\"?>" withString:#""];
XMLString = [XMLString stringByReplacingOccurrencesOfString:#"<toplevel>" withString:#""];
XMLString = [XMLString stringByReplacingOccurrencesOfString:#"</toplevel>" withString:#""];
XMLString = [XMLString stringByReplacingOccurrencesOfString:#"<CompleteSuggestion>" withString:#""];
XMLString = [XMLString stringByReplacingOccurrencesOfString:#"</CompleteSuggestion>" withString:#""];
XMLString = [XMLString stringByReplacingOccurrencesOfString:#"<suggestion data=\"" withString:#""];
suggestions = [NSMutableArray arrayWithArray:[XMLString componentsSeparatedByString:#"\"/>"]];
[suggestions removeLastObject];
}
return suggestions;
}
It works but only if you already know the two letter language code.
Do you know of any way in which I can guess the two letter language code based on the location the user is searching from?
If it's strictly based on location, I wouldn't like it if let's say a user has gone on vacation in France and all of a sudden suggestions change to Google France.
Should it be based on what language the user is already using on his iOS device?
This correction will actually solve the problem:
NSString *URLString = [NSString stringWithFormat:#"http://suggestqueries.google.com/complete/search?q=%#&client=toolbar&hl=%#", query, [[NSLocale currentLocale] languageCode]];
Still, people living in India and having their language set to French will have Google suggestions based on Google France.
This is more like lessening the weight of the problem than solving it.
I m getting this string from server, then i have to split these queries then and implement to local DB code, so how can i do (handle this string like, split and converting)
sql://query=CREATE%20TABLE%20employee%20(empid%20INT%2C%20empname%20TEXT%2C%20age%20INT%2C%20gender%20TEXT%2C%20sync%20INT)&sync=1&syncquery=select%20*%20from%20employee%20where%20sync%3E0%20limit%2010&onadded=update%20employee%20set%20sync%3D0%20where%20empid%3D%27%25empid%25%27&onmodified=update%20employee%20set%20sync%3D0%20where%20empid%3D%27%25empid%25%27&ondeleted=delete%20from%20employee%20where%20empid%3D%27%25empid%25%27&syncfrequency=300"];
here i done decoding process,by using below code
NSString *decodedText = [query stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSLog(#"Original text: %#", decodedText);`
but i want to split all queries by using keys(query, sync,sync query....), how can i do
please help me
NSString *query=[NSString stringWithFormat:#"%s","query=CREATE%20TABLE%20employee%20(empid%20INT%2C%20empname%20TEXT%2C%20age%20INT%2C%20gender%20TEXT%2C%20sync%20INT)&sync=1&syncquery=select%20*%20from%20employee%20where%20sync%3E0%20limit%2010&onadded=update%20employee%20set%20sync%3D0%20where%20empid%3D%27%25empid%25%27&onmodified=update%20employee%20set%20sync%3D0%20where%20empid%3D%27%25empid%25%27&ondeleted=delete%20from%20employee%20where%20empid%3D%27%25empid%25%27&syncfrequency=300"];
NSString *decodedText = [query stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSArray *firstSplit = [decodedText componentsSeparatedByString:#"&"];
for(NSString *currentString in firstSplit) {
NSDictionary *dict = [[NSMutableDictionary alloc] init];
[dict setValue:[[currentString componentsSeparatedByString:#"query="] lastObject] forKey:#"query"];
[dict setValue:[[currentString componentsSeparatedByString:#"sync="] lastObject] forKey:#"sync"];
[dict setValue:[[currentString componentsSeparatedByString:#"syncquery="] lastObject]forKey:#"syncquery"];
[dict setValue:[[currentString componentsSeparatedByString:#"onadded="] lastObject] forKey:#"onadded"];
[dict setValue:[[currentString componentsSeparatedByString:#"ondeleted="] lastObject] forKey:#"ondeleted"];
[dict setValue:[[currentString componentsSeparatedByString:#"syncfrequency="] lastObject] forKey:#"syncfrequency"];
NSLog(#"%#",dict);
}
Your string represents a query, not an entity. It would have no sense converting it to JSON (unless you want to build a parser SQL-to-JSON in a NO-SQL fashion)..
What do you need to do with the query itself? Directly executing it on a local DB or print it to user?
i have implemented a search bar that searching trough an array of countries(presented in a picker view), the problem is that the user need to type the full country name that it will find it and i want him to be able to type even one letter and it will show the first country that starts with that letter and if types another than it sorts even further etc etc.
Anyone have any ideas??
for(int x = 0; x < countryTable.count; x++){
NSString *countryName = [[countryTable objectAtIndex:x]objectForKey:#"name"];
if([searchedStr isEqualToString:countryName.lowercaseString]){
[self.picker selectRow:i inComponent:0 animated:YES];
flag.image = [UIImage imageNamed:[[countryTable objectAtIndex:i]objectForKey:#"flag"]];
}
}
There's a method on NSArray called filteredArrayUsingPredicate: and a method on NSString called hasPrefix:. Together they do what you need...
NSString *userInput = //... user input as lowercase string. don't call this countryName, its confusing
NSPredicate *p = [NSPredicate predicateWithBlock:^BOOL(id element, NSDictionary *bind) {
NSString countryName = [[element objectForKey:#"name"] lowercaseString];
return [countryName hasPrefix:userInput];
}];
NSArray *filteredCountries = [countryTable filteredArrayUsingPredicate:p];
If you're on iOS 8 or OS X Yosemite, you can do:
NSString *country = countryName.lowercaseString; //"england"
NSString *needle = #"engl";
if (![country containsString:needle]) {
NSLog(#"Country string does not contain part (or whole) of searched country");
} else {
NSLog(#"Found the country!");
}
Else, if on versions below iOS 8:
NSString *country = countryName.lowercaseString; //"england"
NSString *needle = #"engl";
if ([country rangeOfString:needle].location == NSNotFound) {
NSLog(#"Country string does not contain part (or whole) of searched country");
} else {
NSLog(#"Found the country!");
}
Lastly, just iterate through all possible countries and apply this to them all. There might exist more robust solutions out there (like danh's solution with some smaller modifications), but this is by far the easiest to start with.
It is a bit complicated to explain my problem. I am using FGallery library https://github.com/gdavis/FGallery-iPhone and especially its feature to load images from URL addresses. When I hardcode the URL path it works super, but wen I pass a string variable to the class which I have created it doesn't work. I tried to debug it and it seems that everything is ok, there is a string assigned to the variable and everything, but do not show the picture. I am doing this in a loop and using ARC.
-(void) loadSoftia
{
//======================================================================================
//THIS WORKS CORRECTLY!!!
wcSofia = [[NSMutableArray alloc] init];
Webcam *s1 = [[Webcam alloc]init];
s1.description=#"Sofia";
s1.location = #"http://www.ampthillweatherstation.info/currentweather/webcamimage.jpg";
[wcSofia addObject:s1];
//======================================================================================
NSMutableString *urlGetOthersLocations =[NSMutableString stringWithFormat:#"http://%#/WebHandlers/%#", #"192.168.188.204", #"BGCam.ashx"];
ServerResponse *serverResponseOthersLocations = [HelperViewController getJsonDataFromTheServer:urlGetOthersLocations];
if(serverResponseOthersLocations.data!=nil)
{
NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:serverResponseOthersLocations.data
options:NSJSONReadingMutableLeaves|NSJSONReadingMutableContainers
error:nil];
[wcSofia removeAllObjects];
Webcam *wc;
int i=0;
for (NSDictionary *locationsDic in dic){
NSString *key;
for ( key in locationsDic) {
NSLog(#"%# = %#", key, [locationsDic objectForKey: key]);
}
NSLog(#"%#", [locationsDic objectForKey:#"URL"]);
NSLog(#"%# = %#", locationsDic, [locationsDic objectForKey: locationsDic]);
NSString *url =[locationsDic objectForKey:#"URL"];
// NSLog(#"%#", url);
NSMutableString* theString = [NSString stringWithFormat:#"%i ",i];
wc = [[Webcam alloc]initWithLocation: [NSString stringWithFormat:#"%#", url] withDescription:#"description"];
wc.location = url;//DOESNT WORK!
wc.description =#"София";// theString;//[NSString stringWithFormat:#"%#", #"aa"]; // #"София";
wc.location = #"http://media.borovets-bg.com/cams/channel?channel=61";//IT WORKS BECAUSE IT IS HARDCODED
if(wcSofia!=nil)
{
[wcSofia addObject:wc];
NSLog(#"%#", wc.location);
}
i++;
}
}
}
I have commented a section of the code which works and which doesn't.
I suppose that you are not going to need the whole program, because it make requests to my local web server to get the URL addresses and descriptions, but trust me this thing works perfect.
Thank you for your help in solving that mystery.
Edit:
Complete source Code: https://dl.dropboxusercontent.com/u/46512232/FGallery.zip
This is very specific case. I believe someone had already solved this somewhere, but it's not easy for me to find it.
The situation:
1 ) an object will return NSString objects for name address1, address2, phone:
[anObject name];
[anObject address1];
[anObject address2];
[anObject name];
2 ) I would like to use these objects to prepare ABUnknownPersonViewController with initially entered values, so the user will not have to enter them before saving them in Address Book.
I have looked at iOS documents and searched through Google and StackOverflow, can't find the right answer for this simple situation.
Can anyone guide me on this?
Found an answer: It's nicely documented in iOS Developer Library:
http://developer.apple.com/library/ios/#samplecode/QuickContacts/Listings/Classes_QuickContactsViewController_m.html#//apple_ref/doc/uid/DTS40009475-Classes_QuickContactsViewController_m-DontLinkElementID_6
Here is a sample code I implemented to return a ABPersonRecordRef as an object. The error I had experienced was related to not retaining the ABPersonRecordRef object after returning it.
- (id)personRecordUsingModelObj:(id)modelObj {
ABRecordRef aContact = ABPersonCreate();
CFErrorRef anError = NULL;
NSString *name = [NSString stringWithFormat:#"%#", [modelObj name]];
ABRecordSetValue(aContact, kABPersonOrganizationProperty, name, &anError);
ABMultiValueRef phone = ABMultiValueCreateMutable(kABMultiStringPropertyType);
ABMultiValueAddValueAndLabel(phone, [modelObj phone], kABPersonPhoneMainLabel, NULL);
ABRecordSetValue(aContact, kABPersonPhoneProperty, phone, &anError);
CFRelease(phone);
NSString *address = [NSString stringWithFormat:#"%# %#", [modelObj addr1], [modelObj addr2]];
NSMutableDictionary *dictionaryAddress = [[NSMutableDictionary alloc] initWithCapacity:0];
[dictionaryAddress setObject:address forKey:(NSString *)kABPersonAddressStreetKey];
[dictionaryAddress setObject:#"us" forKey:(NSString *)kABPersonAddressCountryCodeKey];
ABMutableMultiValueRef address = ABMultiValueCreateMutable(kABDictionaryPropertyType);
ABMultiValueAddValueAndLabel(address, dictionaryAddress, kABPersonAddressStreetKey, NULL);
[dictionaryAddress release];
ABRecordSetValue(aContact, kABPersonAddressProperty, address, &anError);
CFRelease(address);
if (anError) {
aContact = nil;
}
[(id)aContact autorelease];
return (id)aContact;
}