Added a whole lot of stuff
This commit is contained in:
parent
1cf59542b7
commit
1c10fa4b83
@ -1,26 +1,284 @@
|
||||
//
|
||||
// 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>
|
||||
|
||||
/********************************************************/
|
||||
#pragma mark - SA_NSStringExtensions category declaration
|
||||
/********************************************************/
|
||||
|
||||
@interface NSString (SA_NSStringExtensions)
|
||||
|
||||
/******************************/
|
||||
#pragma mark - Class properties
|
||||
/******************************/
|
||||
|
||||
@property (class) BOOL SA_NSStringExtensions_RaiseRegularExpressionCreateException;
|
||||
|
||||
/*************************************/
|
||||
#pragma mark - Working with characters
|
||||
/*************************************/
|
||||
|
||||
/* Testing for occurrences of specified characters.
|
||||
*/
|
||||
-(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
|
||||
|
||||
/**************************************************************************/
|
||||
#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
|
||||
// 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 "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)
|
||||
|
||||
- (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];
|
||||
return rangeOfCharacters.location != NSNotFound;
|
||||
}
|
||||
|
||||
- (BOOL)containsCharactersInString:(NSString *)characters
|
||||
{
|
||||
-(BOOL) containsCharactersInString:(NSString *)characters {
|
||||
return [self containsCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:characters]];
|
||||
}
|
||||
|
||||
- (NSRange)firstWhitespaceAfterRange:(NSRange)aRange
|
||||
{
|
||||
NSRange restOfString = NSRangeMake(NSRangeMax(aRange), self.length - NSRangeMax(aRange));
|
||||
NSRange firstWhitespace = [self rangeOfCharacterFromSet:[NSCharacterSet whitespaceCharacterSet] options:0 range:restOfString];
|
||||
-(NSString *) stringByRemovingCharactersInSet:(NSCharacterSet *)characters {
|
||||
NSMutableString *workingCopy = [self mutableCopy];
|
||||
|
||||
[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;
|
||||
}
|
||||
|
||||
- (NSRange)firstNonWhitespaceAfterRange:(NSRange)aRange
|
||||
{
|
||||
NSRange restOfString = NSRangeMake(NSRangeMax(aRange), self.length - NSRangeMax(aRange));
|
||||
NSRange firstNonWhitespace = [self rangeOfCharacterFromSet:[[NSCharacterSet whitespaceCharacterSet] invertedSet] options:0 range:restOfString];
|
||||
-(NSRange) firstNonWhitespaceAfterRange:(NSRange)aRange {
|
||||
NSRange restOfString = NSMakeRange(NSMaxRange(aRange), self.length - NSMaxRange(aRange));
|
||||
NSRange firstNonWhitespace = [self rangeOfCharacterFromSet:[[NSCharacterSet whitespaceCharacterSet] invertedSet]
|
||||
options:(NSStringCompareOptions) 0
|
||||
range:restOfString];
|
||||
|
||||
return firstNonWhitespace;
|
||||
}
|
||||
|
||||
- (NSRange)rangeAfterRange:(NSRange)aRange
|
||||
{
|
||||
return NSRangeMake(NSRangeMax(aRange), self.length - NSRangeMax(aRange));
|
||||
-(NSRange) lastWhitespaceBeforeRange:(NSRange)aRange {
|
||||
NSRange stringUntilRange = NSMakeRange(0, aRange.location);
|
||||
NSRange lastWhitespace = [self rangeOfCharacterFromSet:[NSCharacterSet whitespaceCharacterSet]
|
||||
options:NSBackwardsSearch
|
||||
range:stringUntilRange];
|
||||
return lastWhitespace;
|
||||
}
|
||||
|
||||
- (NSRange)rangeFromEndOfRange:(NSRange)aRange
|
||||
{
|
||||
return NSRangeMake(NSRangeMax(aRange) - 1, self.length - NSRangeMax(aRange) + 1);
|
||||
-(NSRange) lastNonWhitespaceBeforeRange:(NSRange)aRange {
|
||||
NSRange stringUntilRange = NSMakeRange(0, aRange.location);
|
||||
NSRange lastNonWhitespace = [self rangeOfCharacterFromSet:[[NSCharacterSet whitespaceCharacterSet] invertedSet]
|
||||
options:NSBackwardsSearch
|
||||
range:stringUntilRange];
|
||||
return lastNonWhitespace;
|
||||
}
|
||||
|
||||
- (NSRange)rangeToEndFrom:(NSRange)aRange
|
||||
{
|
||||
return NSRangeMake(aRange.location, self.length - aRange.location);
|
||||
/********************/
|
||||
#pragma mark - Ranges
|
||||
/********************/
|
||||
|
||||
-(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];
|
||||
}
|
||||
|
||||
- (NSArray <NSString *> *)componentsSplitByWhitespaceWithMaxSplits:(NSUInteger)maxSplits
|
||||
{
|
||||
if(maxSplits == 0)
|
||||
{
|
||||
return [NSMutableArray arrayWithObject:self];
|
||||
-(NSArray <NSString *> *) componentsSplitByWhitespaceWithMaxSplits:(NSUInteger)maxSplits {
|
||||
if (maxSplits == 0) {
|
||||
return @[ self ];
|
||||
}
|
||||
|
||||
__block NSMutableArray <NSString *> *components = [NSMutableArray array];
|
||||
NSMutableArray <NSString *> *components = [NSMutableArray array];
|
||||
|
||||
__block NSUInteger tokenStart;
|
||||
__block NSUInteger tokenEnd;
|
||||
__block BOOL currentlyInToken = NO;
|
||||
__block NSRange tokenRange = NSRangeMake(NSNotFound, 0);
|
||||
__block NSRange tokenRange = NSMakeRange(NSNotFound, 0);
|
||||
|
||||
__block NSUInteger splits = 0;
|
||||
|
||||
[self enumerateSubstringsInRange:NSRangeMake(0, self.length)
|
||||
[self enumerateSubstringsInRange:self.fullRange
|
||||
options:NSStringEnumerationByComposedCharacterSequences
|
||||
usingBlock:^(NSString *character, NSRange characterRange, NSRange enclosingRange, BOOL *stop)
|
||||
{
|
||||
if(currentlyInToken == NO && [character containsCharactersInSet:[[NSCharacterSet whitespaceCharacterSet] invertedSet]])
|
||||
{
|
||||
usingBlock:^(NSString *character,
|
||||
NSRange characterRange,
|
||||
NSRange enclosingRange,
|
||||
BOOL *stop) {
|
||||
if ( currentlyInToken == NO
|
||||
&& [character containsCharactersInSet:[[NSCharacterSet whitespaceCharacterSet] invertedSet]]
|
||||
) {
|
||||
currentlyInToken = YES;
|
||||
tokenStart = characterRange.location;
|
||||
}
|
||||
else if(currentlyInToken == YES && [character containsCharactersInSet:[NSCharacterSet whitespaceCharacterSet]])
|
||||
{
|
||||
} else if ( currentlyInToken == YES
|
||||
&& [character containsCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]
|
||||
) {
|
||||
currentlyInToken = NO;
|
||||
tokenEnd = characterRange.location;
|
||||
|
||||
tokenRange = NSRangeMake(tokenStart, tokenEnd - tokenStart);
|
||||
tokenRange = NSMakeRange(tokenStart, tokenEnd - tokenStart);
|
||||
[components addObject:[self substringWithRange:tokenRange]];
|
||||
splits++;
|
||||
if(splits == maxSplits)
|
||||
{
|
||||
if (splits == maxSplits) {
|
||||
*stop = YES;
|
||||
NSRange lastTokenRange = [self rangeToEndFrom:[self firstNonWhitespaceAfterRange:tokenRange]];
|
||||
if(lastTokenRange.location != NSNotFound)
|
||||
{
|
||||
if (lastTokenRange.location != NSNotFound) {
|
||||
[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(splits < maxSplits && currentlyInToken == YES)
|
||||
{
|
||||
if ( splits < maxSplits
|
||||
&& currentlyInToken == YES) {
|
||||
tokenEnd = self.length;
|
||||
|
||||
tokenRange = NSRangeMake(tokenStart, tokenEnd - tokenStart);
|
||||
tokenRange = NSMakeRange(tokenStart,
|
||||
tokenEnd - tokenStart);
|
||||
[components addObject:[self substringWithRange:tokenRange]];
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user