Преглед изворни кода

Added a whole lot of stuff

master
achmizs пре 4 година
родитељ
комит
1c10fa4b83
2 измењених фајлова са 1038 додато и 62 уклоњено
  1. 270
    12
      NSString+SA_NSStringExtensions.h
  2. 768
    50
      NSString+SA_NSStringExtensions.m

+ 270
- 12
NSString+SA_NSStringExtensions.h Прегледај датотеку

@@ -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)

- (BOOL)containsCharactersInSet:(NSCharacterSet *)characters;
- (BOOL)containsCharactersInString:(NSString *)characters;
/******************************/
#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;

- (NSRange)firstWhitespaceAfterRange:(NSRange)aRange;
- (NSRange)firstNonWhitespaceAfterRange:(NSRange)aRange;
/****************************************************/
#pragma mark - Regular expression convenience methods
/****************************************************/

- (NSRange)rangeAfterRange:(NSRange)aRange;
- (NSRange)rangeFromEndOfRange:(NSRange)aRange;
- (NSRange)rangeToEndFrom:(NSRange)aRange;
-(NSRegularExpression *) regularExpression;
-(NSRegularExpression *) regularExpressionWithOptions:(NSRegularExpressionOptions)options;

- (NSArray <NSString *> *)componentsSplitByWhitespace;
- (NSArray <NSString *> *)componentsSplitByWhitespaceWithMaxSplits:(NSUInteger)maxSplits;
-(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


+ 768
- 50
NSString+SA_NSStringExtensions.m Прегледај датотеку

@@ -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) lastNonWhitespaceBeforeRange:(NSRange)aRange {
NSRange stringUntilRange = NSMakeRange(0, aRange.location);
NSRange lastNonWhitespace = [self rangeOfCharacterFromSet:[[NSCharacterSet whitespaceCharacterSet] invertedSet]
options:NSBackwardsSearch
range:stringUntilRange];
return lastNonWhitespace;
}

/********************/
#pragma mark - Ranges
/********************/

-(NSRange) rangeAfterRange:(NSRange)aRange {
return NSMakeRange(NSMaxRange(aRange), self.length - NSMaxRange(aRange));
}

-(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)rangeFromEndOfRange:(NSRange)aRange
{
return NSRangeMake(NSRangeMax(aRange) - 1, self.length - NSRangeMax(aRange) + 1);
-(NSRange) fullRange {
return NSMakeRange(0, self.length);
}

- (NSRange)rangeToEndFrom:(NSRange)aRange
{
return NSRangeMake(aRange.location, self.length - aRange.location);
-(NSRange) endRange {
return NSMakeRange(self.length, 0);
}

- (NSArray <NSString *> *)componentsSplitByWhitespace
{
/***********************/
#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…
Откажи
Сачувај