Created
October 13, 2020 13:55
-
-
Save torinnguyen/4ffae0b328b391fa2dd2bad87b823434 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// NSString+Additions.m | |
// | |
// Created by Daud Abas on 24/2/12. | |
// Copyright (c) 2012 MyCompany. All rights reserved. | |
// | |
#import <time.h> | |
#import "NSString+Additions.h" | |
#import <CommonCrypto/CommonDigest.h> | |
#import "TPLISO8601DateFormatter.h" | |
@implementation NSString (Additions) | |
- (BOOL)isEmpty | |
{ | |
return [[self trim] length] <= 0; | |
} | |
- (BOOL)isNotEmpty | |
{ | |
return [[self trim] length] > 0; | |
} | |
- (NSUInteger)wordCount { | |
NSScanner *scanner = [NSScanner scannerWithString: self]; | |
NSCharacterSet *whiteSpace = [NSCharacterSet whitespaceAndNewlineCharacterSet]; | |
NSUInteger count = 0; | |
while ([scanner scanUpToCharactersFromSet: whiteSpace intoString: nil]) | |
count++; | |
return count; | |
} | |
- (BOOL)contains:(NSString*)needle { | |
if (needle == nil || [needle length] <= 0) | |
return NO; | |
NSRange range = [self rangeOfString:needle options: NSCaseInsensitiveSearch]; | |
return (range.length == needle.length && range.location != NSNotFound); | |
} | |
- (BOOL)containsCaseInsesitive:(NSString*)needle { | |
if (needle == nil || [needle length] <= 0) | |
return NO; | |
NSRange range = [[self lowercaseString] rangeOfString:[needle lowercaseString] options: NSCaseInsensitiveSearch]; | |
return (range.length == needle.length && range.location != NSNotFound); | |
} | |
- (BOOL)startsWith:(NSString*)needle { | |
return [self hasPrefix:needle]; | |
} | |
- (BOOL)endsWith:(NSString*)needle { | |
return [self hasSuffix:needle]; | |
} | |
- (BOOL)isNotEqualToString:(NSString*)anotherString { | |
return [self isEqualToString:anotherString] == NO; | |
} | |
- (BOOL)isEqualToStringIgnoreCase:(NSString *)anotherString { | |
return [[self uppercaseString] isEqualToString:[anotherString uppercaseString]]; | |
} | |
- (NSUInteger)lastIndexOf:(NSString *)needle | |
{ | |
NSRange range = [self rangeOfString:needle options:NSBackwardsSearch]; | |
return range.location; | |
} | |
- (NSInteger)integerValueTrim | |
{ | |
return [[self trimCommaSeparator] integerValue]; | |
} | |
- (CGFloat)floatValueTrim | |
{ | |
return [[self trimCommaSeparator] floatValue]; | |
} | |
- (NSString *)trim | |
{ | |
return [self stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; | |
} | |
- (NSString *)trimHead | |
{ | |
//Leading only | |
NSRange range = [self rangeOfString:@"^\\s*" options:NSRegularExpressionSearch]; | |
return [self stringByReplacingCharactersInRange:range withString:@""]; | |
} | |
- (NSString *)trimTail | |
{ | |
//Trailing only | |
NSRange range = [self rangeOfString:@"\\s*$" options:NSRegularExpressionSearch]; | |
return [self stringByReplacingCharactersInRange:range withString:@""]; | |
} | |
- (NSString *)stringValue | |
{ | |
return self; | |
} | |
- (NSString *)firstCharacter | |
{ | |
if ([self length] <= 1) | |
return self; | |
return [self substringToIndex:1]; | |
} | |
- (NSString *)firstNCharacter:(NSUInteger)n | |
{ | |
if (n <= 0) | |
return nil; | |
if ([self length] <= n) | |
return self; | |
return [self substringToIndex:n]; | |
} | |
- (NSString *)lastCharacter | |
{ | |
if ([self length] <= 1) | |
return self; | |
return [self substringFromIndex:self.length-1]; | |
} | |
- (NSString *)lastNCharacter:(NSUInteger)n | |
{ | |
if (n <= 0) | |
return nil; | |
if ([self length] <= n) | |
return self; | |
return [self substringFromIndex:self.length-n]; | |
} | |
- (NSString *)trimCommaSeparator | |
{ | |
return [self stringByReplacingOccurrencesOfString:@"," withString:@""]; | |
} | |
- (NSString *)substringToString:(NSString *)needle | |
{ | |
NSRange range = [self rangeOfString:needle]; | |
if (range.location == NSNotFound) | |
return self; | |
return [self substringToIndex:range.location]; | |
} | |
- (NSString *)substringToLastString:(NSString *)needle | |
{ | |
NSInteger index = [self lastIndexOf:needle]; | |
if (index == NSNotFound) | |
return self; | |
return [self substringToIndex:index]; | |
} | |
- (NSString *)lowerCaseFirstCharacter | |
{ | |
if ([self length] <= 1) | |
return [self lowercaseString]; | |
NSString *firstCapChar = [[self substringToIndex:1] lowercaseString]; | |
NSString *cappedString = [self stringByReplacingCharactersInRange:NSMakeRange(0,1) withString:firstCapChar]; | |
return cappedString; | |
} | |
- (NSString *)upperCaseFirstCharacter | |
{ | |
if ([self length] <= 1) | |
return [self uppercaseString]; | |
NSString *firstCapChar = [[self substringToIndex:1] uppercaseString]; | |
NSString *cappedString = [self stringByReplacingCharactersInRange:NSMakeRange(0,1) withString:firstCapChar]; | |
return cappedString; | |
} | |
- (NSString *)subSentenceUpToCharacters:(NSUInteger)numChars | |
{ | |
if ([self length] <= numChars) | |
return self; | |
NSArray * sentences = [self componentsSeparatedByString:@". "]; | |
if ([sentences count] <= 1) | |
return [[self substringToIndex:numChars] stringByAppendingString:@"..."]; | |
NSString * outputString = @""; | |
for (NSString * string in sentences) | |
{ | |
if ([outputString length] <= 0) { | |
outputString = string; | |
continue; | |
} | |
if ([outputString length] + [string length] > numChars) | |
break; | |
outputString = [outputString stringByAppendingFormat:@". %@", string]; | |
} | |
return [outputString stringByAppendingFormat:@"..."]; | |
} | |
- (NSData *)dataWithUTF8Encoding | |
{ | |
return [self dataUsingEncoding:NSUTF8StringEncoding]; | |
} | |
- (NSString *)URLEncodedString | |
{ | |
__autoreleasing NSString *encodedString; | |
NSString *originalString = (NSString *)self; | |
encodedString = (__bridge_transfer NSString * ) | |
CFURLCreateStringByAddingPercentEscapes(NULL, | |
(__bridge CFStringRef)originalString, | |
(CFStringRef)@"$-_.+!*'(),&+/:;=?@#", | |
NULL, | |
kCFStringEncodingUTF8); | |
encodedString = [encodedString stringByReplacingOccurrencesOfString:@"%25" withString:@"\%"]; //revert double escape | |
return encodedString; | |
} | |
- (NSString *)URLEncodeEverything | |
{ | |
__autoreleasing NSString *encodedString; | |
NSString *originalString = (NSString *)self; | |
encodedString = (__bridge_transfer NSString * ) | |
CFURLCreateStringByAddingPercentEscapes(NULL, | |
(__bridge CFStringRef)originalString, | |
NULL, | |
(CFStringRef)@"$-_.+!*'(),&+/:;=?@#", | |
kCFStringEncodingUTF8); | |
encodedString = [encodedString stringByReplacingOccurrencesOfString:@"%25" withString:@"\%"]; //revert double escape | |
return encodedString; | |
} | |
- (NSString *)URLDecodeString | |
{ | |
NSString * result = [self stringByReplacingOccurrencesOfString:@"+" withString:@" "]; | |
result = [result stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; | |
return result; | |
} | |
- (NSMutableDictionary *)convertToKeyValueDictionary | |
{ | |
NSArray * keyValuePairs = [self componentsSeparatedByString:@"&"]; | |
NSMutableDictionary * keyValueDictionary = [NSMutableDictionary dictionary]; | |
for (NSString * keyValuePairString in keyValuePairs) | |
{ | |
NSArray *kv = [keyValuePairString componentsSeparatedByString:@"="]; | |
if ([kv count] < 2) | |
continue; | |
NSString * key = [kv objectAtIndex:0]; | |
NSString * value = [kv objectAtIndex:1]; | |
[keyValueDictionary setValidObject:value forKey:key]; | |
} | |
if ([keyValueDictionary count] <= 0) | |
return nil; | |
return keyValueDictionary; | |
} | |
- (NSString *)sha1 { | |
const char *cStr = [self UTF8String]; | |
unsigned char result[CC_SHA1_DIGEST_LENGTH]; | |
CC_SHA1(cStr, (CC_LONG)strlen(cStr), result); | |
NSString *s = [NSString stringWithFormat: | |
@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", | |
result[0], result[1], result[2], result[3], result[4], | |
result[5], result[6], result[7], | |
result[8], result[9], result[10], result[11], result[12], | |
result[13], result[14], result[15], | |
result[16], result[17], result[18], result[19] | |
]; | |
return s; | |
} | |
- (NSString *)sha256 | |
{ | |
const char * s = [self cStringUsingEncoding:NSASCIIStringEncoding]; | |
NSData * keyData=[NSData dataWithBytes:s length:strlen(s)]; | |
uint8_t digest[CC_SHA256_DIGEST_LENGTH] = {0}; | |
CC_SHA256(keyData.bytes, (CC_LONG)keyData.length, digest); | |
NSData * out = [NSData dataWithBytes:digest length:CC_SHA256_DIGEST_LENGTH]; | |
NSString * hash = [out hexString]; | |
hash = [hash stringByReplacingOccurrencesOfString:@" " withString:@""]; | |
hash = [hash stringByReplacingOccurrencesOfString:@"<" withString:@""]; | |
hash = [hash stringByReplacingOccurrencesOfString:@">" withString:@""]; | |
return hash; | |
} | |
- (NSString *)hashm | |
{ | |
const char * cStr = [self UTF8String]; | |
unsigned char result[CC_MD5_DIGEST_LENGTH]; | |
CC_MD5(cStr, (CC_LONG)strlen(cStr), result); | |
NSString * s = [NSString stringWithFormat: | |
@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", | |
result[0], result[1], result[2], result[3], result[4], | |
result[5], result[6], result[7], | |
result[8], result[9], result[10], result[11], result[12], | |
result[13], result[14], result[15] | |
]; | |
return s; | |
} | |
- (NSDate*)dateFromString | |
{ | |
static TPLISO8601DateFormatter *dateFormatter = nil; | |
if (dateFormatter == nil) | |
dateFormatter = [[TPLISO8601DateFormatter alloc] init]; | |
NSDate *theDate = [dateFormatter dateFromString:self]; | |
return theDate; | |
} | |
- (NSDate*)dateFromFacebookBirthdayFormat | |
{ | |
//"07/31/1985" | |
//Creating a NSDateFormater is expensive, we cache it | |
static NSDateFormatter * myFormatter = nil; | |
static dispatch_once_t onceToken; | |
dispatch_once(&onceToken, ^{ | |
myFormatter = [[NSDateFormatter alloc] init]; | |
[myFormatter setDateFormat:@"MM/dd/yyyy"]; | |
}); | |
NSDate * myDate = [myFormatter dateFromString:self]; | |
return myDate; | |
} | |
+ (NSString *)formatFloat:(CGFloat)num maxDecimalPlaces:(NSUInteger)numDecPlaces | |
{ | |
if (numDecPlaces <= 0) | |
return [NSString stringWithFormat:@"%f", roundf(num)]; | |
//Round the number up to N number of decimal places | |
num = roundf(num * pow(10,numDecPlaces)) / (int)pow(10,numDecPlaces); | |
return [@(num) stringValue]; | |
} | |
#pragma mark - Substring | |
- (BOOL)matchRegexPattern:(NSString *)pattern | |
{ | |
NSPredicate *regExPredicate = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", pattern]; | |
return [regExPredicate evaluateWithObject:self]; | |
} | |
- (NSString *)replaceRegexPattern:(NSString *)pattern withString:(NSString *)newString | |
{ | |
NSError *error = nil; | |
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:pattern | |
options:NSRegularExpressionCaseInsensitive | |
error:&error]; | |
if (error != nil) { | |
DLog(@"%@", error); | |
return self; | |
} | |
NSString *modifiedString = [regex stringByReplacingMatchesInString:self | |
options:0 | |
range:NSMakeRange(0, [self length]) | |
withTemplate:newString]; | |
return modifiedString; | |
} | |
- (NSArray *)getAllRangesOfOccurrencesOfString:(NSString *)substring | |
{ | |
NSMutableArray * array = [NSMutableArray array]; | |
NSUInteger count = 0, length = [self length]; | |
NSRange range = NSMakeRange(0, length); | |
while (range.location != NSNotFound) | |
{ | |
range = [self rangeOfString:substring options:0 range:range]; | |
if (range.location != NSNotFound) | |
{ | |
[array addObject:[NSValue valueWithRange:range]]; | |
range = NSMakeRange(range.location + range.length, length - (range.location + range.length)); | |
count++; | |
} | |
} | |
return array; | |
} | |
- (NSInteger)countOccurencesOfString:(NSString *)searchString | |
{ | |
NSInteger strCount = [self length] - [[self stringByReplacingOccurrencesOfString:searchString withString:@""] length]; | |
return strCount / [searchString length]; | |
} | |
- (BOOL)safeIsEqualToNumber:(id)stringOrNumber | |
{ | |
NSString * anotherString = [NSString stringWithFormat:@"%@", stringOrNumber]; | |
return [self integerValue] == [anotherString integerValue]; | |
} | |
- (NSArray *)arrayOfCaptureComponentsMatchedByRegex:(NSString *)regex | |
{ | |
NSError *error = NULL; | |
NSRegularExpression *regExpression = [NSRegularExpression regularExpressionWithPattern:regex | |
options:NSRegularExpressionCaseInsensitive | |
error:&error]; | |
NSMutableArray *test = [NSMutableArray array]; | |
NSArray *matches = [regExpression matchesInString:self options:NSMatchingReportProgress range:NSMakeRange(0, self.length)]; | |
for(NSTextCheckingResult *match in matches) { | |
NSMutableArray *result = [NSMutableArray arrayWithCapacity:match.numberOfRanges]; | |
for(NSInteger i=0; i<match.numberOfRanges; i++) { | |
NSRange matchRange = [match rangeAtIndex:i]; | |
NSString *matchStr = nil; | |
if(matchRange.location != NSNotFound) { | |
matchStr = [self substringWithRange:matchRange]; | |
} else { | |
matchStr = @""; | |
} | |
[result addObject:matchStr]; | |
} | |
[test addObject:result]; | |
} | |
return test; | |
} | |
- (NSString *)extractContentBetweenHtmlTag:(NSString *)htmlTag | |
{ | |
NSString * regExp = [NSString stringWithFormat:@"(?i)([\\s\\.,>'-])(%@)([\\s\\.,;!\\?\\)<])", htmlTag]; | |
NSArray * regexResult = [self arrayOfCaptureComponentsMatchedByRegex:regExp]; | |
NSString * result = self; | |
if ([regexResult count] > 0) | |
{ | |
for (NSArray * match in regexResult) | |
{ | |
NSString * all = [match objectAtIndex:0]; | |
NSString * before = [match objectAtIndex:1]; | |
NSString * matched = [match objectAtIndex:2]; | |
NSString * after = [match objectAtIndex:3]; | |
result = [result stringByReplacingOccurrencesOfString:all | |
withString:[NSString stringWithFormat:@"%@<SPAN style=\"BACKGROUND-COLOR: #FF0000\">%@</SPAN>%@",before, matched, after] | |
options:NSCaseInsensitiveSearch | |
range: [result rangeOfString:all]]; | |
} | |
} | |
return result; | |
} | |
- (NSString *)stringByReplacingOpenTag:(NSString *)openTag closeTag:(NSString *)closeTag replaceOpenTag:(NSString *)replaceOpenTag replaceCloseTag:(NSString *)replaceCloseTag | |
{ | |
//Stop condition for recursive | |
NSRange fullRange = NSMakeRange(0, [self length]); | |
NSRange startRange = [self rangeOfString:openTag options:NSCaseInsensitiveSearch range:fullRange]; | |
if (startRange.location == NSNotFound) | |
return self; | |
//Stop condition for recursive | |
NSRange endSearchRange = NSMakeRange(startRange.location+startRange.length, fullRange.length-startRange.location-startRange.length); | |
NSRange endRange = [self rangeOfString:closeTag options:NSCaseInsensitiveSearch range:endSearchRange]; | |
if (endSearchRange.location == NSNotFound) | |
return self; | |
NSRange centerRange = NSMakeRange(startRange.location + startRange.length, endRange.location - startRange.location - startRange.length); | |
NSString * centerString = [self substringWithRange:centerRange]; | |
NSString * frontString = [self substringToIndex:startRange.location]; | |
NSString * endString = [self substringFromIndex:endRange.location+endRange.length]; | |
NSString * combinedString = [NSString stringWithFormat:@"%@%@%@%@%@", frontString, replaceOpenTag, centerString, replaceCloseTag, endString]; | |
//Recursive | |
return [combinedString stringByReplacingOpenTag:openTag closeTag:closeTag replaceOpenTag:replaceOpenTag replaceCloseTag:replaceCloseTag]; | |
} | |
- (NSString *)stringByReplacingOpenTag:(NSString *)openTag closeTag:(NSString *)closeTag withTag:(NSString *)replaceTag | |
{ | |
NSString * replaceOpenTag = @""; | |
NSString * replaceCloseTag = @""; | |
if ([replaceTag length] > 0) { | |
replaceOpenTag = [NSString stringWithFormat:@"<%@>", replaceTag]; | |
replaceCloseTag = [NSString stringWithFormat:@"</%@>", replaceTag]; | |
} | |
return [self stringByReplacingOpenTag:openTag closeTag:closeTag replaceOpenTag:replaceOpenTag replaceCloseTag:replaceCloseTag]; | |
} | |
- (NSString *)stringByStrippingEmptyTag:(NSString *)stripTagtag | |
{ | |
NSString * openTag = [NSString stringWithFormat:@"<%@>", tag]; | |
NSString * closeTag = [NSString stringWithFormat:@"</%@>", tag]; | |
return [self stringByReplacingOpenTag:openTag closeTag:closeTag withTag:nil]; | |
} | |
- (NSString *)stringByReplacingWithBlockQuote | |
{ | |
//<p style="margin-left:24.99999px;">some quoted text</p> | |
return [self stringByReplacingOpenTag:@"<p style=\"margin-left:24.99999px;\">" closeTag:@"</p>" withTag:@"blockquote"]; | |
} | |
- (NSString *)stringByRemovingHtmlTag:(NSString *)tag | |
{ | |
NSString *regexStr = [NSString stringWithFormat:@"<%@ ([^>]+)>([^>]+)</%@>", tag, tag]; | |
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:regexStr options:NSRegularExpressionCaseInsensitive error:NULL]; | |
return [regex stringByReplacingMatchesInString:self options:0 range:NSMakeRange(0, [self length]) withTemplate:@"$2"]; | |
} | |
- (NSString *)extractFirstImageUrl | |
{ | |
NSString *string = self; | |
NSDataDetector *linkDetector = [NSDataDetector dataDetectorWithTypes:NSTextCheckingTypeLink error:nil]; | |
NSArray *matches = [linkDetector matchesInString:string options:0 range:NSMakeRange(0, [string length])]; | |
for (NSTextCheckingResult *match in matches) | |
if ([match resultType] == NSTextCheckingTypeLink) { | |
NSString * string = [[match URL] absoluteString]; | |
NSString * lowerUrl = [string lowercaseString]; | |
if ([lowerUrl contains:@".jpg"] || [lowerUrl contains:@"jpeg"] || [lowerUrl contains:@"png"] || [lowerUrl contains:@"gif"]) | |
return string; | |
} | |
return nil; | |
} | |
- (NSString *)extractFirstUrlWithSuffix:(NSString *)suffix | |
{ | |
NSString *string = self; | |
NSDataDetector *linkDetector = [NSDataDetector dataDetectorWithTypes:NSTextCheckingTypeLink error:nil]; | |
NSArray *matches = [linkDetector matchesInString:string options:0 range:NSMakeRange(0, [string length])]; | |
for (NSTextCheckingResult *match in matches) | |
if ([match resultType] == NSTextCheckingTypeLink) | |
if ([[[match URL] absoluteString] hasSuffix:suffix]) | |
return [[match URL] absoluteString]; | |
return nil; | |
} | |
- (NSArray *)extractArrayOfUrls | |
{ | |
NSMutableArray * arrayOfURLs = [NSMutableArray array]; | |
NSString *string = self; | |
NSDataDetector *linkDetector = [NSDataDetector dataDetectorWithTypes:NSTextCheckingTypeLink error:nil]; | |
NSArray *matches = [linkDetector matchesInString:string options:0 range:NSMakeRange(0, [string length])]; | |
for (NSTextCheckingResult *match in matches) | |
if ([match resultType] == NSTextCheckingTypeLink) { | |
NSString * string = [[match URL] absoluteString]; | |
if ([string isHttpUrl]) | |
[arrayOfURLs addStringWithoutDuplication:[[match URL] absoluteString] caseSensitive:NO]; | |
} | |
//Fallback to regex | |
NSRegularExpression *expression = [NSRegularExpression regularExpressionWithPattern:@"(?i)\\b((?:[a-z][\\w-]+:(?:/{1,3}|[a-z0-9%])|www\\d{0,3}[.]|[a-z0-9.\\-]+[.][a-z]{2,4}/)(?:[^\\s()<>]+|\\(([^\\s()<>]+|(\\([^\\s()<>]+\\)))*\\))+(?:\\(([^\\s()<>]+|(\\([^\\s()<>]+\\)))*\\)|[^\\s`!()\\[\\]{};:'\".,<>?«»“”‘’]))" options:NSRegularExpressionCaseInsensitive error:NULL]; | |
NSArray *regexMatches = [expression matchesInString:self options:NSMatchingReportCompletion range:NSMakeRange(0, [self length])]; | |
for (NSTextCheckingResult *result in regexMatches) { | |
NSString *url = [self substringWithRange:result.range]; | |
[arrayOfURLs addStringWithoutDuplication:url caseSensitive:NO]; | |
} | |
return arrayOfURLs; | |
} | |
- (void)drawWithBasePoint:(CGPoint)basePoint | |
andAngle:(CGFloat)angle | |
andFont:(UIFont *)font{ | |
CGSize textSize = [self sizeWithFont:font]; | |
CGContextRef context = UIGraphicsGetCurrentContext(); | |
CGAffineTransform t = CGAffineTransformMakeTranslation(basePoint.x, basePoint.y); | |
CGAffineTransform r = CGAffineTransformMakeRotation(angle); | |
CGContextConcatCTM(context, t); | |
CGContextConcatCTM(context, r); | |
[self drawAtPoint:CGPointMake(-1 * textSize.width / 2, -1 * textSize.height / 2) | |
withFont:font]; | |
CGContextConcatCTM(context, CGAffineTransformInvert(r)); | |
CGContextConcatCTM(context, CGAffineTransformInvert(t)); | |
} | |
@end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment