diff options
Diffstat (limited to 'third_party/mozilla/NSString+Utils.m')
-rw-r--r-- | third_party/mozilla/NSString+Utils.m | 362 |
1 files changed, 362 insertions, 0 deletions
diff --git a/third_party/mozilla/NSString+Utils.m b/third_party/mozilla/NSString+Utils.m new file mode 100644 index 0000000..c145505 --- /dev/null +++ b/third_party/mozilla/NSString+Utils.m @@ -0,0 +1,362 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Chimera code. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 2002 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Simon Fraser <sfraser@netscape.com> + * David Haas <haasd@cae.wisc.edu> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#import <AppKit/AppKit.h> // for NSStringDrawing.h + +#import "NSString+Utils.h" + + +@implementation NSString (ChimeraStringUtils) + ++ (id)ellipsisString +{ + static NSString* sEllipsisString = nil; + if (!sEllipsisString) { + unichar ellipsisChar = 0x2026; + sEllipsisString = [[NSString alloc] initWithCharacters:&ellipsisChar length:1]; + } + + return sEllipsisString; +} + ++ (NSString*)stringWithUUID +{ + NSString* uuidString = nil; + CFUUIDRef newUUID = CFUUIDCreate(kCFAllocatorDefault); + if (newUUID) { + uuidString = (NSString *)CFUUIDCreateString(kCFAllocatorDefault, newUUID); + CFRelease(newUUID); + } + return [uuidString autorelease]; +} + +- (BOOL)isEqualToStringIgnoringCase:(NSString*)inString +{ + return ([self compare:inString options:NSCaseInsensitiveSearch] == NSOrderedSame); +} + +- (BOOL)hasCaseInsensitivePrefix:(NSString*)inString +{ + if ([self length] < [inString length]) + return NO; + return ([self compare:inString options:NSCaseInsensitiveSearch range:NSMakeRange(0, [inString length])] == NSOrderedSame); +} + +- (BOOL)isLooselyValidatedURI +{ + return ([self hasCaseInsensitivePrefix:@"javascript:"] || [self hasCaseInsensitivePrefix:@"data:"]); +} + +- (BOOL)isPotentiallyDangerousURI +{ + return ([self hasCaseInsensitivePrefix:@"javascript:"] || [self hasCaseInsensitivePrefix:@"data:"]); +} + +- (BOOL)isValidURI +{ + // This will only return a non-nil object for valid, well-formed URI strings + NSURL* testURL = [NSURL URLWithString:self]; + + // |javascript:| and |data:| URIs might not have passed the test, + // but spaces will work OK, so evaluate them separately. + if ((testURL) || [self isLooselyValidatedURI]) { + return YES; + } + return NO; +} + +- (NSString *)stringByRemovingCharactersInSet:(NSCharacterSet*)characterSet +{ + NSScanner* cleanerScanner = [NSScanner scannerWithString:self]; + NSMutableString* cleanString = [NSMutableString stringWithCapacity:[self length]]; + // Make sure we don't skip whitespace, which NSScanner does by default + [cleanerScanner setCharactersToBeSkipped:[NSCharacterSet characterSetWithCharactersInString:@""]]; + + while (![cleanerScanner isAtEnd]) { + NSString* stringFragment; + if ([cleanerScanner scanUpToCharactersFromSet:characterSet intoString:&stringFragment]) + [cleanString appendString:stringFragment]; + + [cleanerScanner scanCharactersFromSet:characterSet intoString:nil]; + } + + return cleanString; +} + +- (NSString *)stringByReplacingCharactersInSet:(NSCharacterSet*)characterSet + withString:(NSString*)string +{ + NSScanner* cleanerScanner = [NSScanner scannerWithString:self]; + NSMutableString* cleanString = [NSMutableString stringWithCapacity:[self length]]; + // Make sure we don't skip whitespace, which NSScanner does by default + [cleanerScanner setCharactersToBeSkipped:[NSCharacterSet characterSetWithCharactersInString:@""]]; + + while (![cleanerScanner isAtEnd]) + { + NSString* stringFragment; + if ([cleanerScanner scanUpToCharactersFromSet:characterSet intoString:&stringFragment]) + [cleanString appendString:stringFragment]; + + if ([cleanerScanner scanCharactersFromSet:characterSet intoString:nil]) + [cleanString appendString:string]; + } + + return cleanString; +} + +- (NSString*)stringByTruncatingTo:(unsigned int)maxCharacters at:(ETruncationType)truncationType +{ + if ([self length] > maxCharacters) + { + NSMutableString *mutableCopy = [self mutableCopy]; + [mutableCopy truncateTo:maxCharacters at:truncationType]; + return [mutableCopy autorelease]; + } + + return self; +} + +- (NSString *)stringByTruncatingToWidth:(float)inWidth at:(ETruncationType)truncationType + withAttributes:(NSDictionary *)attributes +{ + if ([self sizeWithAttributes:attributes].width > inWidth) + { + NSMutableString *mutableCopy = [self mutableCopy]; + [mutableCopy truncateToWidth:inWidth at:truncationType withAttributes:attributes]; + return [mutableCopy autorelease]; + } + + return self; +} + +- (NSString *)stringByTrimmingWhitespace +{ + return [self stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; +} + +-(NSString *)stringByRemovingAmpEscapes +{ + NSMutableString* dirtyStringMutant = [NSMutableString stringWithString:self]; + [dirtyStringMutant replaceOccurrencesOfString:@"&" + withString:@"&" + options:NSLiteralSearch + range:NSMakeRange(0,[dirtyStringMutant length])]; + [dirtyStringMutant replaceOccurrencesOfString:@""" + withString:@"\"" + options:NSLiteralSearch + range:NSMakeRange(0,[dirtyStringMutant length])]; + [dirtyStringMutant replaceOccurrencesOfString:@"<" + withString:@"<" + options:NSLiteralSearch + range:NSMakeRange(0,[dirtyStringMutant length])]; + [dirtyStringMutant replaceOccurrencesOfString:@">" + withString:@">" + options:NSLiteralSearch + range:NSMakeRange(0,[dirtyStringMutant length])]; + [dirtyStringMutant replaceOccurrencesOfString:@"—" + withString:@"-" + options:NSLiteralSearch + range:NSMakeRange(0,[dirtyStringMutant length])]; + [dirtyStringMutant replaceOccurrencesOfString:@"'" + withString:@"'" + options:NSLiteralSearch + range:NSMakeRange(0,[dirtyStringMutant length])]; + // fix import from old Firefox versions, which exported ' instead of a plain apostrophe + [dirtyStringMutant replaceOccurrencesOfString:@"'" + withString:@"'" + options:NSLiteralSearch + range:NSMakeRange(0,[dirtyStringMutant length])]; + return [dirtyStringMutant stringByRemovingCharactersInSet:[NSCharacterSet controlCharacterSet]]; +} + +-(NSString *)stringByAddingAmpEscapes +{ + NSMutableString* dirtyStringMutant = [NSMutableString stringWithString:self]; + [dirtyStringMutant replaceOccurrencesOfString:@"&" + withString:@"&" + options:NSLiteralSearch + range:NSMakeRange(0,[dirtyStringMutant length])]; + [dirtyStringMutant replaceOccurrencesOfString:@"\"" + withString:@""" + options:NSLiteralSearch + range:NSMakeRange(0,[dirtyStringMutant length])]; + [dirtyStringMutant replaceOccurrencesOfString:@"<" + withString:@"<" + options:NSLiteralSearch + range:NSMakeRange(0,[dirtyStringMutant length])]; + [dirtyStringMutant replaceOccurrencesOfString:@">" + withString:@">" + options:NSLiteralSearch + range:NSMakeRange(0,[dirtyStringMutant length])]; + return [NSString stringWithString:dirtyStringMutant]; +} + +@end + + +@implementation NSMutableString (ChimeraMutableStringUtils) + +- (void)truncateTo:(unsigned)maxCharacters at:(ETruncationType)truncationType +{ + if ([self length] <= maxCharacters) + return; + + NSRange replaceRange; + replaceRange.length = [self length] - maxCharacters; + + switch (truncationType) { + case kTruncateAtStart: + replaceRange.location = 0; + break; + + case kTruncateAtMiddle: + replaceRange.location = maxCharacters / 2; + break; + + case kTruncateAtEnd: + replaceRange.location = maxCharacters; + break; + + default: +#if DEBUG + NSLog(@"Unknown truncation type in stringByTruncatingTo::"); +#endif + replaceRange.location = maxCharacters; + break; + } + + [self replaceCharactersInRange:replaceRange withString:[NSString ellipsisString]]; +} + + +- (void)truncateToWidth:(float)maxWidth + at:(ETruncationType)truncationType + withAttributes:(NSDictionary *)attributes +{ + // First check if we have to truncate at all. + if ([self sizeWithAttributes:attributes].width <= maxWidth) + return; + + // Essentially, we perform a binary search on the string length + // which fits best into maxWidth. + + float width = maxWidth; + int lo = 0; + int hi = [self length]; + int mid; + + // Make a backup copy of the string so that we can restore it if we fail low. + NSMutableString *backup = [self mutableCopy]; + + while (hi >= lo) { + mid = (hi + lo) / 2; + + // Cut to mid chars and calculate the resulting width + [self truncateTo:mid at:truncationType]; + width = [self sizeWithAttributes:attributes].width; + + if (width > maxWidth) { + // Fail high - string is still to wide. For the next cut, we can simply + // work on the already cut string, so we don't restore using the backup. + hi = mid - 1; + } + else if (width == maxWidth) { + // Perfect match, abort the search. + break; + } + else { + // Fail low - we cut off too much. Restore the string before cutting again. + lo = mid + 1; + [self setString:backup]; + } + } + // Perform the final cut (unless this was already a perfect match). + if (width != maxWidth) + [self truncateTo:hi at:truncationType]; + [backup release]; +} + +@end + +@implementation NSString (ChimeraFilePathStringUtils) + +- (NSString*)volumeNamePathComponent +{ + // if the file doesn't exist, then componentsToDisplayForPath will return nil, + // so back up to the nearest existing dir + NSString* curPath = self; + while (![[NSFileManager defaultManager] fileExistsAtPath:curPath]) + { + NSString* parentDirPath = [curPath stringByDeletingLastPathComponent]; + if ([parentDirPath isEqualToString:curPath]) + break; // avoid endless loop + curPath = parentDirPath; + } + + NSArray* displayComponents = [[NSFileManager defaultManager] componentsToDisplayForPath:curPath]; + if ([displayComponents count] > 0) + return [displayComponents objectAtIndex:0]; + + return self; +} + +- (NSString*)displayNameOfLastPathComponent +{ + return [[NSFileManager defaultManager] displayNameAtPath:self]; +} + +@end + +@implementation NSString (CaminoURLStringUtils) + +- (BOOL)isBlankURL +{ + return ([self isEqualToString:@"about:blank"] || [self isEqualToString:@""]); +} + +// Excluded character list comes from RFC2396 and by examining Safari's behaviour +- (NSString*)unescapedURI +{ + NSString *unescapedURI = (NSString*)CFURLCreateStringByReplacingPercentEscapesUsingEncoding(kCFAllocatorDefault, + (CFStringRef)self, + CFSTR(" \"\';/?:@&=+$,#"), + kCFStringEncodingUTF8); + return unescapedURI ? [unescapedURI autorelease] : self; +} + +@end |