diff options
Diffstat (limited to 'Source/SPStringAdditions.m')
-rw-r--r-- | Source/SPStringAdditions.m | 107 |
1 files changed, 93 insertions, 14 deletions
diff --git a/Source/SPStringAdditions.m b/Source/SPStringAdditions.m index 29b4144d..0254dc36 100644 --- a/Source/SPStringAdditions.m +++ b/Source/SPStringAdditions.m @@ -31,11 +31,7 @@ #import "SPStringAdditions.h" #import "RegexKitLite.h" -@interface NSString (PrivateAPI) - -- (NSInteger)_smallestOf:(NSInteger)a andOf:(NSInteger)b andOf:(NSInteger)c; - -@end +static NSInteger _smallestOf(NSInteger a, NSInteger b, NSInteger c); @implementation NSString (SPStringAdditions) @@ -326,7 +322,7 @@ NSMutableArray *lineRangesArray = [NSMutableArray array]; // Check that the range supplied is valid - if not return an empty array. - if (aRange.location == NSNotFound || aRange.location + aRange.length > [self length]) { + if (aRange.location == NSNotFound || NSMaxRange(aRange) > [self length]) { return lineRangesArray; } @@ -336,9 +332,9 @@ [lineRangesArray addObject:NSStringFromRange(currentLineRange)]; // Loop through until the line end matches or surpasses the end of the specified range - while (currentLineRange.location + currentLineRange.length < aRange.location + aRange.length) + while (NSMaxRange(currentLineRange) < NSMaxRange(aRange)) { - currentLineRange = [self lineRangeForRange:NSMakeRange(currentLineRange.location + currentLineRange.length, 0)]; + currentLineRange = [self lineRangeForRange:NSMakeRange(NSMaxRange(currentLineRange), 0)]; [lineRangesArray addObject:NSStringFromRange(currentLineRange)]; } @@ -426,9 +422,9 @@ cost = ([stringA characterAtIndex:i - 1] == [stringB characterAtIndex:j - 1]) ? 0 : 1; d[j * n + i] = - [self _smallestOf:d[(j - 1) * n + i] + 1 - andOf:d[j * n + i - 1] + 1 - andOf:d[(j - 1) * n + i -1] + cost]; + _smallestOf(d[(j - 1) * n + i] + 1, + d[j * n + i - 1] + 1, + d[(j - 1) * n + i -1] + cost); } distance = d[n * m - 1]; @@ -468,10 +464,95 @@ } } +- (BOOL)nonConsecutivelySearchString:(NSString *)other matchingRanges:(NSArray **)submatches +{ + NSStringCompareOptions opts = NSCaseInsensitiveSearch|NSDiacriticInsensitiveSearch|NSWidthInsensitiveSearch; + BOOL recordMatches = (submatches != NULL); //for readability + NSRange selfRange = NSMakeRange(0, [self length]); + + //shortcut: * a longer string can never be contained in a shorter one. + // * nil can never match in a string. + // * an empty other can never match if self is non-empty + if(([other length] > [self length]) || (!other) || ([self length] && ![other length])) + return NO; + + //handle the simple case via the default algorithm + if ([self compare:other options:opts] == NSOrderedSame) { + if(recordMatches) { + *submatches = [NSArray arrayWithObject:[NSValue valueWithRange:selfRange]]; + } + return YES; + } + + // for now let's save the overhead of NSArray and NSValues + NSRange *tmpMatchStore = recordMatches? calloc([other length], sizeof(NSRange)) : NULL; + __block NSUInteger matchCount = 0; + __block NSRange searchRange = selfRange; + + //this looks a bit silly but is basically Apples approach to handling multibyte charsets + void (^it)(NSString *,NSRange,NSRange,BOOL *) = ^(NSString *substring,NSRange substringRange,NSRange enclosingRange,BOOL *stop) { + //look for the current character of other in the remaining part of self + NSRange found = [self rangeOfString:substring options:opts range:searchRange]; + if(found.location == NSNotFound) { + matchCount = 0; //reset match count to "no match" + *stop = YES; + return; + } + if(recordMatches) + tmpMatchStore[matchCount] = found; + matchCount++; + //move the next search past the current match + searchRange.location = NSMaxRange(found); + searchRange.length = [self length] - searchRange.location; + }; + + [other enumerateSubstringsInRange:NSMakeRange(0, [other length]) + options:NSStringEnumerationByComposedCharacterSequences + usingBlock:it]; + + if(matchCount && recordMatches) { + //we want to re-combine sub-matches that are actually consecutive + + //This algorithm uses a simple look-behind for merges: + // Object 1 checks if it continues object 0. If so, 1 and 0 will merge + // and be placed in the slot of 0 (slot 1 is now invalid). + // Then object 2 checks if it continues object 0. If it does not, it is + // placed in slot 1. + // Object 3 then checks if it continues object 1 and so on... + NSUInteger mergeTarget = 0; + for (NSUInteger i = 1; i < matchCount; i++ ) { + NSRange prev = tmpMatchStore[mergeTarget]; + NSRange this = tmpMatchStore[i]; + //if the previous match ends right before this match begins we can merge them + if(NSMaxRange(prev) == this.location) { + NSRange mergedRange = NSMakeRange(prev.location, prev.length+this.length); + tmpMatchStore[mergeTarget] = mergedRange; + } + //otherwise we have to move on to the next and make ourselves the new base + else { + if(++mergeTarget != i) + tmpMatchStore[mergeTarget] = this; + } + } + + NSMutableArray *combinedArray = [NSMutableArray arrayWithCapacity:mergeTarget+1]; + for (NSUInteger j = 0; j <= mergeTarget; j++) { + [combinedArray addObject:[NSValue valueWithRange:tmpMatchStore[j]]]; + } + + *submatches = combinedArray; + } + + free(tmpMatchStore); // free(NULL) is safe as per OS X man page + return (matchCount > 0); +} + +@end + /** * Returns the minimum of a, b and c. */ -- (NSInteger)_smallestOf:(NSInteger)a andOf:(NSInteger)b andOf:(NSInteger)c +static NSInteger _smallestOf(NSInteger a, NSInteger b, NSInteger c) { NSInteger min = a; @@ -481,5 +562,3 @@ return min; } - -@end |