Return NSRegularexpression results in an array - ios

I am attempting to retrieve image url strings from some text, and then create an array containing all of these image url strings. I think I know how to get the image urls using NSRegularExpression, but I just don't know how to grab each individual result. I'm done find and replace before, but that just involves manipulating one giant string. Here is my code:
-(NSArray*)parseImages:(NSString*)contents {
NSArray* imageArray = [[NSArray alloc] init];
NSError* error = nil;
NSString* imageHandler = #"\(\?:\\<a\\shref=\"\)https\?:\/\/[\^\/\\s]\+\/\\S\+\\\.\(jpg|png|gif\)";
NSRegularExpression *imageGrabber = [NSRegularExpression regularExpressionWithPattern:imageHandler options:NSRegularExpressionCaseInsensitive error:&error];
if (error)
{
NSLog(#"Couldn't create regex with given string and options");
return nil;
} else {
//Code to add each individual match to imageArray
return imageArray;
}

Here's what I use for multiple matches in a regular expression:
NSArray *matchesArray = [self rangesOfString:#"{regex}" inString:aString];
for (NSValue *rangeVal in matchesArray)
{
NSRange range = [rangeVal rangeValue];
if (range.location != NSNotFound) {
// do stuff with the found range - like add to an NSMutableArray!
}
}
And the rangesOfString method:
- (NSArray *)rangesOfString:(NSString *)searchString inString:(NSString *)str {
NSMutableArray *results = [NSMutableArray array];
NSRange searchRange = NSMakeRange(0, [str length]);
NSRange range;
while ((range = [str rangeOfString:searchString options:NSRegularExpressionSearch|NSCaseInsensitiveSearch range:searchRange]).location != NSNotFound) {
[results addObject:[NSValue valueWithRange:range]];
searchRange = NSMakeRange(NSMaxRange(range), [str length] - NSMaxRange(range));
}
return results;
}

Let Apple do the heavy-lifting with NSDataDetector
NSString *text = #"jibberish http://link1.com jibberish http://link2.com jibberish";
NSError *error;
NSDataDetector *dd = [NSDataDetector dataDetectorWithTypes:NSTextCheckingTypeLink error:&error];
NSArray *matches = [dd matchesInString:text options:0 range:NSMakeRange(0, text.length)];
NSMutableArray *links = [NSMutableArray new];
for (NSTextCheckingResult *result in matches) {
[links addObject:[result URL]];
}
NSLog(#"links: %#", links);
NSLog output:
links: (
"http://link1.com",
"http://link2.com"
)
If there is a need to restrict to extension types:
Add the extensions to a set:
NSSet *extensions = [NSSet setWithArray: #[#"jpg", #"png", #"gif"]];
Add only to the array only if the extension is in the set:
NSString *ext = [[url resourceSpecifier] pathExtension];
if ([extensions containsObject:ext]) {
[links addObject:url];
}

Related

Scan for numbers in Objective - C

I wonder how could I able to find all the numbers as follows.
Input
NSString* input = # "1m3s"
The desired output
#[#"1" #"3"]
I have tried the following approach
NSArray *arr = [input componentsSeparatedByCharactersInSet:
[NSCharacterSet characterSetWithCharactersInString:#"ms"]];
it returned me
#[#"1",#"3",#"#"]
Is there a better approach to solve this problem, any suggestion?
Even though this question has been marked as a duplication, I have tested the following answer but did not work.
Update
That could be any string value.
if input is #"11m32s" or #11m32 and then desired output would be #[#"11", #"32"];
if input is #"11x32" or #11x32y and then desired output would be #[#"11", #"32"];
Using a NSScanner would allow you to scan floats as well. In addition your array is populated directly with NSNumber instead of NSString.
NSString * input = # "12m32s";
NSScanner * aScanner = [NSScanner scannerWithString:input];
NSInteger aInt;
NSMutableArray * result = [NSMutableArray array];
while (!aScanner.isAtEnd)
{
if ([aScanner scanInteger:&aInt])
{
[result addObject:#(aInt)];
}
if (!aScanner.isAtEnd)
{
aScanner.scanLocation += 1;
}
}
NSLog(#"%#",result);
Try:
NSString* input = # "1m3s";
NSCharacterSet *nonDigitCharacterSet = [[NSCharacterSet decimalDigitCharacterSet] invertedSet];
NSArray *outArray = [input componentsSeparatedByCharactersInSet:nonDigitCharacterSet];
outArray = [outArray filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"length > 0"]];
To extract numbers you can use Regular Expression.
NSString* input = # "1m3s";
NSString *pattern = #"\\d+"; // searches for one or more digits
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:pattern options:0 error:nil];
NSArray *matches = [regex matchesInString:input options:0 range:NSMakeRange(0, input.length)];
NSMutableArray *result = [[NSMutableArray alloc] init];
for (NSTextCheckingResult *match in matches) {
[result addObject: [input substringWithRange:match.range]];
}
NSLog(#"%#", result);
Or if you want to be more specific and extract the numbers in an expression ##m##s use
NSString* input = # "1m3s";
NSString *pattern = #"(\\d+)m(\\d+)s";
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:pattern options:0 error:nil];
NSTextCheckingResult *match = [regex firstMatchInString:input options:0 range:NSMakeRange(0, input.length)];
if (match) {
NSArray *result = #[[input substringWithRange:[match rangeAtIndex:1]], [input substringWithRange:[match rangeAtIndex:2]]];
NSLog(#"%#", result);
}
I think's it's helpful for you below the code work for me.
NSString *originalString = #"1m3s";
NSMutableArray *arr = [[NSMutableArray alloc] init];
for (int i=0; i<[originalString length]; i++) {
if (isdigit([originalString characterAtIndex:i])) {
NSMutableString *strippedString = [NSMutableString stringWithCapacity:10];
[strippedString appendFormat:#"%c",[originalString characterAtIndex:i]];
[arr addObject:strippedString];
}
}
NSLog(#"%#",arr.description);
Use below code :
NSCharacterSet *nonDigitCharacterSet = [[NSCharacterSet decimalDigitCharacterSet] invertedSet];
NSArray *outArray = [[input componentsSeparatedByCharactersInSet:nonDigitCharacterSet] componentsJoinedByString:#""];
Use this handy approach:
+ (NSArray *)extractNumberFromText:(NSString *)text
{
NSCharacterSet *nonDigitCharacterSet = [[NSCharacterSet decimalDigitCharacterSet] invertedSet];
NSMutableArray *numberArray = [[text componentsSeparatedByCharactersInSet:nonDigitCharacterSet]mutableCopy];
[numberArray removeObject:#""];
return numberArray;
}
Here is code to get your desire output:
NSString* input = #"1m3s";
NSString *newString = [[input componentsSeparatedByCharactersInSet:
[[NSCharacterSet decimalDigitCharacterSet] invertedSet]]
componentsJoinedByString:#""];
NSMutableArray *array = [NSMutableArray array];
for (int i = 0; i < [newString length]; i++) {
NSString *ch = [newString substringWithRange:NSMakeRange(i, 1)];
[array addObject:ch];
}
NSLog(#"%#",array.description);
You can also use this code bellow.
NSString * String = #"24fsd35rf";
NSArray* Array = [String componentsSeparatedByCharactersInSet:
[[NSCharacterSet decimalDigitCharacterSet] invertedSet]];
NSLog(#"%#",[Array filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"self <> ''"]]);

Is there a way to normalize json data from NSJSONSerialization?

It seems that since XCode 6.1, the iPhone 5S, iPhone 6 and iPhone 6+ simulators (all 64-bit) all return data from the following system method differently (keys are ordered differently) than their 32-bit simulator counterparts (e.g. iPhone 5 simulator)
+ (NSData *)dataWithJSONObject:(id)obj options:(NSJSONWritingOptions)opt error:(NSError **)error;
This difference of key ordering caused a problem for me since we calculate the SHA1 of that JSON data (converted to NSString*) and use it for a validation test. Since the ordering changed, the SHA1 changed and the validation fails.
Simplified sample code (non-ARC) to get the SHA1 is below:
NSData* jsonData = [NSJSONSerialization dataWithJSONObject:dict
options:0
error:&error];
NSString * json = [[[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding] autorelease];
NSString * sha1 = [MyUtils computeSHA1:json];
+(NSString*) computeSHA1:(NSString*)input
{
const char *cstr = [input cStringUsingEncoding:NSUTF8StringEncoding];
NSData *data = [NSData dataWithBytes:cstr length:input.length];
NSNumber* dataLen = [NSNumber numberWithUnsignedInteger:data.length];
uint8_t digest[CC_SHA1_DIGEST_LENGTH];
CC_SHA1(data.bytes, dataLen.unsignedIntValue, digest);
NSMutableString* output = [NSMutableString stringWithCapacity:CC_SHA1_DIGEST_LENGTH * 2];
for(int i = 0; i < CC_SHA1_DIGEST_LENGTH; i++)
[output appendFormat:#"%02x", digest[i]];
return output;
}
Apparently, this key ordering difference doesn't happen on the actual devices (previous behavior was preserved).
I also tried with the NSJSONWritingPrettyPrinted option but the JSON ordering is still inconsistent between simulators.
So, the question is: Does anyone have a recommendation on how to normalize such JSON data so as to not be susceptible to key ordering changes? Alternately, is there any way to get the previous (32-bit simulator) behavior?
Key ordering in dictionaries is not guaranteed. If you need them sorted, put them into an array and sort them.
The code below (non-ARC) worked for me to better canonicalize JSON output. The code assumes the class methods below are all in a class called MyUtils.
Simply pass the the NSDictionary to serialize into "canonicalized JSON" to canonicalJSONRepresentationWithDictionary:
The returned NSString* then contains serialized JSON that has the keys ordered lexicographically/alphabetically in a non-human readable format.
+(NSString *) canonicalJSONRepresentationWithDictionary:(NSDictionary *)dict
{
NSMutableString* json = [NSMutableString string];
[json appendString:#"{"];
NSArray* keys = [[dict allKeys] sortedArrayUsingComparator:^NSComparisonResult(NSString* a, NSString* b) {
return [a compare:b];
}];
for (int i = 0; i < keys.count; i++) {
NSString* key = keys[i];
[json appendFormat:#"\"%#\":", key];
if ([dict[key] isKindOfClass:[NSString class]]) {
[json appendFormat:#"\"%#\"", [MyUtils canonicalJSONRepresentationWithString:dict[key]]];
} else if ([dict[key] isKindOfClass:[NSDictionary class]]) {
[json appendString:[MyUtils canonicalJSONRepresentationWithDictionary:dict[key]]];
} else if ([dict[key] isKindOfClass:[NSArray class]]) {
[json appendString:[MyUtils canonicalJSONRepresentationWithArray:dict[key]]];
} else {
return nil;
}
if (i < keys.count - 1) {
[json appendString:#","];
}
}
[json appendString:#"}"];
return json;
}
+(NSString *) canonicalJSONRepresentationWithArray:(NSArray *) array
{
NSMutableString* json = [NSMutableString string];
[json appendString:#"["];
for (int i = 0; i < array.count; i++) {
if ([array[i] isKindOfClass:[NSString class]]) {
[json appendFormat:#"\"%#\"", [MyUtils canonicalJSONRepresentationWithString:array[i]]];
} else if ([array[i] isKindOfClass:[NSDictionary class]]) {
[json appendString:[MyUtils canonicalJSONRepresentationWithDictionary:array[i]]];
} else if ([array[i] isKindOfClass:[NSArray class]]) {
[json appendString:[MyUtils canonicalJSONRepresentationWithArray:array[i]]];
} else {
return nil;
}
if (i < array.count - 1) {
[json appendString:#","];
}
}
[json appendString:#"]"];
return json;
}
+(NSString *) canonicalJSONRepresentationWithString:(NSString *) string;
{
NSDictionary* dict = [NSDictionary dictionaryWithObjectsAndKeys:string, #"a", nil];
NSError * error;
NSData * jsonData = nil;
NSString * json = nil;
jsonData = [NSJSONSerialization dataWithJSONObject:dict
options:0
error:&error];
if (!jsonData) {
NSLog(#"Got an error serializing json: %#", error);
return nil;
} else {
json = [[[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding] autorelease];
}
NSRange colonQuote = [json rangeOfString:#":\""];
NSRange lastQuote = [json rangeOfString:#"\"" options:NSBackwardsSearch];
NSRange range = NSMakeRange(colonQuote.location + 2, lastQuote.location - colonQuote.location - 2);
NSString* rc = [json substringWithRange:range];
return rc;
}

How to delete that rows, when i get text from URL?

i try to get some text from an URL and put it out in a UITextView.
Thats my code in the ViewController:
NSString *urlString = #"http:/...";
NSError *error = nil;
NSData *dataURL = [NSData dataWithContentsOfURL:[NSURL URLWithString:urlString] options:kNilOptions error:&error];
if (error)
NSLog(#"%s: dataWithContentsOfURL error: %#", __FUNCTION__, error);
else
{
NSString *result = [[NSString alloc] initWithData:dataURL encoding:NSUTF8StringEncoding];
NSLog(#"%s: result = %#", __FUNCTION__, result);
// if you were updating a label with an `IBOutlet` called `resultLabel`, you'd do something like:
self.mytextview.text = result;
}
Now the simulator shows that text in my view:
Sorry, the text is german :)
Now how can I hide or delete that html, head, body etc.?
It displays like this because you are downloading the content of that PHP file which is in HTML format.
You can create a method to find & delete each HTML tag between "<" and ">"
- (NSString*)stringByRemovingHTMLtags:(NSString*)string {
NSRange range;
while ((range = [string rangeOfString:#"<[^>]+>" options:NSRegularExpressionSearch]).location != NSNotFound)
string = [string stringByReplacingCharactersInRange:range withString:#""];
return string;
}
Then set your text in the UITextView like this:
self.mytextview.text = [self stringByRemovingHTMLtags:result];
EDIT: In order to get the content of the <p> tag use this method:
- (NSString*)getPtagContentFromString:(NSString*)htmlString {
NSRange startRange = [htmlString rangeOfString:#"<p>"];
if (startRange.location != NSNotFound) {
NSRange targetRange;
targetRange.location = startRange.location + startRange.length;
targetRange.length = [htmlString length] - targetRange.location;
NSRange endRange = [htmlString rangeOfString:#"</p>" options:0 range:targetRange];
if (endRange.location != NSNotFound) {
targetRange.length = endRange.location - targetRange.location;
return [htmlString substringWithRange:targetRange];
}
}
return nil;
}
Then set your text in the UITextView like this:
self.mytextview.text = [self getPtagContentFromString:result];
You should use UIWebView and load your html text like this:
[_webView loadHTMLString:result baseURL:nil];

Replace using regular expression with replacement function

In objective-c (for ios) I want to achieve the same as I can in AS3:
var test:String = "Abba";
var reg:RegExp = /(a)|(b)/g;
var replacement:Function = function (...args):String
{
var $1:String = args[1];//matched 'a'
var $2:String = args[2];//matched 'b'
if($1)
{
//replace a with -
return "-";
}
if ($2)
{
//replace b with +
return "+";
}
return null;
}
var result:String = test.replace(reg, replacement);//A++-
trace(test, result);//Abba A++-
In other words I would like to have ability to identify which capturing group was matched and replace it accordingly, I'm looking for examples on enumerateMatchesInString: but can't find anything that can solve my problem.
enumerateMatchesInString: calls a block with an NSTextCheckingResult for each match,
and rangeAtIndex:idx gives the range of a captured subgroup:
NSString *string = #"Abba";
NSString *pattern = #"(a)|(b)";
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:pattern
options:0
error:NULL];
NSMutableString *newString = [string mutableCopy];
[regex enumerateMatchesInString:string options:0 range:NSMakeRange(0, [string length])
usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
NSRange r1 = [result rangeAtIndex:1];
if (r1.location != NSNotFound) {
[newString replaceCharactersInRange:r1 withString:#"-"];
}
NSRange r2 = [result rangeAtIndex:2];
if (r2.location != NSNotFound) {
[newString replaceCharactersInRange:r2 withString:#"+"];
}
}];
NSLog(#"%#", newString);
// Output: A++-
If the replacement strings have not the same length as the original strings, then it
gets slightly more complicated, because you have to keep track of the length changes
in the resulting string:
NSMutableString *newString = [string mutableCopy];
__block int offset = 0;
[regex enumerateMatchesInString:string options:0 range:NSMakeRange(0, [string length])
usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
NSRange r1 = [result rangeAtIndex:1];
if (r1.location != NSNotFound) {
r1.location += offset;
NSString *repl = #"---";
[newString replaceCharactersInRange:r1 withString:repl];
offset += [repl length] - r1.length;
}
NSRange r2 = [result rangeAtIndex:2];
if (r2.location != NSNotFound) {
r2.location += offset;
NSString *repl = #"++";
[newString replaceCharactersInRange:r2 withString:repl];
offset += [repl length] - r2.length;
}
}];
NSLog(#"%#", newString);
// Output: A++++---

Index of a character from the NSString in iOS

I have an NSString for example "This is my question".I want to find all the indices of the character/substring "i" ie In this case If index starts from 0,then I want 2,5,16 as my answer.
The other answer is a bit of an overkill. Why don't you simply iterate over the characters like this:
NSString *x = #"This is my question";
for (NSUInteger i=0;i<[x length];i++)
{
if ([x characterAtIndex:i]=='i')
{
NSLog(#"found: %d", i);
}
}
It outputs exactly your positions:
found: 2
found: 5
found: 16
I'd like suggest my solution. It is like this:
NSString* str = #"This is my question";
NSArray* arr = [str componentsSeparatedByString: #"i"];
NSMutableArray* marr = [NSMutableArray arr];
NSInteger cnt = 0;
for (NSInteger i = 0; i < ([arr count]); i++)
{
NSString* s = [arr objectAtIndex: i];
cnt += [s length];
[marr addObject: [NSNumber numberWithInt: cnt]];
cnt += [#"i" length];
}
NSLog(#"%#", [marr description]);
On console:
2
5
16
I don't know is there any built-in functions available for doing this. You can use this method:
- (NSMutableArray *)indexOfCharacter:(char)c inString:(NSString*)string
{
NSMutableArray *returnArray = [[NSMutableArray alloc] init];
for(int i=0;i<string.length;i++)
{
if(c == [string characterAtIndex:i])
{
[returnArray addObject:[NSNumber numberWithInt:i]];
}
}
return returnArray;
}
Using NSRange and loop and with some string manipulation you can easily do it.
NSString *string = #"This is my question";
NSString *substring = #"i";
NSRange searchRange = NSMakeRange(0,string.length);
NSRange foundRange;
while (searchRange.location < string.length)
{
searchRange.length = string.length-searchRange.location;
foundRange = [string rangeOfString:substring options:nil range:searchRange];
if (foundRange.location != NSNotFound)
{
// found an occurrence of the char
searchRange.location = foundRange.location+foundRange.length;
NSLog(#"Location of '%#' is %d",substring,searchRange.location-1);
}
}
EDIT
Using NSRegularExpression and NSRange you can do like this.
NSString *string = #"This is my question";
NSString *substring = #"i";
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:substring
options:0
error:NULL];
[regex enumerateMatchesInString:string options:0 range:NSMakeRange(0, [string length])
usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
NSRange range = [result range];
NSLog(#"Location of '%#' is %d",substring, range.location);
}];
output is
Location of 'i' is 2
Location of 'i' is 5
Location of 'i' is 16
This is my attempt at a no loop code of getting what you want. I coded this blind, meaning not-tested etc. Its basically a recursive function, but I think it gets you the general idea.
- (NSArray *)getAllEyes:(NSString *)s index:(int)index) {
if (!s || s.length <= 0 || index >= s.length) return [NSArray new];
NSRange *r = [s rangeOfString(#"i") options:NSLiteralSearch range:NSMakeRange(index, s.length - index)];
if (r.location == NSNotFound) {
return [NSArray new];
} else {
NSMutableArray *array = [NSMutableArray new];
[array addObject:#(r.location)];
[array addObjectsFromArray:[self getAllEyes:s index:r.location + 1]];
return array;
}
}
// usage:
NSArray *allEyes = [self getAllEyes:#""];
for (NSNumber *n in allEyes) {
NSLog(#"i = %#", n);
}

Resources