Accessing a specific string using rangeOfString in Objective C - objective-c

I'm using NSURLConnection to pull data from a webpage. I'm looking to find a specific line of text to display in a basic app. I've converted my NSData to an NSString. The program successfully locates the string I'm looking for:
#"Most recent instantaneous value:
However, I need to actually pull and store the string that follows "instantaneous value: myString "
I'm noob, so I'm stuck. Here's my code:
- (void)viewWillAppear:(BOOL)animated
{
[super viewDidAppear:animated];
NSURLRequest *request = [NSURLRequest requestWithURL: [NSURL URLWithString:#"http://waterdata.usgs.gov/ga/nwis/uv?cb_72036=on&cb_00062=on&format=gif_default&period=1&site_no=02334400"] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:60.0];
NSURLConnection *connection = [[NSURLConnection alloc]initWithRequest:request delegate:self];
if (connection) {
// Connect
label.text = #"Connecting...";
myData = [[NSMutableData alloc] init];
} else {
// Error
}
}
- (void)connection:(NSURLConnection *) connection didReceiveData:(NSData *)data
{
[myData appendData:data];
}
-(void)connectionDidFinishLoading: (NSURLConnection *)connection {
response = [[NSString alloc] initWithData:myData encoding:NSUTF8StringEncoding];
label.text = response;
NSString *string1 = [[NSString alloc] initWithData:myData encoding:NSUTF8StringEncoding];
if ([string1 rangeOfString:#"Most recent instantaneous value: "].location == NSNotFound){
NSLog(#"Not found");
} else
{
NSLog(#"Found");
}
connection = nil;
}

You already have half the solution:
NSRange range = [string1 rangeOfString:#"Most recent instantaneous value: "];
if (range.location == NSNotFound) {
NSLog(#"Not found");
} else {
NSLog(#"Found");
// Method 1
NSString *string2 = [string1 stringByReplacingCharactersInRange:range withString:#""];
NSLog(#"%#", string2);
// Method 2
NSString *string3 = [string1 substringFromIndex:range.location+range.length];
NSLog(#"%#", string3);
}
Both methods produce the result you are looking for.

This depends no the format of what follows. You can easily use a regular expression to extract this value if it has a delimiter. For example, let's say it looks like this:
...Most recent instantaneous value: 74219MoredataBlahBLahBlah
This would be extracted using the regular expression "Most recent instantaneous value: ([0-9]+)"
However, if it is something like this
...Most recent instantaneous value: MyValueMoredataBlahBLahBlah
Then you are pretty much out of luck unless it is the same size each time
If it is like this
......Most recent instantaneous value: MyValue MoredataBlahBLahBlah
Then you can get it like this "...Most recent instantaneous value: ([A-Z,a-z]+ )" (note the space at the end inside the parenthesis).
Could you perhaps tell a bit more about the format of your value?
EDIT:
Since you know the length of your string, just do as shown above and when you get here:
NSString *restOfString = [string1 substringFromIndex:theRange.location+theRange.length];
Continue with this: NSString *cutString = [restOfString substringToIndex:8];

A good general tip for this sort of problem is to look at the documented methods in the NSString Class Reference. For example, the section under "Dividing Strings" has a few options for you, including
– substringFromIndex:
Your code might go something like this
NSString *string1 = [[NSString alloc] initWithData:myData encoding:NSUTF8StringEncoding];
NSRange theRange = [string1 rangeOfString:#"Most recent instantaneous value: "];
if (theRange.location == NSNotFound){
NSLog(#"Not found");
} else
{
NSString *restOfString = [string1 substringFromIndex:theRange.location+theRange.length];
}

ComponentSeparatedBy ":" should work.

Related

Strange NSData Output

I have some unexpected results coming from the following code:
- (NSData *)postDataWithDict:(NSDictionary *)postDict
{
// Assume key is urlValid
NSUInteger postCount = [postDict count];
NSMutableArray *buildArray = [[NSMutableArray alloc] initWithCapacity:postCount];
for (NSString *key in postDict) {
//post data is key=value&key=value&key=value...
// start with key
NSMutableString *arrayLine = [NSMutableString stringWithString:key];
[arrayLine appendString:#"="];
// analyze and then append value
id postValue = [postDict objectForKey:key];
if ([postValue isKindOfClass:[NSNumber class]]) {
NSString *valueString = [NSString stringWithFormat:#"%#",postValue];
[arrayLine appendString:valueString];
}
else if ([postValue isKindOfClass:[NSString class]]) {
NSString *urlEncodedString = [self urlEncodeValue:postValue];
[arrayLine appendString:urlEncodedString];
}
else {
NSLog(#"postKey: %#, postValue class:%#", key, [postValue class]);
NSError *jsonError = nil;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:postValue
options:0
error:&jsonError];
if (jsonError != nil) {
NSLog(#"JSON serialization failed: %# - %#", [jsonError localizedDescription], [jsonError userInfo]);
NSLog(#"value: %#", postValue);
}
else {
// need to urlencode
NSString *stringifyJSON = [NSString stringWithUTF8String:[jsonData bytes]];
NSString *urlJSONstring = [self urlEncodeValue:stringifyJSON];
[arrayLine appendString:urlJSONstring];
}
}
[buildArray addObject:arrayLine];
}
NSString *postString = [buildArray componentsJoinedByString:#"&"];
NSData *postData = [postString dataUsingEncoding:NSUTF8StringEncoding];
//testing
NSLog(#"Post Dict: %#", postDict);
NSLog(#"Post Array: %#", buildArray);
NSLog(#"Post String: %#", postString);
NSLog(#"Post Data: %#", [NSString stringWithUTF8String:[postData bytes]]);
return postData;
}
My //testing log results:
Post Dict: {
authenticationString = b3210c0bc6d2c47f4c2f7eeea12e063d;
dataMode = updateSingle;
dateCreated = "374300293.81108";
dateModified = "374609294.313093";
dateSynced = "374610683.588062";
entityName = CommodityTypes;
myName = 21;
sortKey = 21;
username = iPhoneAdamek;
usernameString = iPhoneAdamek;
uuidKey = "53403EAE-DD4F-4226-A979-316EF7F43991";
}
Post Dict looks good. Just what I wanted.
2012-11-14 13:31:23.640 FoodyU[11393:907] Post Array: (
"myName=21",
"dataMode=updateSingle",
"dateSynced=374610683.588062",
"uuidKey=53403EAE-DD4F-4226-A979-316EF7F43991",
"sortKey=21",
"dateModified=374609294.313093",
"entityName=CommodityTypes",
"dateCreated=374300293.81108",
"authenticationString=b3210c0bc6d2c47f4c2f7eeea12e063d",
"usernameString=iPhoneAdamek",
"username=iPhoneAdamek"
)
Post Array looks good. Strings are all set to be concatenated for a HTTP POST string.
2012-11-14 13:31:23.641 FoodyU[11393:907] Post String: myName=21&dataMode=updateSingle&dateSynced=374610683.588062&uuidKey=53403EAE-DD4F-4226-A979-316EF7F43991&sortKey=21&dateModified=374609294.313093&entityName=CommodityTypes&dateCreated=374300293.81108&authenticationString=b3210c0bc6d2c47f4c2f7eeea12e063d&usernameString=iPhoneAdamek&username=iPhoneAdamek
Post String looks good. I'm ready to convert it to data to use in [NSMutableURLRequest setHTTPBody:postData].
2012-11-14 13:31:23.643 FoodyU[11393:907] Post Data: myName=21&dataMode=updateSingle&dateSynced=374610683.588062&uuidKey=53403EAE-DD4F-4226-A979-316EF7F43991&sortKey=21&dateModified=374609294.313093&entityName=CommodityTypes&dateCreated=374300293.81108&authenticationString=b3210c0bc6d2c47f4c2f7eeea12e063d&usernameString=iPhoneAdamek&username=iPhoneAdamekoneAdamek;
usernameString = iPhoneAdamek;
uuidKey = "53403EAE-DD4F-4226-A
WTF??? How did &username=iPhoneAdamek become &username=iPhoneAdamekoneAdamek;
usernameString = iPhoneAdamek;
uuidKey = "53403EAE-DD4F-4226-A?
I'm fairly new to Cocoa. Is there something wrong with:
NSData *postData = [postString dataUsingEncoding:NSUTF8StringEncoding];
or
NSLog(#"Post Data: %#", [NSString stringWithUTF8String:[postData bytes]]);
You shouldn't be using NSLog of NSData as,
NSLog(#"Post Data: %#", [NSString stringWithUTF8String:[postData bytes]]);
Instead use it as,
NSLog(#"Post Data: %#", [[NSString alloc] initWithData:postData encoding:NSUTF8StringEncoding]);
[NSString stringWithUTF8String:[postData bytes]] always returns unexpected results.
As per documentation for bytes,
bytes: Returns a pointer to the receiver’s contents.
And as per Apple documentation for stringWithUTF8String,
stringWithUTF8String:
Returns a string created by copying the data from a given C array of UTF8-encoded bytes.
Parameters: bytes - A NULL-terminated C array of bytes in UTF8 encoding.
So when you are using [postData bytes], it is not NULL-terminated and hence when you are using with stringWithUTF8String returns the data written in memory till it encounters a NULL-termination.

Dealing with NSDictionary content parsed from Flickr

I'm having an issue properly accessing an NSDictionary built from Flickr data (the flickr.photosets.getPhotos call). Instead of just showing the content of a description tag, it reads the description tag… along with some unnecessary data and quotes.
For example:
NSLog (#"Item description readout: %#", itemDescriptionPre);
yields this response:
Item description readout: {
"_content" = "This is a caption from a photo drawn through Flickr";
}
I've tried to modify the NSString with this
NSString *descripTruncated = [itemDescriptionPre substringFromIndex:17];
But it didn't causes a crash at runtime. It also doesn't address the items at the end of the item. I apologize since NSString modifications seem to be talked about a lot here, but I couldn't find circumstances that mirror mine.
Here is some more context to my code:
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
NSString *jsonString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(#"Storing incoming data");
NSDictionary *results = [jsonString JSONValue];
NSLog(#"Building NSDictionary.");
NSArray *photos = [[results objectForKey:#"photoset"] objectForKey:#"photo"];
NSLog(#"Building array from dictionary.");
// Loop through each entry in the dictionary...
for (NSDictionary *photo in photos)
{
NSString *title = [photo objectForKey:#"title"];
NSString *description = [photo objectForKey:#"description"];
[photoTitles addObject:title];
[photoDescriptions addObject:description];
}
NSLog(#"Nicer display for results: %# First image title: %# First image description: %#", results, [photoTitles objectAtIndex:0], [photoDescriptions objectAtIndex:0]);
[self updateDisplay];
}
-(void) updateDisplay{
NSString *capTitle = [[photoTitles objectAtIndex:0] uppercaseString];
photoTitleDisplay.text = capTitle;
NSString *itemDescriptionPre = [photoDescriptions objectAtIndex:0];
NSLog (#"Item description readout: %#", itemDescriptionPre);
}
itemDescriptionPre is actually an NSDictionary. This should work:
NSDictionary *itemDescriptionPre = [photoDescriptions objectAtIndex:0];
NSString *itemDescription = [itemDescriptionPre objectForKey:#"_content"];

NSRegularExpression:enumerateMatchesInString hangs when called more than once

In the context of an iPhone app I am developing, I am parsing some html to extract data to map, using NSRegularExpression. This information is updated whenever the user "pans" the map to a new location.
This works fine the first time or two through, but on the second or third time the function is called, the application hangs. I have used XCode's profiler to confirm I am not leaking memory, and no error is generated (the application does not terminate, it just sits in execution at the point shown below).
When I examine the HTML being parsed, I do not see that it is incomplete or otherwise garbled when the application hangs.
Furthermore, if I replace the regex code with a collection of explicitly address strings, everything works as expected.
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
// receivedData contains the returned HTML
NSString *result = [[NSString alloc] initWithData:receivedData encoding:NSASCIIStringEncoding];
NSError *error = nil;
NSString *pattern = #"description.*?h4>(.*?)<\\/h4>.*?\"address>[ \\s]*(.*?)<.*?zip>.*?(\\d{5,5}), US<";
NSRegularExpression *regex = [NSRegularExpression
regularExpressionWithPattern:pattern
options:NSRegularExpressionDotMatchesLineSeparators
error:&error];
__block NSUInteger counter = 0;
// the application hangs on the next line after 1-2 times through
[regex enumerateMatchesInString:result options:0 range:NSMakeRange(0, [result length]) usingBlock:^(NSTextCheckingResult *match, NSMatchingFlags flags, BOOL *stop){
NSRange range = [match rangeAtIndex:2];
NSString *streetAddress =[result substringWithRange:range];
range = [match rangeAtIndex:3];
NSString *cityStateZip = [result substringWithRange:range];
NSString *address = [NSString stringWithFormat:#"%# %#", streetAddress, cityStateZip];
EKItemInfo *party = [self addItem:address]; // geocode address and then map it
if (++counter > 4) *stop = true;
}];
[receivedData release];
[result release];
[connection release]; //alloc'd previously, so release here.
}
I realize this is going to be difficult/impossible to duplicate, but I was wondering if anyone has run into a similar issue with NSRegularExpression or if there is something obviously wrong here.
I also have encountered the regular expression exception, too. In my case, the problem was Character Encoding. So that I wrote a code to go well with several character encoding. Maybe this code help you.
+ (NSString *)encodedStringWithContentsOfURL:(NSURL *)url
{
// Get the web page HTML
NSData *data = [NSData dataWithContentsOfURL:url];
// response
int enc_arr[] = {
NSUTF8StringEncoding, // UTF-8
NSShiftJISStringEncoding, // Shift_JIS
NSJapaneseEUCStringEncoding, // EUC-JP
NSISO2022JPStringEncoding, // JIS
NSUnicodeStringEncoding, // Unicode
NSASCIIStringEncoding // ASCII
};
NSString *data_str = nil;
int max = sizeof(enc_arr) / sizeof(enc_arr[0]);
for (int i=0; i<max; i++) {
data_str = [
[NSString alloc]
initWithData : data
encoding : enc_arr[i]
];
if (data_str!=nil) {
break;
}
}
return data_str;
}
You can download the whole category library from GitHub and just run it. I wish this helps you.
https://github.com/weed/p120801_CharacterEncodingLibrary
Maybe the answer to this question can be found at: NSRegularExpression enumerateMatchesInString: [...] usingBlock does never stop .
I had this issue solved by passing NSMatchingReportCompletion as option and by setting stop to YES when the match is nil.

How to do different actions depending on ASIHTTPRequest?

I'm using ASIFormDataRequest for uploading an image to TwitPic and I get an response, from here all ok. But on - (void)requestFinished:(ASIHTTPRequest *)request I already have an action for a TwitLonger response (which works correctly). Now how would I do a different action depending of the type of response? I tried setting an string and comparing with if, getting the last thing of the response, but with no luck. This is the way I tried:
- (void)requestFinished:(ASIHTTPRequest *)request
{
NSString *responseString = [request responseString];
NSString *first = [responseString substringFromIndex: [responseString length] - 7];
NSLog(#"%#", first);
if (first == #"</rsp>"+
) {
NSString *responseString = [request responseString];
NSLog(#"%#", responseString);
NSString *result = nil;
// Determine "<div>" location
NSRange divRange = [responseString rangeOfString:#"<mediaurl>" options:NSCaseInsensitiveSearch];
if (divRange.location != NSNotFound)
{
// Determine "</div>" location according to "<div>" location
NSRange endDivRange;
endDivRange.location = divRange.length + divRange.location;
endDivRange.length = [responseString length] - endDivRange.location;
endDivRange = [responseString rangeOfString:#"</mediaurl>" options:NSCaseInsensitiveSearch range:endDivRange];
if (endDivRange.location != NSNotFound)
{
// Tags found: retrieve string between them
divRange.location += divRange.length;
divRange.length = endDivRange.location - divRange.location;
result = [responseString substringWithRange:divRange];
}
tweet.text = result;
NSLog(#"%#", result);
}
} else {
// Use when fetching text data
NSString *responseString = [request responseString];
NSLog(#"%#", responseString);
NSString *result = nil;
// Determine "<div>" location
NSRange divRange = [responseString rangeOfString:#"<content>" options:NSCaseInsensitiveSearch];
if (divRange.location != NSNotFound)
{
// Determine "</div>" location according to "<div>" location
NSRange endDivRange;
endDivRange.location = divRange.length + divRange.location;
endDivRange.length = [responseString length] - endDivRange.location;
endDivRange = [responseString rangeOfString:#"</content>" options:NSCaseInsensitiveSearch range:endDivRange];
if (endDivRange.location != NSNotFound)
{
// Tags found: retrieve string between them
divRange.location += divRange.length;
divRange.length = endDivRange.location - divRange.location;
result = [responseString substringWithRange:divRange];
}
tweet.text = result;
[_engine setAccessToken:token];
[_engine sendUpdate:tweet.text];
[self.parentViewController dismissModalViewControllerAnimated:true];
}
}
}
And this is an example of the response:
<?xml version="1.0" encoding="UTF-8"?>
<rsp stat="ok">
<mediaid>50ia96</mediaid>
<mediaurl>URL GOES HERE</mediaurl>
</rsp>
So ending up, what I want is getting the URL between tags.
Thanks in advance!
Go to their documentation, and scroll down to the "Handling success and failure for multiple requests in delegate methods" section. They give you three alternatives - most of the times it's enough to set the userInfo dictionary when you make the request, and then just read it in your callbacks and take proper action. Some quick code:
set this when you create and start the request:
request.userInfo = [NSDictionary dictionaryWithObjectsAndKeys: #"firstRequestId", #"id", nil];
and then in your callbacks:
if([[request.userInfo objectForKey:#"id"] isEqualToString:#"firstRequestId"]) {
// Handle the request with id 'firstRequestId'
}

Cannot get Length of a NSString - unrecognized selector sent to instance

What I'm trying to get is to search for the Anime Titile's ID, compare the length and perform some action afterwards. Here is what I get in the debugger:
2010-08-09 14:30:48.818 MAL Updater OS X[37415:a0f] Detected : Amagami SS - 06
2010-08-09 14:30:48.821 MAL Updater OS X[37415:a0f] http://mal-api.com/anime/search?q=Amagami%20SS
2010-08-09 14:30:49.635 MAL Updater OS X[37415:a0f] 8676
2010-08-09 14:30:49.636 MAL Updater OS X[37415:a0f] -[NSCFNumber length]: unrecognized selector sent to instance 0x384aa40
2010-08-09 14:30:49.637 MAL Updater OS X[37415:a0f] -[NSCFNumber length]: unrecognized selector sent to instance 0x384aa40
The code in question:
if ([self detectmedia] == 1) { // Detects Media from MPlayer via LSOF
NSLog(#"Detected : %# - %#", DetectedTitle, DetectedEpisode);
NSString * AniID = [self searchanime]; // Perform a Search Operation and Returns the ID of the time from JSON
NSLog(#"%#",AniID);
if (AniID.length > 0) { // Compare the length of AniID to make sure it contains a ID
// Other Action here
}
//Release Detected Title and Episode
[DetectedTitle release];
[DetectedEpisode release];
}
SearchAnime method:
-(NSString *)searchanime{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
//Escape Search Term
NSString * searchterm = (NSString *)CFURLCreateStringByAddingPercentEscapes(
NULL,
(CFStringRef)DetectedTitle,
NULL,
(CFStringRef)#"!*'();:#&=+$,/?%#[]",
kCFStringEncodingUTF8 );
//Set Search API
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:#"http://mal-api.com/anime/search?q=%#",searchterm]];
NSLog(#"%#",[NSString stringWithFormat:#"http://mal-api.com/anime/search?q=%#",searchterm]);
//Release searchterm
[searchterm release];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
//Ignore Cookies
[request setUseCookiePersistence:NO];
//Set Token
[request addRequestHeader:#"Authorization" value:[NSString stringWithFormat:#"Basic %#",[defaults objectForKey:#"Base64Token"]]];
//Perform Search
[request startSynchronous];
// Get Status Code
int statusCode = [request responseStatusCode];
NSString *response = [request responseString];
if (statusCode == 200 ) {
return [self RegExSearchTitle:response]; // Returns ID as NSString
}
else {
return #"";
}
}
RegExSearchTitle
-(NSString *)RegExSearchTitle:(NSString *)ResponseData {
OGRegularExpressionMatch *match;
OGRegularExpression *regex;
//Set Detected Anime Title
regex = [OGRegularExpression regularExpressionWithString:DetectedTitle];
NSEnumerator *enumerator;
// Initalize JSON parser
SBJsonParser *parser = [[SBJsonParser alloc] init];
NSArray *searchdata = [parser objectWithString:ResponseData error:nil];
for (id obj in searchdata) {
// Look in every RegEx Entry until the extact title is found.
enumerator = [regex matchEnumeratorInString:[obj objectForKey:#"title"]];
while ((match = [enumerator nextObject]) != nil) {
// Return the AniID for the matched title
return [obj objectForKey:#"id"];
}
}
// Nothing Found, return nothing
return #"";
}
This behavior is unusual because I have compared the NSString's length in the past and it never failed on me. I am wondering, what is causing the problem?
The declared return type of RegExSearchTitle is NSString *, but that doesn’t force the returned object to actually be an NSString. The "id" element of obj (from the JSON) is a number, so an NSNumber is being returned. The compiler can’t warn you about this because it doesn’t know what classes will be found in a collection.
There are other bugs in the code. Having an unconditional return in a while statement in a for statement does not make sense.
On a side note, by convention Objective-C method names start with a lowercase letter.
Well, it's because you assigned an NSNumber to AniID, not an NSString. NSNumber doesn't have a length method.