Added a whole lot of stuff
This commit is contained in:
parent
1cf59542b7
commit
1c10fa4b83
@ -1,26 +1,284 @@
|
|||||||
//
|
//
|
||||||
// NSString+SA_NSStringExtensions.h
|
// NSString+SA_NSStringExtensions.h
|
||||||
|
// RPGBot
|
||||||
|
//
|
||||||
|
// Created by Said Achmiz on 12/31/15.
|
||||||
//
|
//
|
||||||
// Copyright (c) 2016 Said Achmiz.
|
|
||||||
//
|
//
|
||||||
// This software is licensed under the MIT license.
|
|
||||||
// See the file "LICENSE" for more information.
|
|
||||||
|
|
||||||
#import <Foundation/Foundation.h>
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
/********************************************************/
|
||||||
|
#pragma mark - SA_NSStringExtensions category declaration
|
||||||
|
/********************************************************/
|
||||||
|
|
||||||
@interface NSString (SA_NSStringExtensions)
|
@interface NSString (SA_NSStringExtensions)
|
||||||
|
|
||||||
- (BOOL)containsCharactersInSet:(NSCharacterSet *)characters;
|
/******************************/
|
||||||
- (BOOL)containsCharactersInString:(NSString *)characters;
|
#pragma mark - Class properties
|
||||||
|
/******************************/
|
||||||
|
|
||||||
- (NSRange)firstWhitespaceAfterRange:(NSRange)aRange;
|
@property (class) BOOL SA_NSStringExtensions_RaiseRegularExpressionCreateException;
|
||||||
- (NSRange)firstNonWhitespaceAfterRange:(NSRange)aRange;
|
|
||||||
|
|
||||||
- (NSRange)rangeAfterRange:(NSRange)aRange;
|
/*************************************/
|
||||||
- (NSRange)rangeFromEndOfRange:(NSRange)aRange;
|
#pragma mark - Working with characters
|
||||||
- (NSRange)rangeToEndFrom:(NSRange)aRange;
|
/*************************************/
|
||||||
|
|
||||||
- (NSArray <NSString *> *)componentsSplitByWhitespace;
|
/* Testing for occurrences of specified characters.
|
||||||
- (NSArray <NSString *> *)componentsSplitByWhitespaceWithMaxSplits:(NSUInteger)maxSplits;
|
*/
|
||||||
|
-(BOOL) containsCharactersInSet:(NSCharacterSet *)characters;
|
||||||
|
-(BOOL) containsCharactersInString:(NSString *)characters;
|
||||||
|
|
||||||
|
/* Removing occurrences of specified characters.
|
||||||
|
*/
|
||||||
|
-(NSString *) stringByRemovingCharactersInSet:(NSCharacterSet *)characters;
|
||||||
|
-(NSString *) stringByRemovingCharactersInString:(NSString *)characters;
|
||||||
|
|
||||||
|
/**********************/
|
||||||
|
#pragma mark - Trimming
|
||||||
|
/**********************/
|
||||||
|
|
||||||
|
/* “Smart” trimming.
|
||||||
|
*/
|
||||||
|
typedef NS_OPTIONS(NSUInteger, SA_NSStringTrimmingOptions) {
|
||||||
|
SA_NSStringTrimming_CollapseWhitespace = 1 << 0,
|
||||||
|
SA_NSStringTrimming_TrimWhitespace = 1 << 1,
|
||||||
|
SA_NSStringTrimming_AppendEllipsis = 1 << 2,
|
||||||
|
SA_NSStringTrimming_ElideEllipsisWhenEmpty = 1 << 3
|
||||||
|
};
|
||||||
|
-(NSString *) stringByTrimmingToMaxLengthInBytes:(NSUInteger)maxLengthInBytes
|
||||||
|
usingEncoding:(NSStringEncoding)encoding
|
||||||
|
withStringEnumerationOptions:(NSStringEnumerationOptions)enumerationOptions
|
||||||
|
andStringTrimmingOptions:(SA_NSStringTrimmingOptions)trimmingOptions;
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
/* Construct trimmed and cleaned string from component dictionary array.
|
||||||
|
*
|
||||||
|
* Component dictionary keys:
|
||||||
|
*
|
||||||
|
* value
|
||||||
|
* The string value of the component.
|
||||||
|
*
|
||||||
|
* appendFormat (optional)
|
||||||
|
* A format string, with exactly one format specifier, which must be
|
||||||
|
* ‘%@’; this will be replaced with the string value.
|
||||||
|
*
|
||||||
|
* priority
|
||||||
|
* Priority. Lower values mean higher priority. Must be an integer.
|
||||||
|
* (Equal-priority components are NOT guaranteed to be trimmed in
|
||||||
|
* array order.)
|
||||||
|
*
|
||||||
|
* limit (optional)
|
||||||
|
* Individual length limit for the given component. If present, the
|
||||||
|
* string value (NOT the full formatted component!) will be trimmed to
|
||||||
|
* the given length limit *before* the components are formatted and
|
||||||
|
* combined.
|
||||||
|
*
|
||||||
|
* trimBy (optional)
|
||||||
|
* Wrapped NSStringEnumerationOptions flags value. Specifies the unit
|
||||||
|
* by which to trim (character, word, etc.) Components without a value
|
||||||
|
* for this key are not trimmed, only (if need be) removed entirely.
|
||||||
|
* NOTE: This means that if you specify a value for they ‘limit’ key,
|
||||||
|
* you MUST specify a value for this key also, otherwise the limit
|
||||||
|
* will be ignored!)
|
||||||
|
******************************************************************************/
|
||||||
|
+(instancetype) trimmedStringFromComponents:(NSArray <NSDictionary *> *)components
|
||||||
|
maxLength:(NSUInteger)maxLengthInBytes
|
||||||
|
encoding:(NSStringEncoding)encoding
|
||||||
|
cleanWhitespace:(BOOL)cleanWhitespace;
|
||||||
|
|
||||||
|
/****************************************/
|
||||||
|
#pragma mark - Partitioning by whitespace
|
||||||
|
/****************************************/
|
||||||
|
|
||||||
|
/* Finding runs of whitespace.
|
||||||
|
*/
|
||||||
|
-(NSRange) firstWhitespaceAfterRange:(NSRange)aRange;
|
||||||
|
-(NSRange) firstNonWhitespaceAfterRange:(NSRange)aRange;
|
||||||
|
-(NSRange) lastWhitespaceBeforeRange:(NSRange)aRange;
|
||||||
|
-(NSRange) lastNonWhitespaceBeforeRange:(NSRange)aRange;
|
||||||
|
|
||||||
|
/********************/
|
||||||
|
#pragma mark - Ranges
|
||||||
|
/********************/
|
||||||
|
|
||||||
|
/* Range manipulation.
|
||||||
|
*/
|
||||||
|
-(NSRange) rangeAfterRange:(NSRange)aRange;
|
||||||
|
-(NSRange) rangeFromEndOfRange:(NSRange)aRange;
|
||||||
|
-(NSRange) rangeToEndFrom:(NSRange)aRange;
|
||||||
|
|
||||||
|
@property (readonly) NSRange startRange;
|
||||||
|
@property (readonly) NSRange fullRange;
|
||||||
|
@property (readonly) NSRange endRange;
|
||||||
|
|
||||||
|
/***********************/
|
||||||
|
#pragma mark - Splitting
|
||||||
|
/***********************/
|
||||||
|
|
||||||
|
/* Splitting by whitespace.
|
||||||
|
*/
|
||||||
|
-(NSArray <NSString *> *) componentsSplitByWhitespace;
|
||||||
|
-(NSArray <NSString *> *) componentsSplitByWhitespaceWithMaxSplits:(NSUInteger)maxSplits;
|
||||||
|
|
||||||
|
/* Splitting by specified delimiter string.
|
||||||
|
*/
|
||||||
|
-(NSArray <NSString *> *) componentsSeparatedByString:(NSString *)separator
|
||||||
|
maxSplits:(NSUInteger)maxSplits;
|
||||||
|
-(NSArray <NSString *> *) componentsSeparatedByString:(NSString *)separator
|
||||||
|
dropEmptyString:(BOOL)dropEmptyString;
|
||||||
|
|
||||||
|
/***************************/
|
||||||
|
#pragma mark - Byte encoding
|
||||||
|
/***************************/
|
||||||
|
|
||||||
|
/* Convenience method; alias for lengthOfBytesUsingEncoding:NSUTF8StringEncoding.
|
||||||
|
*/
|
||||||
|
@property (readonly) NSUInteger UTF8length;
|
||||||
|
|
||||||
|
/* Convenience method; alias for dataWithEncoding:NSUTF8StringEncoding.
|
||||||
|
*/
|
||||||
|
@property (nonatomic, readonly) NSData *dataAsUTF8;
|
||||||
|
|
||||||
|
/* Convenience method; alias for [[foo alloc] initWithData:data encoding:encoding].
|
||||||
|
*/
|
||||||
|
+(instancetype) stringWithData:(NSData *)data
|
||||||
|
encoding:(NSStringEncoding)encoding;
|
||||||
|
|
||||||
|
/* Convenience method; alias for stringWithData:data encoding:NSUTF8StringEncoding.
|
||||||
|
*/
|
||||||
|
+(instancetype) stringWithUTF8Data:(NSData *)data;
|
||||||
|
|
||||||
|
/*********************/
|
||||||
|
#pragma mark - Hashing
|
||||||
|
/*********************/
|
||||||
|
|
||||||
|
/* MD5 hashing.
|
||||||
|
*/
|
||||||
|
@property (nonatomic, readonly) NSString *MD5Hash;
|
||||||
|
|
||||||
|
/***********************/
|
||||||
|
#pragma mark - Sentences
|
||||||
|
/***********************/
|
||||||
|
|
||||||
|
/* Get first sentence.
|
||||||
|
*/
|
||||||
|
@property (nonatomic, readonly) NSString *firstSentence;
|
||||||
|
|
||||||
|
/*********************/
|
||||||
|
#pragma mark - Padding
|
||||||
|
/*********************/
|
||||||
|
|
||||||
|
/* Left-pad.
|
||||||
|
*/
|
||||||
|
-(NSString *) stringLeftPaddedTo:(int)width;
|
||||||
|
|
||||||
|
/****************************************************/
|
||||||
|
#pragma mark - Regular expression convenience methods
|
||||||
|
/****************************************************/
|
||||||
|
|
||||||
|
-(NSRegularExpression *) regularExpression;
|
||||||
|
-(NSRegularExpression *) regularExpressionWithOptions:(NSRegularExpressionOptions)options;
|
||||||
|
|
||||||
|
-(NSArray <NSString *> *) matchesForRegex:(NSRegularExpression *)regex;
|
||||||
|
|
||||||
|
-(NSArray <NSArray <NSString *> *> *) allMatchesForRegex:(NSRegularExpression *)regex;
|
||||||
|
|
||||||
|
-(NSArray <NSString *> *) matchesForRegexPattern:(NSString *)pattern;
|
||||||
|
-(NSArray <NSString *> *) matchesForRegexPattern:(NSString *)pattern
|
||||||
|
options:(NSRegularExpressionOptions)options;
|
||||||
|
|
||||||
|
-(NSArray <NSArray <NSString *> *> *) allMatchesForRegexPattern:(NSString *)pattern;
|
||||||
|
-(NSArray <NSArray <NSString *> *> *) allMatchesForRegexPattern:(NSString *)pattern
|
||||||
|
options:(NSRegularExpressionOptions)options;
|
||||||
|
|
||||||
|
-(NSString *) stringByReplacingFirstOccurrenceOfPattern:(NSString *)pattern
|
||||||
|
withTemplate:(NSString *)replacement;
|
||||||
|
|
||||||
|
-(NSString *) stringByReplacingFirstOccurrenceOfPattern:(NSString *)pattern
|
||||||
|
withTemplate:(NSString *)replacement
|
||||||
|
regularExpressionOptions:(NSRegularExpressionOptions)regexpOptions
|
||||||
|
matchingOptions:(NSMatchingOptions)matchingOptions;
|
||||||
|
|
||||||
|
-(NSString *) stringByReplacingAllOccurrencesOfPattern:(NSString *)pattern
|
||||||
|
withTemplate:(NSString *)replacement;
|
||||||
|
|
||||||
|
-(NSString *) stringByReplacingAllOccurrencesOfPattern:(NSString *)pattern
|
||||||
|
withTemplate:(NSString *)replacement
|
||||||
|
regularExpressionOptions:(NSRegularExpressionOptions)regexpOptions
|
||||||
|
matchingOptions:(NSMatchingOptions)matchingOptions;
|
||||||
|
|
||||||
|
-(NSString *) stringByReplacingAllOccurrencesOfPatterns:(NSArray <NSString *> *)patterns
|
||||||
|
withTemplates:(NSArray <NSString *> *)replacements;
|
||||||
|
|
||||||
|
-(NSString *) stringByReplacingAllOccurrencesOfPatterns:(NSArray <NSString *> *)patterns
|
||||||
|
withTemplates:(NSArray <NSString *> *)replacements
|
||||||
|
regularExpressionOptions:(NSRegularExpressionOptions)regexpOptions
|
||||||
|
matchingOptions:(NSMatchingOptions)matchingOptions;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
/**************************************************************************/
|
||||||
|
#pragma mark - SA_NSStringExtensions category declaration (NSMutableString)
|
||||||
|
/**************************************************************************/
|
||||||
|
|
||||||
|
@interface NSMutableString (SA_NSStringExtensions)
|
||||||
|
|
||||||
|
/*************************************/
|
||||||
|
#pragma mark - Working with characters
|
||||||
|
/*************************************/
|
||||||
|
|
||||||
|
/* Removing occurrences of specified characters.
|
||||||
|
*/
|
||||||
|
-(void) removeCharactersInSet:(NSCharacterSet *)characters;
|
||||||
|
-(void) removeCharactersInString:(NSString *)characters;
|
||||||
|
|
||||||
|
/**********************/
|
||||||
|
#pragma mark - Trimming
|
||||||
|
/**********************/
|
||||||
|
|
||||||
|
/* “Smart” trimming.
|
||||||
|
*/
|
||||||
|
-(void) trimToMaxLengthInBytes:(NSUInteger)maxLengthInBytes
|
||||||
|
usingEncoding:(NSStringEncoding)encoding
|
||||||
|
withStringEnumerationOptions:(NSStringEnumerationOptions)enumerationOptions
|
||||||
|
andStringTrimmingOptions:(SA_NSStringTrimmingOptions)trimmingOptions;
|
||||||
|
|
||||||
|
/*********************/
|
||||||
|
#pragma mark - Padding
|
||||||
|
/*********************/
|
||||||
|
|
||||||
|
/* Left-pad.
|
||||||
|
*/
|
||||||
|
-(void) leftPadTo:(int)width;
|
||||||
|
|
||||||
|
/****************************************************/
|
||||||
|
#pragma mark - Regular expression convenience methods
|
||||||
|
/****************************************************/
|
||||||
|
|
||||||
|
-(void) replaceFirstOccurrenceOfPattern:(NSString *)pattern
|
||||||
|
withTemplate:(NSString *)replacement;
|
||||||
|
|
||||||
|
-(void) replaceFirstOccurrenceOfPattern:(NSString *)pattern
|
||||||
|
withTemplate:(NSString *)replacement
|
||||||
|
regularExpressionOptions:(NSRegularExpressionOptions)regexpOptions
|
||||||
|
matchingOptions:(NSMatchingOptions)matchingOptions;
|
||||||
|
|
||||||
|
-(void) replaceAllOccurrencesOfPattern:(NSString *)pattern
|
||||||
|
withTemplate:(NSString *)replacement;
|
||||||
|
|
||||||
|
-(void) replaceAllOccurrencesOfPattern:(NSString *)pattern
|
||||||
|
withTemplate:(NSString *)replacement
|
||||||
|
regularExpressionOptions:(NSRegularExpressionOptions)regexpOptions
|
||||||
|
matchingOptions:(NSMatchingOptions)matchingOptions;
|
||||||
|
|
||||||
|
-(void) replaceAllOccurrencesOfPatterns:(NSArray <NSString *> *)patterns
|
||||||
|
withTemplates:(NSArray <NSString *> *)replacements;
|
||||||
|
|
||||||
|
-(void) replaceAllOccurrencesOfPatterns:(NSArray <NSString *> *)patterns
|
||||||
|
withTemplates:(NSArray <NSString *> *)replacements
|
||||||
|
regularExpressionOptions:(NSRegularExpressionOptions)regexpOptions
|
||||||
|
matchingOptions:(NSMatchingOptions)matchingOptions;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
|||||||
@ -1,103 +1,380 @@
|
|||||||
//
|
//
|
||||||
// NSString+SA_NSStringExtensions.m
|
// NSString+SA_NSStringExtensions.m
|
||||||
|
// RPGBot
|
||||||
|
//
|
||||||
|
// Created by Said Achmiz on 12/31/15.
|
||||||
//
|
//
|
||||||
// Copyright (c) 2016 Said Achmiz.
|
|
||||||
//
|
//
|
||||||
// This software is licensed under the MIT license.
|
|
||||||
// See the file "LICENSE" for more information.
|
|
||||||
|
|
||||||
#import "NSString+SA_NSStringExtensions.h"
|
#import "NSString+SA_NSStringExtensions.h"
|
||||||
|
|
||||||
#import "NSRange-Conventional.h"
|
#import "NSIndexSet+SA_NSIndexSetExtensions.h"
|
||||||
|
#import "NSArray+SA_NSArrayExtensions.h"
|
||||||
|
#import <CommonCrypto/CommonDigest.h>
|
||||||
|
|
||||||
|
static BOOL _SA_NSStringExtensions_RaiseRegularExpressionCreateException = YES;
|
||||||
|
|
||||||
|
/***********************************************************/
|
||||||
|
#pragma mark - SA_NSStringExtensions category implementation
|
||||||
|
/***********************************************************/
|
||||||
|
|
||||||
@implementation NSString (SA_NSStringExtensions)
|
@implementation NSString (SA_NSStringExtensions)
|
||||||
|
|
||||||
- (BOOL)containsCharactersInSet:(NSCharacterSet *)characters
|
/******************************/
|
||||||
{
|
#pragma mark - Class properties
|
||||||
|
/******************************/
|
||||||
|
|
||||||
|
+(void) setSA_NSStringExtensions_RaiseRegularExpressionCreateException:(BOOL)SA_NSStringExtensions_RaiseRegularExpressionCreateException {
|
||||||
|
_SA_NSStringExtensions_RaiseRegularExpressionCreateException = SA_NSStringExtensions_RaiseRegularExpressionCreateException;
|
||||||
|
}
|
||||||
|
|
||||||
|
+(BOOL) SA_NSStringExtensions_RaiseRegularExpressionCreateException {
|
||||||
|
return _SA_NSStringExtensions_RaiseRegularExpressionCreateException;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*************************************/
|
||||||
|
#pragma mark - Working with characters
|
||||||
|
/*************************************/
|
||||||
|
|
||||||
|
-(BOOL) containsCharactersInSet:(NSCharacterSet *)characters {
|
||||||
NSRange rangeOfCharacters = [self rangeOfCharacterFromSet:characters];
|
NSRange rangeOfCharacters = [self rangeOfCharacterFromSet:characters];
|
||||||
return rangeOfCharacters.location != NSNotFound;
|
return rangeOfCharacters.location != NSNotFound;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)containsCharactersInString:(NSString *)characters
|
-(BOOL) containsCharactersInString:(NSString *)characters {
|
||||||
{
|
|
||||||
return [self containsCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:characters]];
|
return [self containsCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:characters]];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSRange)firstWhitespaceAfterRange:(NSRange)aRange
|
-(NSString *) stringByRemovingCharactersInSet:(NSCharacterSet *)characters {
|
||||||
{
|
NSMutableString *workingCopy = [self mutableCopy];
|
||||||
NSRange restOfString = NSRangeMake(NSRangeMax(aRange), self.length - NSRangeMax(aRange));
|
|
||||||
NSRange firstWhitespace = [self rangeOfCharacterFromSet:[NSCharacterSet whitespaceCharacterSet] options:0 range:restOfString];
|
[workingCopy removeCharactersInSet:characters];
|
||||||
|
// NSRange rangeOfCharacters = [workingCopy rangeOfCharacterFromSet:characters];
|
||||||
|
// while (rangeOfCharacters.location != NSNotFound) {
|
||||||
|
// [workingCopy replaceCharactersInRange:rangeOfCharacters withString:@""];
|
||||||
|
// rangeOfCharacters = [workingCopy rangeOfCharacterFromSet:characters];
|
||||||
|
// }
|
||||||
|
|
||||||
|
return [workingCopy copy];
|
||||||
|
}
|
||||||
|
|
||||||
|
-(NSString *) stringByRemovingCharactersInString:(NSString *)characters {
|
||||||
|
return [self stringByRemovingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:characters]];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**********************/
|
||||||
|
#pragma mark - Trimming
|
||||||
|
/**********************/
|
||||||
|
|
||||||
|
-(NSString *) stringByTrimmingToMaxLengthInBytes:(NSUInteger)maxLengthInBytes
|
||||||
|
usingEncoding:(NSStringEncoding)encoding
|
||||||
|
withStringEnumerationOptions:(NSStringEnumerationOptions)enumerationOptions
|
||||||
|
andStringTrimmingOptions:(SA_NSStringTrimmingOptions)trimmingOptions {
|
||||||
|
NSMutableString *workingCopy = [self mutableCopy];
|
||||||
|
|
||||||
|
[workingCopy trimToMaxLengthInBytes:maxLengthInBytes
|
||||||
|
usingEncoding:encoding
|
||||||
|
withStringEnumerationOptions:enumerationOptions
|
||||||
|
andStringTrimmingOptions:trimmingOptions];
|
||||||
|
|
||||||
|
return [workingCopy copy];
|
||||||
|
|
||||||
|
// NSString *trimmedString = self;
|
||||||
|
//
|
||||||
|
// // Trim whitespace.
|
||||||
|
// if (trimmingOptions & SA_NSStringTrimming_TrimWhitespace)
|
||||||
|
// trimmedString = [trimmedString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
|
||||||
|
//
|
||||||
|
// // Collapse whitespace.
|
||||||
|
// if (trimmingOptions & SA_NSStringTrimming_CollapseWhitespace)
|
||||||
|
// trimmedString = [trimmedString stringByReplacingAllOccurrencesOfPattern:@"\\s+"
|
||||||
|
// withTemplate:@" "];
|
||||||
|
//
|
||||||
|
// // Length of the ellipsis suffix, in bytes.
|
||||||
|
// NSString *ellipsis = @" …";
|
||||||
|
// NSUInteger ellipsisLengthInBytes = [ellipsis lengthOfBytesUsingEncoding:encoding];
|
||||||
|
//
|
||||||
|
// // Trim (leaving space for ellipsis, if necessary).
|
||||||
|
// __block NSUInteger cutoffLength = 0;
|
||||||
|
// [trimmedString enumerateSubstringsInRange:trimmedString.fullRange
|
||||||
|
// options:(enumerationOptions|NSStringEnumerationSubstringNotRequired)
|
||||||
|
// usingBlock:^(NSString * _Nullable substring,
|
||||||
|
// NSRange substringRange,
|
||||||
|
// NSRange enclosingRange,
|
||||||
|
// BOOL * _Nonnull stop) {
|
||||||
|
// NSUInteger endOfEnclosingRange = NSMaxRange(enclosingRange);
|
||||||
|
// NSUInteger endOfEnclosingRangeInBytes = [[trimmedString substringToIndex:endOfEnclosingRange]
|
||||||
|
// lengthOfBytesUsingEncoding:encoding];
|
||||||
|
//
|
||||||
|
// // If we need to append ellipsis when trimming...
|
||||||
|
// if (trimmingOptions & SA_NSStringTrimming_AppendEllipsis) {
|
||||||
|
// if ( trimmedString.fullRange.length == endOfEnclosingRange
|
||||||
|
// && endOfEnclosingRangeInBytes <= maxLengthInBytes) {
|
||||||
|
// // Either the ellipsis is not needed, because the string is not cut off...
|
||||||
|
// cutoffLength = endOfEnclosingRange;
|
||||||
|
// } else if (endOfEnclosingRangeInBytes <= (maxLengthInBytes - ellipsisLengthInBytes)) {
|
||||||
|
// // Or there will still be room for the ellipsis after adding this piece...
|
||||||
|
// cutoffLength = endOfEnclosingRange;
|
||||||
|
// } else {
|
||||||
|
// // Or we don’t add this piece.
|
||||||
|
// *stop = YES;
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// if (endOfEnclosingRangeInBytes <= maxLengthInBytes) {
|
||||||
|
// cutoffLength = endOfEnclosingRange;
|
||||||
|
// } else {
|
||||||
|
// *stop = YES;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }];
|
||||||
|
// NSUInteger lengthBeforeTrimming = trimmedString.length;
|
||||||
|
// trimmedString = [trimmedString substringToIndex:cutoffLength];
|
||||||
|
//
|
||||||
|
// // Append ellipsis.
|
||||||
|
// if ( trimmingOptions & SA_NSStringTrimming_AppendEllipsis
|
||||||
|
// && cutoffLength < lengthBeforeTrimming
|
||||||
|
// && maxLengthInBytes >= ellipsisLengthInBytes
|
||||||
|
// && ( cutoffLength > 0
|
||||||
|
// || !(trimmingOptions & SA_NSStringTrimming_ElideEllipsisWhenEmpty))
|
||||||
|
// ) {
|
||||||
|
// trimmedString = [trimmedString stringByAppendingString:ellipsis];
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// return trimmedString;
|
||||||
|
}
|
||||||
|
|
||||||
|
+(instancetype) trimmedStringFromComponents:(NSArray <NSDictionary *> *)components
|
||||||
|
maxLength:(NSUInteger)maxLengthInBytes
|
||||||
|
encoding:(NSStringEncoding)encoding
|
||||||
|
cleanWhitespace:(BOOL)cleanWhitespace {
|
||||||
|
SA_NSStringTrimmingOptions trimmingOptions = (cleanWhitespace
|
||||||
|
? ( SA_NSStringTrimming_CollapseWhitespace
|
||||||
|
|SA_NSStringTrimming_TrimWhitespace
|
||||||
|
|SA_NSStringTrimming_AppendEllipsis)
|
||||||
|
: SA_NSStringTrimming_AppendEllipsis);
|
||||||
|
|
||||||
|
NSMutableArray <NSDictionary *> *mutableComponents = [components mutableCopy];
|
||||||
|
|
||||||
|
// Get the formatted version of the component
|
||||||
|
// (inserting the value into the format string).
|
||||||
|
NSString *(^formatComponent)(NSDictionary *) = ^NSString *(NSDictionary *component) {
|
||||||
|
return (component[@"appendFormat"] != nil
|
||||||
|
? [NSString stringWithFormat:component[@"appendFormat"], component[@"value"]]
|
||||||
|
: component[@"value"]);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get the full formatted result.
|
||||||
|
NSString *(^formatResult)(NSArray <NSDictionary *> *) = ^NSString *(NSArray <NSDictionary *> *componentsArray) {
|
||||||
|
return [componentsArray reduce:^NSString *(NSString *resultSoFar,
|
||||||
|
NSDictionary *component) {
|
||||||
|
return [resultSoFar stringByAppendingString:formatComponent(component)];
|
||||||
|
} initial:@""];
|
||||||
|
};
|
||||||
|
|
||||||
|
// Clean and trim (if need be) each component.
|
||||||
|
[mutableComponents enumerateObjectsUsingBlock:^(NSDictionary * _Nonnull component,
|
||||||
|
NSUInteger idx,
|
||||||
|
BOOL * _Nonnull stop) {
|
||||||
|
NSMutableDictionary *adjustedComponent = [component mutableCopy];
|
||||||
|
|
||||||
|
// Clean whitespace.
|
||||||
|
if (cleanWhitespace) {
|
||||||
|
adjustedComponent[@"value"] = [adjustedComponent[@"value"] stringByReplacingAllOccurrencesOfPatterns:@[ @"^\\s*(.*?)\\s*$", // Trim whitespace.
|
||||||
|
@"(\\s*\\n\\s*)+", // Replace newlines with ‘ / ’.
|
||||||
|
@"\\s+" // Collapse whitespace.
|
||||||
|
]
|
||||||
|
withTemplates:@[ @"$1",
|
||||||
|
@" / ",
|
||||||
|
@" "
|
||||||
|
]];
|
||||||
|
}
|
||||||
|
|
||||||
|
// If component length is individually limited, trim it.
|
||||||
|
if ( adjustedComponent[@"limit"] != nil
|
||||||
|
&& adjustedComponent[@"trimBy"] != nil) {
|
||||||
|
adjustedComponent[@"value"] = [adjustedComponent[@"value"] stringByTrimmingToMaxLengthInBytes:[adjustedComponent[@"limit"] unsignedIntegerValue]
|
||||||
|
usingEncoding:encoding
|
||||||
|
withStringEnumerationOptions:[adjustedComponent[@"trimBy"] unsignedIntegerValue]
|
||||||
|
andStringTrimmingOptions:trimmingOptions];
|
||||||
|
}
|
||||||
|
|
||||||
|
[mutableComponents replaceObjectAtIndex:idx
|
||||||
|
withObject:adjustedComponent];
|
||||||
|
}];
|
||||||
|
|
||||||
|
// Maybe there’s no length limit? If so, don’t trim; just format and return.
|
||||||
|
if (maxLengthInBytes == 0)
|
||||||
|
return formatResult(mutableComponents);
|
||||||
|
|
||||||
|
// Get the total (formatted) length of all the components.
|
||||||
|
NSUInteger (^getTotalLength)(NSArray <NSDictionary *> *) = ^NSUInteger(NSArray <NSDictionary *> *componentsArray) {
|
||||||
|
return ((NSNumber *)[componentsArray reduce:^NSNumber *(NSNumber *lengthSoFar,
|
||||||
|
NSDictionary *component) {
|
||||||
|
return @(lengthSoFar.unsignedIntegerValue + [formatComponent(component) lengthOfBytesUsingEncoding:encoding]);
|
||||||
|
} initial:@(0)]).unsignedIntegerValue;
|
||||||
|
};
|
||||||
|
|
||||||
|
// The “lowest” priority is actually the highest numeric priority value.
|
||||||
|
NSUInteger (^getIndexOfLowestPriorityComponent)(NSArray <NSDictionary *> *) = ^NSUInteger(NSArray <NSDictionary *> *componentsArray) {
|
||||||
|
// By default, return the index of the last component.
|
||||||
|
__block NSUInteger lowestPriorityComponentIndex = (componentsArray.count - 1);
|
||||||
|
[componentsArray enumerateObjectsUsingBlock:^(NSDictionary * _Nonnull component,
|
||||||
|
NSUInteger idx,
|
||||||
|
BOOL * _Nonnull stop) {
|
||||||
|
if ([component[@"priority"] unsignedIntegerValue] > [componentsArray[lowestPriorityComponentIndex][@"priority"] unsignedIntegerValue])
|
||||||
|
lowestPriorityComponentIndex = idx;
|
||||||
|
}];
|
||||||
|
return lowestPriorityComponentIndex;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Keep trimming until we’re below the max length.
|
||||||
|
NSInteger excessLength = (NSInteger)(getTotalLength(mutableComponents) - maxLengthInBytes);
|
||||||
|
while (excessLength > 0) {
|
||||||
|
NSUInteger lowestPriorityComponentIndex = getIndexOfLowestPriorityComponent(mutableComponents);
|
||||||
|
NSDictionary *lowestPriorityComponent = mutableComponents[lowestPriorityComponentIndex];
|
||||||
|
NSUInteger lowestPriorityComponentValueLengthInBytes = [lowestPriorityComponent[@"value"] lengthOfBytesUsingEncoding:encoding];
|
||||||
|
|
||||||
|
if ( lowestPriorityComponent[@"trimBy"] == nil
|
||||||
|
|| lowestPriorityComponentValueLengthInBytes <= (NSUInteger)excessLength) {
|
||||||
|
[mutableComponents removeObjectAtIndex:lowestPriorityComponentIndex];
|
||||||
|
} else {
|
||||||
|
NSMutableDictionary *adjustedComponent = [lowestPriorityComponent mutableCopy];
|
||||||
|
adjustedComponent[@"value"] = [lowestPriorityComponent[@"value"] stringByTrimmingToMaxLengthInBytes:(lowestPriorityComponentValueLengthInBytes - (NSUInteger)excessLength)
|
||||||
|
usingEncoding:encoding
|
||||||
|
withStringEnumerationOptions:[lowestPriorityComponent[@"trimBy"] unsignedIntegerValue]
|
||||||
|
andStringTrimmingOptions:trimmingOptions];
|
||||||
|
// Check to make sure we haven’t trimmed all the way to nothing!
|
||||||
|
// (Actually this can’t happen because the SA_NSStringTrimming_ElideEllipsisWhenEmpty
|
||||||
|
// flag is not set on the trim call above...)
|
||||||
|
if ([adjustedComponent[@"value"] lengthOfBytesUsingEncoding:encoding] == 0) {
|
||||||
|
// ... if we have, just remove the component.
|
||||||
|
[mutableComponents removeObjectAtIndex:lowestPriorityComponentIndex];
|
||||||
|
} else {
|
||||||
|
// ... otherwise, update it.
|
||||||
|
[mutableComponents replaceObjectAtIndex:lowestPriorityComponentIndex
|
||||||
|
withObject:adjustedComponent];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
excessLength = (NSInteger)(getTotalLength(mutableComponents) - maxLengthInBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trimming is done; return.
|
||||||
|
return formatResult(mutableComponents);
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************/
|
||||||
|
#pragma mark - Partitioning by whitespace
|
||||||
|
/****************************************/
|
||||||
|
|
||||||
|
-(NSRange) firstWhitespaceAfterRange:(NSRange)aRange {
|
||||||
|
NSRange restOfString = NSMakeRange(NSMaxRange(aRange), self.length - NSMaxRange(aRange));
|
||||||
|
NSRange firstWhitespace = [self rangeOfCharacterFromSet:[NSCharacterSet whitespaceCharacterSet]
|
||||||
|
options:(NSStringCompareOptions) 0
|
||||||
|
range:restOfString];
|
||||||
|
|
||||||
return firstWhitespace;
|
return firstWhitespace;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSRange)firstNonWhitespaceAfterRange:(NSRange)aRange
|
-(NSRange) firstNonWhitespaceAfterRange:(NSRange)aRange {
|
||||||
{
|
NSRange restOfString = NSMakeRange(NSMaxRange(aRange), self.length - NSMaxRange(aRange));
|
||||||
NSRange restOfString = NSRangeMake(NSRangeMax(aRange), self.length - NSRangeMax(aRange));
|
NSRange firstNonWhitespace = [self rangeOfCharacterFromSet:[[NSCharacterSet whitespaceCharacterSet] invertedSet]
|
||||||
NSRange firstNonWhitespace = [self rangeOfCharacterFromSet:[[NSCharacterSet whitespaceCharacterSet] invertedSet] options:0 range:restOfString];
|
options:(NSStringCompareOptions) 0
|
||||||
|
range:restOfString];
|
||||||
|
|
||||||
return firstNonWhitespace;
|
return firstNonWhitespace;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSRange)rangeAfterRange:(NSRange)aRange
|
-(NSRange) lastWhitespaceBeforeRange:(NSRange)aRange {
|
||||||
{
|
NSRange stringUntilRange = NSMakeRange(0, aRange.location);
|
||||||
return NSRangeMake(NSRangeMax(aRange), self.length - NSRangeMax(aRange));
|
NSRange lastWhitespace = [self rangeOfCharacterFromSet:[NSCharacterSet whitespaceCharacterSet]
|
||||||
|
options:NSBackwardsSearch
|
||||||
|
range:stringUntilRange];
|
||||||
|
return lastWhitespace;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSRange)rangeFromEndOfRange:(NSRange)aRange
|
-(NSRange) lastNonWhitespaceBeforeRange:(NSRange)aRange {
|
||||||
{
|
NSRange stringUntilRange = NSMakeRange(0, aRange.location);
|
||||||
return NSRangeMake(NSRangeMax(aRange) - 1, self.length - NSRangeMax(aRange) + 1);
|
NSRange lastNonWhitespace = [self rangeOfCharacterFromSet:[[NSCharacterSet whitespaceCharacterSet] invertedSet]
|
||||||
|
options:NSBackwardsSearch
|
||||||
|
range:stringUntilRange];
|
||||||
|
return lastNonWhitespace;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSRange)rangeToEndFrom:(NSRange)aRange
|
/********************/
|
||||||
{
|
#pragma mark - Ranges
|
||||||
return NSRangeMake(aRange.location, self.length - aRange.location);
|
/********************/
|
||||||
|
|
||||||
|
-(NSRange) rangeAfterRange:(NSRange)aRange {
|
||||||
|
return NSMakeRange(NSMaxRange(aRange), self.length - NSMaxRange(aRange));
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSArray <NSString *> *)componentsSplitByWhitespace
|
-(NSRange) rangeFromEndOfRange:(NSRange)aRange {
|
||||||
{
|
return NSMakeRange(NSMaxRange(aRange) - 1, self.length - NSMaxRange(aRange) + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
-(NSRange) rangeToEndFrom:(NSRange)aRange {
|
||||||
|
return NSMakeRange(aRange.location, self.length - aRange.location);
|
||||||
|
}
|
||||||
|
|
||||||
|
-(NSRange) startRange {
|
||||||
|
return NSMakeRange(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
-(NSRange) fullRange {
|
||||||
|
return NSMakeRange(0, self.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
-(NSRange) endRange {
|
||||||
|
return NSMakeRange(self.length, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/***********************/
|
||||||
|
#pragma mark - Splitting
|
||||||
|
/***********************/
|
||||||
|
|
||||||
|
-(NSArray <NSString *> *) componentsSplitByWhitespace {
|
||||||
return [self componentsSplitByWhitespaceWithMaxSplits:NSUIntegerMax];
|
return [self componentsSplitByWhitespaceWithMaxSplits:NSUIntegerMax];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSArray <NSString *> *)componentsSplitByWhitespaceWithMaxSplits:(NSUInteger)maxSplits
|
-(NSArray <NSString *> *) componentsSplitByWhitespaceWithMaxSplits:(NSUInteger)maxSplits {
|
||||||
{
|
if (maxSplits == 0) {
|
||||||
if(maxSplits == 0)
|
return @[ self ];
|
||||||
{
|
|
||||||
return [NSMutableArray arrayWithObject:self];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
__block NSMutableArray <NSString *> *components = [NSMutableArray array];
|
NSMutableArray <NSString *> *components = [NSMutableArray array];
|
||||||
|
|
||||||
__block NSUInteger tokenStart;
|
__block NSUInteger tokenStart;
|
||||||
__block NSUInteger tokenEnd;
|
__block NSUInteger tokenEnd;
|
||||||
__block BOOL currentlyInToken = NO;
|
__block BOOL currentlyInToken = NO;
|
||||||
__block NSRange tokenRange = NSRangeMake(NSNotFound, 0);
|
__block NSRange tokenRange = NSMakeRange(NSNotFound, 0);
|
||||||
|
|
||||||
__block NSUInteger splits = 0;
|
__block NSUInteger splits = 0;
|
||||||
|
|
||||||
[self enumerateSubstringsInRange:NSRangeMake(0, self.length)
|
[self enumerateSubstringsInRange:self.fullRange
|
||||||
options:NSStringEnumerationByComposedCharacterSequences
|
options:NSStringEnumerationByComposedCharacterSequences
|
||||||
usingBlock:^(NSString *character, NSRange characterRange, NSRange enclosingRange, BOOL *stop)
|
usingBlock:^(NSString *character,
|
||||||
{
|
NSRange characterRange,
|
||||||
if(currentlyInToken == NO && [character containsCharactersInSet:[[NSCharacterSet whitespaceCharacterSet] invertedSet]])
|
NSRange enclosingRange,
|
||||||
{
|
BOOL *stop) {
|
||||||
|
if ( currentlyInToken == NO
|
||||||
|
&& [character containsCharactersInSet:[[NSCharacterSet whitespaceCharacterSet] invertedSet]]
|
||||||
|
) {
|
||||||
currentlyInToken = YES;
|
currentlyInToken = YES;
|
||||||
tokenStart = characterRange.location;
|
tokenStart = characterRange.location;
|
||||||
}
|
} else if ( currentlyInToken == YES
|
||||||
else if(currentlyInToken == YES && [character containsCharactersInSet:[NSCharacterSet whitespaceCharacterSet]])
|
&& [character containsCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]
|
||||||
{
|
) {
|
||||||
currentlyInToken = NO;
|
currentlyInToken = NO;
|
||||||
tokenEnd = characterRange.location;
|
tokenEnd = characterRange.location;
|
||||||
|
|
||||||
tokenRange = NSRangeMake(tokenStart, tokenEnd - tokenStart);
|
tokenRange = NSMakeRange(tokenStart, tokenEnd - tokenStart);
|
||||||
[components addObject:[self substringWithRange:tokenRange]];
|
[components addObject:[self substringWithRange:tokenRange]];
|
||||||
splits++;
|
splits++;
|
||||||
if(splits == maxSplits)
|
if (splits == maxSplits) {
|
||||||
{
|
|
||||||
*stop = YES;
|
*stop = YES;
|
||||||
NSRange lastTokenRange = [self rangeToEndFrom:[self firstNonWhitespaceAfterRange:tokenRange]];
|
NSRange lastTokenRange = [self rangeToEndFrom:[self firstNonWhitespaceAfterRange:tokenRange]];
|
||||||
if(lastTokenRange.location != NSNotFound)
|
if (lastTokenRange.location != NSNotFound) {
|
||||||
{
|
|
||||||
[components addObject:[self substringWithRange:lastTokenRange]];
|
[components addObject:[self substringWithRange:lastTokenRange]];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -105,15 +382,456 @@
|
|||||||
}];
|
}];
|
||||||
|
|
||||||
// If we were in a token when we got to the end, add that last token.
|
// If we were in a token when we got to the end, add that last token.
|
||||||
if(splits < maxSplits && currentlyInToken == YES)
|
if ( splits < maxSplits
|
||||||
{
|
&& currentlyInToken == YES) {
|
||||||
tokenEnd = self.length;
|
tokenEnd = self.length;
|
||||||
|
|
||||||
tokenRange = NSRangeMake(tokenStart, tokenEnd - tokenStart);
|
tokenRange = NSMakeRange(tokenStart,
|
||||||
|
tokenEnd - tokenStart);
|
||||||
[components addObject:[self substringWithRange:tokenRange]];
|
[components addObject:[self substringWithRange:tokenRange]];
|
||||||
}
|
}
|
||||||
|
|
||||||
return components;
|
return components;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
-(NSArray <NSString *> *) componentsSeparatedByString:(NSString *)separator
|
||||||
|
maxSplits:(NSUInteger)maxSplits {
|
||||||
|
NSArray <NSString *> *components = [self componentsSeparatedByString:separator];
|
||||||
|
if (maxSplits >= (components.count - 1))
|
||||||
|
return components;
|
||||||
|
|
||||||
|
return [[components subarrayWithRange:NSMakeRange(0, maxSplits)]
|
||||||
|
arrayByAddingObject:[[components
|
||||||
|
subarrayWithRange:NSMakeRange(maxSplits,
|
||||||
|
components.count - maxSplits)]
|
||||||
|
componentsJoinedByString:separator]];
|
||||||
|
}
|
||||||
|
|
||||||
|
-(NSArray <NSString *> *) componentsSeparatedByString:(NSString *)separator
|
||||||
|
dropEmptyString:(BOOL)dropEmptyString {
|
||||||
|
NSMutableArray* components = [[self componentsSeparatedByString:separator] mutableCopy];
|
||||||
|
if (dropEmptyString == YES)
|
||||||
|
[components removeObject:@""];
|
||||||
|
return [components copy];
|
||||||
|
}
|
||||||
|
|
||||||
|
/***************************/
|
||||||
|
#pragma mark - Byte encoding
|
||||||
|
/***************************/
|
||||||
|
|
||||||
|
-(NSUInteger) UTF8length {
|
||||||
|
return [self lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
|
||||||
|
}
|
||||||
|
|
||||||
|
-(NSData *) dataAsUTF8 {
|
||||||
|
return [self dataUsingEncoding:NSUTF8StringEncoding];
|
||||||
|
}
|
||||||
|
|
||||||
|
+(instancetype) stringWithData:(NSData *)data
|
||||||
|
encoding:(NSStringEncoding)encoding {
|
||||||
|
return [[self alloc] initWithData:data
|
||||||
|
encoding:encoding];
|
||||||
|
}
|
||||||
|
|
||||||
|
+(instancetype) stringWithUTF8Data:(NSData *)data {
|
||||||
|
return [self stringWithData:data
|
||||||
|
encoding:NSUTF8StringEncoding];
|
||||||
|
}
|
||||||
|
|
||||||
|
/*********************/
|
||||||
|
#pragma mark - Hashing
|
||||||
|
/*********************/
|
||||||
|
|
||||||
|
-(NSString *) MD5Hash {
|
||||||
|
const char *cStr = [self UTF8String];
|
||||||
|
unsigned char result[CC_MD5_DIGEST_LENGTH];
|
||||||
|
CC_MD5(cStr, (CC_LONG) strlen(cStr), result);
|
||||||
|
|
||||||
|
return [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]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/***********************/
|
||||||
|
#pragma mark - Sentences
|
||||||
|
/***********************/
|
||||||
|
|
||||||
|
-(NSString *) firstSentence {
|
||||||
|
__block NSString *firstSentence;
|
||||||
|
[self enumerateSubstringsInRange:self.fullRange
|
||||||
|
options:NSStringEnumerationBySentences
|
||||||
|
usingBlock:^(NSString * _Nullable substring,
|
||||||
|
NSRange substringRange,
|
||||||
|
NSRange enclosingRange,
|
||||||
|
BOOL * _Nonnull stop) {
|
||||||
|
firstSentence = substring;
|
||||||
|
*stop = YES;
|
||||||
|
}];
|
||||||
|
return firstSentence;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*********************/
|
||||||
|
#pragma mark - Padding
|
||||||
|
/*********************/
|
||||||
|
|
||||||
|
-(NSString *) stringLeftPaddedTo:(int)width {
|
||||||
|
return [NSString stringWithFormat:@"%*s", width, [self stringByAppendingString:@"\0"].dataAsUTF8.bytes];
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************/
|
||||||
|
#pragma mark - Regular expression convenience methods
|
||||||
|
/****************************************************/
|
||||||
|
|
||||||
|
/*********************************************/
|
||||||
|
/* Construct regular expressions from strings.
|
||||||
|
*********************************************/
|
||||||
|
|
||||||
|
-(NSRegularExpression *) regularExpression {
|
||||||
|
return [self regularExpressionWithOptions:(NSRegularExpressionOptions) 0];
|
||||||
|
}
|
||||||
|
|
||||||
|
-(NSRegularExpression *) regularExpressionWithOptions:(NSRegularExpressionOptions)options {
|
||||||
|
NSError *error;
|
||||||
|
NSRegularExpression *regexp = [NSRegularExpression regularExpressionWithPattern:self
|
||||||
|
options:options
|
||||||
|
error:&error];
|
||||||
|
if (error) {
|
||||||
|
if (NSString.SA_NSStringExtensions_RaiseRegularExpressionCreateException == YES)
|
||||||
|
[NSException raise:@"SA_NSStringExtensions_RegularExpressionCreateException"
|
||||||
|
format:@"%@", error.localizedDescription];
|
||||||
|
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
return regexp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**********************************************/
|
||||||
|
/* Get matches for a regular expression object.
|
||||||
|
**********************************************/
|
||||||
|
|
||||||
|
-(NSArray <NSString *> *) matchesForRegex:(NSRegularExpression *)regex {
|
||||||
|
NSMutableArray <NSString *> *matches = [NSMutableArray arrayWithCapacity:regex.numberOfCaptureGroups];
|
||||||
|
[regex enumerateMatchesInString:self
|
||||||
|
options:(NSMatchingOptions) 0
|
||||||
|
range:self.fullRange
|
||||||
|
usingBlock:^(NSTextCheckingResult * _Nullable result,
|
||||||
|
NSMatchingFlags flags,
|
||||||
|
BOOL *stop) {
|
||||||
|
[NSIndexSet from:0
|
||||||
|
for:result.numberOfRanges
|
||||||
|
do:^(NSUInteger idx) {
|
||||||
|
NSString *resultString = ([result rangeAtIndex:idx].location == NSNotFound
|
||||||
|
? @""
|
||||||
|
: [self substringWithRange:[result rangeAtIndex:idx]]);
|
||||||
|
[matches addObject:resultString];
|
||||||
|
}];
|
||||||
|
|
||||||
|
*stop = YES;
|
||||||
|
}];
|
||||||
|
return matches;
|
||||||
|
}
|
||||||
|
|
||||||
|
-(NSArray <NSArray <NSString *> *> *) allMatchesForRegex:(NSRegularExpression *)regex {
|
||||||
|
NSMutableArray <NSMutableArray <NSString *> *> *matches = [NSMutableArray arrayWithCapacity:regex.numberOfCaptureGroups];
|
||||||
|
[NSIndexSet from:0
|
||||||
|
for:regex.numberOfCaptureGroups
|
||||||
|
do:^(NSUInteger idx) {
|
||||||
|
[matches addObject:[NSMutableArray array]];
|
||||||
|
}];
|
||||||
|
[regex enumerateMatchesInString:self
|
||||||
|
options:(NSMatchingOptions) 0
|
||||||
|
range:self.fullRange
|
||||||
|
usingBlock:^(NSTextCheckingResult * _Nullable result,
|
||||||
|
NSMatchingFlags flags,
|
||||||
|
BOOL *stop) {
|
||||||
|
[NSIndexSet from:0
|
||||||
|
for:result.numberOfRanges
|
||||||
|
do:^(NSUInteger idx) {
|
||||||
|
NSString *resultString = ([result rangeAtIndex:idx].location == NSNotFound
|
||||||
|
? @""
|
||||||
|
: [self substringWithRange:[result rangeAtIndex:idx]]);
|
||||||
|
[matches[idx] addObject:resultString];
|
||||||
|
}];
|
||||||
|
}];
|
||||||
|
return matches;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*************************************************************************/
|
||||||
|
/* Get matches for a string representing a regular expression (a pattern).
|
||||||
|
*************************************************************************/
|
||||||
|
|
||||||
|
-(NSArray <NSString *> *) matchesForRegexPattern:(NSString *)pattern {
|
||||||
|
return [self matchesForRegex:[pattern regularExpression]];
|
||||||
|
}
|
||||||
|
|
||||||
|
-(NSArray <NSString *> *) matchesForRegexPattern:(NSString *)pattern
|
||||||
|
options:(NSRegularExpressionOptions)options {
|
||||||
|
return [self matchesForRegex:[pattern regularExpressionWithOptions:options]];
|
||||||
|
}
|
||||||
|
|
||||||
|
-(NSArray <NSArray <NSString *> *> *) allMatchesForRegexPattern:(NSString *)pattern {
|
||||||
|
return [self allMatchesForRegex:[pattern regularExpression]];
|
||||||
|
}
|
||||||
|
|
||||||
|
-(NSArray <NSArray <NSString *> *> *) allMatchesForRegexPattern:(NSString *)pattern
|
||||||
|
options:(NSRegularExpressionOptions)options {
|
||||||
|
return [self allMatchesForRegex:[pattern regularExpressionWithOptions:options]];
|
||||||
|
}
|
||||||
|
|
||||||
|
/*******************************************************************************/
|
||||||
|
/* Use a pattern (a string representing a regular expression) to do replacement.
|
||||||
|
*******************************************************************************/
|
||||||
|
|
||||||
|
-(NSString *) stringByReplacingFirstOccurrenceOfPattern:(NSString *)pattern
|
||||||
|
withTemplate:(NSString *)template {
|
||||||
|
return [self stringByReplacingFirstOccurrenceOfPattern:pattern
|
||||||
|
withTemplate:template
|
||||||
|
regularExpressionOptions:(NSRegularExpressionOptions) 0
|
||||||
|
matchingOptions:(NSMatchingOptions) 0];
|
||||||
|
}
|
||||||
|
|
||||||
|
-(NSString *) stringByReplacingFirstOccurrenceOfPattern:(NSString *)pattern
|
||||||
|
withTemplate:(NSString *)template
|
||||||
|
regularExpressionOptions:(NSRegularExpressionOptions)regexpOptions
|
||||||
|
matchingOptions:(NSMatchingOptions)matchingOptions {
|
||||||
|
NSRegularExpression *regexp = [pattern regularExpressionWithOptions:regexpOptions];
|
||||||
|
NSTextCheckingResult *match = [regexp firstMatchInString:self
|
||||||
|
options:matchingOptions
|
||||||
|
range:self.fullRange];
|
||||||
|
if ( match
|
||||||
|
&& match.range.location != NSNotFound) {
|
||||||
|
return [self stringByReplacingCharactersInRange:match.range
|
||||||
|
withString:[regexp replacementStringForResult:match
|
||||||
|
inString:self
|
||||||
|
offset:0
|
||||||
|
template:template]];
|
||||||
|
} else {
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
-(NSString *) stringByReplacingAllOccurrencesOfPattern:(NSString *)pattern
|
||||||
|
withTemplate:(NSString *)template {
|
||||||
|
return [self stringByReplacingAllOccurrencesOfPattern:pattern
|
||||||
|
withTemplate:template
|
||||||
|
regularExpressionOptions:(NSRegularExpressionOptions) 0
|
||||||
|
matchingOptions:(NSMatchingOptions) 0];
|
||||||
|
}
|
||||||
|
|
||||||
|
-(NSString *) stringByReplacingAllOccurrencesOfPattern:(NSString *)pattern
|
||||||
|
withTemplate:(NSString *)template
|
||||||
|
regularExpressionOptions:(NSRegularExpressionOptions)regexpOptions
|
||||||
|
matchingOptions:(NSMatchingOptions)matchingOptions {
|
||||||
|
return [[pattern regularExpressionWithOptions:regexpOptions] stringByReplacingMatchesInString:self
|
||||||
|
options:matchingOptions
|
||||||
|
range:self.fullRange
|
||||||
|
withTemplate:template];
|
||||||
|
}
|
||||||
|
|
||||||
|
-(NSString *) stringByReplacingAllOccurrencesOfPatterns:(NSArray <NSString *> *)patterns
|
||||||
|
withTemplates:(NSArray <NSString *> *)replacements {
|
||||||
|
return [self stringByReplacingAllOccurrencesOfPatterns:patterns
|
||||||
|
withTemplates:replacements
|
||||||
|
regularExpressionOptions:(NSRegularExpressionOptions) 0
|
||||||
|
matchingOptions:(NSMatchingOptions) 0];
|
||||||
|
}
|
||||||
|
|
||||||
|
-(NSString *) stringByReplacingAllOccurrencesOfPatterns:(NSArray <NSString *> *)patterns
|
||||||
|
withTemplates:(NSArray <NSString *> *)replacements
|
||||||
|
regularExpressionOptions:(NSRegularExpressionOptions)regexpOptions
|
||||||
|
matchingOptions:(NSMatchingOptions)matchingOptions {
|
||||||
|
NSMutableString *workingCopy = [self mutableCopy];
|
||||||
|
|
||||||
|
[workingCopy replaceAllOccurrencesOfPatterns:patterns
|
||||||
|
withTemplates:replacements
|
||||||
|
regularExpressionOptions:regexpOptions
|
||||||
|
matchingOptions:matchingOptions];
|
||||||
|
|
||||||
|
return [workingCopy copy];
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
/*****************************************************************************/
|
||||||
|
#pragma mark - SA_NSStringExtensions category implementation (NSMutableString)
|
||||||
|
/*****************************************************************************/
|
||||||
|
|
||||||
|
@implementation NSMutableString (SA_NSStringExtensions)
|
||||||
|
|
||||||
|
/*************************************/
|
||||||
|
#pragma mark - Working with characters
|
||||||
|
/*************************************/
|
||||||
|
|
||||||
|
-(void) removeCharactersInSet:(NSCharacterSet *)characters {
|
||||||
|
NSRange rangeOfCharacters = [self rangeOfCharacterFromSet:characters];
|
||||||
|
while (rangeOfCharacters.location != NSNotFound) {
|
||||||
|
[self replaceCharactersInRange:rangeOfCharacters withString:@""];
|
||||||
|
rangeOfCharacters = [self rangeOfCharacterFromSet:characters];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
-(void) removeCharactersInString:(NSString *)characters {
|
||||||
|
[self removeCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:characters]];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**********************/
|
||||||
|
#pragma mark - Trimming
|
||||||
|
/**********************/
|
||||||
|
|
||||||
|
-(void) trimToMaxLengthInBytes:(NSUInteger)maxLengthInBytes
|
||||||
|
usingEncoding:(NSStringEncoding)encoding
|
||||||
|
withStringEnumerationOptions:(NSStringEnumerationOptions)enumerationOptions
|
||||||
|
andStringTrimmingOptions:(SA_NSStringTrimmingOptions)trimmingOptions {
|
||||||
|
// Trim whitespace.
|
||||||
|
if (trimmingOptions & SA_NSStringTrimming_TrimWhitespace)
|
||||||
|
[self replaceAllOccurrencesOfPattern:@"^\\s*(.*?)\\s*$"
|
||||||
|
withTemplate:@"$1"];
|
||||||
|
|
||||||
|
// Collapse whitespace.
|
||||||
|
if (trimmingOptions & SA_NSStringTrimming_CollapseWhitespace)
|
||||||
|
[self replaceAllOccurrencesOfPattern:@"\\s+"
|
||||||
|
withTemplate:@" "];
|
||||||
|
|
||||||
|
// Length of the ellipsis suffix, in bytes.
|
||||||
|
NSString *ellipsis = @" …";
|
||||||
|
NSUInteger ellipsisLengthInBytes = [ellipsis lengthOfBytesUsingEncoding:encoding];
|
||||||
|
|
||||||
|
// Trim (leaving space for ellipsis, if necessary).
|
||||||
|
__block NSUInteger cutoffLength = 0;
|
||||||
|
[self enumerateSubstringsInRange:self.fullRange
|
||||||
|
options:(enumerationOptions|NSStringEnumerationSubstringNotRequired)
|
||||||
|
usingBlock:^(NSString * _Nullable substring,
|
||||||
|
NSRange substringRange,
|
||||||
|
NSRange enclosingRange,
|
||||||
|
BOOL * _Nonnull stop) {
|
||||||
|
NSUInteger endOfEnclosingRange = NSMaxRange(enclosingRange);
|
||||||
|
NSUInteger endOfEnclosingRangeInBytes = [[self substringToIndex:endOfEnclosingRange] lengthOfBytesUsingEncoding:encoding];
|
||||||
|
|
||||||
|
// If we need to append ellipsis when trimming...
|
||||||
|
if (trimmingOptions & SA_NSStringTrimming_AppendEllipsis) {
|
||||||
|
if ( self.fullRange.length == endOfEnclosingRange
|
||||||
|
&& endOfEnclosingRangeInBytes <= maxLengthInBytes) {
|
||||||
|
// Either the ellipsis is not needed, because the string is not cut off...
|
||||||
|
cutoffLength = endOfEnclosingRange;
|
||||||
|
} else if (endOfEnclosingRangeInBytes <= (maxLengthInBytes - ellipsisLengthInBytes)) {
|
||||||
|
// Or there will still be room for the ellipsis after adding this piece...
|
||||||
|
cutoffLength = endOfEnclosingRange;
|
||||||
|
} else {
|
||||||
|
// Or we don’t add this piece.
|
||||||
|
*stop = YES;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (endOfEnclosingRangeInBytes <= maxLengthInBytes) {
|
||||||
|
cutoffLength = endOfEnclosingRange;
|
||||||
|
} else {
|
||||||
|
*stop = YES;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
NSUInteger lengthBeforeTrimming = self.length;
|
||||||
|
[self deleteCharactersInRange:NSMakeRange(cutoffLength, self.length - cutoffLength)];
|
||||||
|
|
||||||
|
// Trim whitespace again.
|
||||||
|
if (trimmingOptions & SA_NSStringTrimming_TrimWhitespace)
|
||||||
|
[self replaceAllOccurrencesOfPattern:@"^\\s*(.*?)\\s*$"
|
||||||
|
withTemplate:@"$1"];
|
||||||
|
|
||||||
|
// Append ellipsis.
|
||||||
|
if ( trimmingOptions & SA_NSStringTrimming_AppendEllipsis
|
||||||
|
&& cutoffLength < lengthBeforeTrimming
|
||||||
|
&& maxLengthInBytes >= ellipsisLengthInBytes
|
||||||
|
&& ( cutoffLength > 0
|
||||||
|
|| !(trimmingOptions & SA_NSStringTrimming_ElideEllipsisWhenEmpty))
|
||||||
|
) {
|
||||||
|
[self appendString:ellipsis];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*********************/
|
||||||
|
#pragma mark - Padding
|
||||||
|
/*********************/
|
||||||
|
|
||||||
|
-(void) leftPadTo:(int)width {
|
||||||
|
[self setString:[NSString stringWithFormat:@"%*s", width, [self stringByAppendingString:@"\0"].dataAsUTF8.bytes]];
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************/
|
||||||
|
#pragma mark - Regular expression convenience methods
|
||||||
|
/****************************************************/
|
||||||
|
|
||||||
|
-(void) replaceFirstOccurrenceOfPattern:(NSString *)pattern
|
||||||
|
withTemplate:(NSString *)template {
|
||||||
|
[self replaceFirstOccurrenceOfPattern:pattern
|
||||||
|
withTemplate:template
|
||||||
|
regularExpressionOptions:(NSRegularExpressionOptions) 0
|
||||||
|
matchingOptions:(NSMatchingOptions) 0];
|
||||||
|
}
|
||||||
|
|
||||||
|
-(void) replaceFirstOccurrenceOfPattern:(NSString *)pattern
|
||||||
|
withTemplate:(NSString *)template
|
||||||
|
regularExpressionOptions:(NSRegularExpressionOptions)regexpOptions
|
||||||
|
matchingOptions:(NSMatchingOptions)matchingOptions {
|
||||||
|
NSRegularExpression *regexp = [pattern regularExpressionWithOptions:regexpOptions];
|
||||||
|
NSTextCheckingResult *match = [regexp firstMatchInString:self
|
||||||
|
options:matchingOptions
|
||||||
|
range:self.fullRange];
|
||||||
|
if ( match
|
||||||
|
&& match.range.location != NSNotFound) {
|
||||||
|
NSString *replacementString = [regexp replacementStringForResult:match
|
||||||
|
inString:self
|
||||||
|
offset:0
|
||||||
|
template:template];
|
||||||
|
[self replaceCharactersInRange:match.range
|
||||||
|
withString:replacementString];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
-(void) replaceAllOccurrencesOfPattern:(NSString *)pattern
|
||||||
|
withTemplate:(NSString *)template {
|
||||||
|
[self replaceAllOccurrencesOfPattern:pattern
|
||||||
|
withTemplate:template
|
||||||
|
regularExpressionOptions:(NSRegularExpressionOptions) 0
|
||||||
|
matchingOptions:(NSMatchingOptions) 0];
|
||||||
|
}
|
||||||
|
|
||||||
|
-(void) replaceAllOccurrencesOfPattern:(NSString *)pattern
|
||||||
|
withTemplate:(NSString *)template
|
||||||
|
regularExpressionOptions:(NSRegularExpressionOptions)regexpOptions
|
||||||
|
matchingOptions:(NSMatchingOptions)matchingOptions {
|
||||||
|
[[pattern regularExpressionWithOptions:regexpOptions] replaceMatchesInString:self
|
||||||
|
options:matchingOptions
|
||||||
|
range:self.fullRange
|
||||||
|
withTemplate:template];
|
||||||
|
}
|
||||||
|
|
||||||
|
-(void) replaceAllOccurrencesOfPatterns:(NSArray <NSString *> *)patterns
|
||||||
|
withTemplates:(NSArray <NSString *> *)replacements {
|
||||||
|
[self replaceAllOccurrencesOfPatterns:patterns
|
||||||
|
withTemplates:replacements
|
||||||
|
regularExpressionOptions:(NSRegularExpressionOptions) 0
|
||||||
|
matchingOptions:(NSMatchingOptions) 0];
|
||||||
|
}
|
||||||
|
|
||||||
|
-(void) replaceAllOccurrencesOfPatterns:(NSArray <NSString *> *)patterns
|
||||||
|
withTemplates:(NSArray <NSString *> *)replacements
|
||||||
|
regularExpressionOptions:(NSRegularExpressionOptions)regexpOptions
|
||||||
|
matchingOptions:(NSMatchingOptions)matchingOptions {
|
||||||
|
[patterns enumerateObjectsUsingBlock:^(NSString * _Nonnull pattern,
|
||||||
|
NSUInteger idx,
|
||||||
|
BOOL * _Nonnull stop) {
|
||||||
|
NSString *replacement = (replacements.count > idx
|
||||||
|
? replacements[idx]
|
||||||
|
: @"");
|
||||||
|
[self replaceAllOccurrencesOfPattern:pattern
|
||||||
|
withTemplate:replacement
|
||||||
|
regularExpressionOptions:regexpOptions
|
||||||
|
matchingOptions:matchingOptions];
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user