| OLD | NEW |
| (Empty) | |
| 1 /* ***** BEGIN LICENSE BLOCK ***** |
| 2 * Version: MPL 1.1/GPL 2.0/LGPL 2.1 |
| 3 * |
| 4 * The contents of this file are subject to the Mozilla Public License Version |
| 5 * 1.1 (the "License"); you may not use this file except in compliance with |
| 6 * the License. You may obtain a copy of the License at |
| 7 * http://www.mozilla.org/MPL/ |
| 8 * |
| 9 * Software distributed under the License is distributed on an "AS IS" basis, |
| 10 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License |
| 11 * for the specific language governing rights and limitations under the |
| 12 * License. |
| 13 * |
| 14 * The Original Code is Chimera code. |
| 15 * |
| 16 * The Initial Developer of the Original Code is |
| 17 * Netscape Communications Corporation. |
| 18 * Portions created by the Initial Developer are Copyright (C) 2002 |
| 19 * the Initial Developer. All Rights Reserved. |
| 20 * |
| 21 * Contributor(s): |
| 22 * Simon Fraser <sfraser@netscape.com> |
| 23 * David Haas <haasd@cae.wisc.edu> |
| 24 * |
| 25 * Alternatively, the contents of this file may be used under the terms of |
| 26 * either the GNU General Public License Version 2 or later (the "GPL"), or |
| 27 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), |
| 28 * in which case the provisions of the GPL or the LGPL are applicable instead |
| 29 * of those above. If you wish to allow use of your version of this file only |
| 30 * under the terms of either the GPL or the LGPL, and not to allow others to |
| 31 * use your version of this file under the terms of the MPL, indicate your |
| 32 * decision by deleting the provisions above and replace them with the notice |
| 33 * and other provisions required by the GPL or the LGPL. If you do not delete |
| 34 * the provisions above, a recipient may use your version of this file under |
| 35 * the terms of any one of the MPL, the GPL or the LGPL. |
| 36 * |
| 37 * ***** END LICENSE BLOCK ***** */ |
| 38 |
| 39 #import <AppKit/AppKit.h> // for NSStringDrawing.h |
| 40 |
| 41 #import "NSString+Utils.h" |
| 42 |
| 43 |
| 44 @implementation NSString (ChimeraStringUtils) |
| 45 |
| 46 + (id)ellipsisString |
| 47 { |
| 48 static NSString* sEllipsisString = nil; |
| 49 if (!sEllipsisString) { |
| 50 unichar ellipsisChar = 0x2026; |
| 51 sEllipsisString = [[NSString alloc] initWithCharacters:&ellipsisChar length:
1]; |
| 52 } |
| 53 |
| 54 return sEllipsisString; |
| 55 } |
| 56 |
| 57 + (NSString*)stringWithUUID |
| 58 { |
| 59 NSString* uuidString = nil; |
| 60 CFUUIDRef newUUID = CFUUIDCreate(kCFAllocatorDefault); |
| 61 if (newUUID) { |
| 62 uuidString = (NSString *)CFUUIDCreateString(kCFAllocatorDefault, newUUID); |
| 63 CFRelease(newUUID); |
| 64 } |
| 65 return [uuidString autorelease]; |
| 66 } |
| 67 |
| 68 - (BOOL)isEqualToStringIgnoringCase:(NSString*)inString |
| 69 { |
| 70 return ([self compare:inString options:NSCaseInsensitiveSearch] == NSOrderedSa
me); |
| 71 } |
| 72 |
| 73 - (BOOL)hasCaseInsensitivePrefix:(NSString*)inString |
| 74 { |
| 75 if ([self length] < [inString length]) |
| 76 return NO; |
| 77 return ([self compare:inString options:NSCaseInsensitiveSearch range:NSMakeRan
ge(0, [inString length])] == NSOrderedSame); |
| 78 } |
| 79 |
| 80 - (BOOL)isLooselyValidatedURI |
| 81 { |
| 82 return ([self hasCaseInsensitivePrefix:@"javascript:"] || [self hasCaseInsensi
tivePrefix:@"data:"]); |
| 83 } |
| 84 |
| 85 - (BOOL)isPotentiallyDangerousURI |
| 86 { |
| 87 return ([self hasCaseInsensitivePrefix:@"javascript:"] || [self hasCaseInsensi
tivePrefix:@"data:"]); |
| 88 } |
| 89 |
| 90 - (BOOL)isValidURI |
| 91 { |
| 92 // This will only return a non-nil object for valid, well-formed URI strings |
| 93 NSURL* testURL = [NSURL URLWithString:self]; |
| 94 |
| 95 // |javascript:| and |data:| URIs might not have passed the test, |
| 96 // but spaces will work OK, so evaluate them separately. |
| 97 if ((testURL) || [self isLooselyValidatedURI]) { |
| 98 return YES; |
| 99 } |
| 100 return NO; |
| 101 } |
| 102 |
| 103 - (NSString *)stringByRemovingCharactersInSet:(NSCharacterSet*)characterSet |
| 104 { |
| 105 NSScanner* cleanerScanner = [NSScanner scannerWithString:self]; |
| 106 NSMutableString* cleanString = [NSMutableString stringWithCapacity:[self le
ngth]]; |
| 107 // Make sure we don't skip whitespace, which NSScanner does by default |
| 108 [cleanerScanner setCharactersToBeSkipped:[NSCharacterSet characterSetWithChara
ctersInString:@""]]; |
| 109 |
| 110 while (![cleanerScanner isAtEnd]) { |
| 111 NSString* stringFragment; |
| 112 if ([cleanerScanner scanUpToCharactersFromSet:characterSet intoString:&strin
gFragment]) |
| 113 [cleanString appendString:stringFragment]; |
| 114 |
| 115 [cleanerScanner scanCharactersFromSet:characterSet intoString:nil]; |
| 116 } |
| 117 |
| 118 return cleanString; |
| 119 } |
| 120 |
| 121 - (NSString *)stringByReplacingCharactersInSet:(NSCharacterSet*)characterSet |
| 122 withString:(NSString*)string |
| 123 { |
| 124 NSScanner* cleanerScanner = [NSScanner scannerWithString:self]; |
| 125 NSMutableString* cleanString = [NSMutableString stringWithCapacity:[self le
ngth]]; |
| 126 // Make sure we don't skip whitespace, which NSScanner does by default |
| 127 [cleanerScanner setCharactersToBeSkipped:[NSCharacterSet characterSetWithChara
ctersInString:@""]]; |
| 128 |
| 129 while (![cleanerScanner isAtEnd]) |
| 130 { |
| 131 NSString* stringFragment; |
| 132 if ([cleanerScanner scanUpToCharactersFromSet:characterSet intoString:&strin
gFragment]) |
| 133 [cleanString appendString:stringFragment]; |
| 134 |
| 135 if ([cleanerScanner scanCharactersFromSet:characterSet intoString:nil]) |
| 136 [cleanString appendString:string]; |
| 137 } |
| 138 |
| 139 return cleanString; |
| 140 } |
| 141 |
| 142 - (NSString*)stringByTruncatingTo:(unsigned int)maxCharacters at:(ETruncationTyp
e)truncationType |
| 143 { |
| 144 if ([self length] > maxCharacters) |
| 145 { |
| 146 NSMutableString *mutableCopy = [self mutableCopy]; |
| 147 [mutableCopy truncateTo:maxCharacters at:truncationType]; |
| 148 return [mutableCopy autorelease]; |
| 149 } |
| 150 |
| 151 return self; |
| 152 } |
| 153 |
| 154 - (NSString *)stringByTruncatingToWidth:(float)inWidth at:(ETruncationType)trunc
ationType |
| 155 withAttributes:(NSDictionary *)attributes |
| 156 { |
| 157 if ([self sizeWithAttributes:attributes].width > inWidth) |
| 158 { |
| 159 NSMutableString *mutableCopy = [self mutableCopy]; |
| 160 [mutableCopy truncateToWidth:inWidth at:truncationType withAttributes:attrib
utes]; |
| 161 return [mutableCopy autorelease]; |
| 162 } |
| 163 |
| 164 return self; |
| 165 } |
| 166 |
| 167 - (NSString *)stringByTrimmingWhitespace |
| 168 { |
| 169 return [self stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharact
erSet]]; |
| 170 } |
| 171 |
| 172 -(NSString *)stringByRemovingAmpEscapes |
| 173 { |
| 174 NSMutableString* dirtyStringMutant = [NSMutableString stringWithString:self]; |
| 175 [dirtyStringMutant replaceOccurrencesOfString:@"&" |
| 176 withString:@"&" |
| 177 options:NSLiteralSearch |
| 178 range:NSMakeRange(0,[dirtyStringMutant
length])]; |
| 179 [dirtyStringMutant replaceOccurrencesOfString:@""" |
| 180 withString:@"\"" |
| 181 options:NSLiteralSearch |
| 182 range:NSMakeRange(0,[dirtyStringMutant
length])]; |
| 183 [dirtyStringMutant replaceOccurrencesOfString:@"<" |
| 184 withString:@"<" |
| 185 options:NSLiteralSearch |
| 186 range:NSMakeRange(0,[dirtyStringMutant
length])]; |
| 187 [dirtyStringMutant replaceOccurrencesOfString:@">" |
| 188 withString:@">" |
| 189 options:NSLiteralSearch |
| 190 range:NSMakeRange(0,[dirtyStringMutant
length])]; |
| 191 [dirtyStringMutant replaceOccurrencesOfString:@"—" |
| 192 withString:@"-" |
| 193 options:NSLiteralSearch |
| 194 range:NSMakeRange(0,[dirtyStringMutant
length])]; |
| 195 [dirtyStringMutant replaceOccurrencesOfString:@"'" |
| 196 withString:@"'" |
| 197 options:NSLiteralSearch |
| 198 range:NSMakeRange(0,[dirtyStringMutant
length])]; |
| 199 // fix import from old Firefox versions, which exported ' instead of a pla
in apostrophe |
| 200 [dirtyStringMutant replaceOccurrencesOfString:@"'" |
| 201 withString:@"'" |
| 202 options:NSLiteralSearch |
| 203 range:NSMakeRange(0,[dirtyStringMutant
length])]; |
| 204 return [dirtyStringMutant stringByRemovingCharactersInSet:[NSCharacterSet cont
rolCharacterSet]]; |
| 205 } |
| 206 |
| 207 -(NSString *)stringByAddingAmpEscapes |
| 208 { |
| 209 NSMutableString* dirtyStringMutant = [NSMutableString stringWithString:self]; |
| 210 [dirtyStringMutant replaceOccurrencesOfString:@"&" |
| 211 withString:@"&" |
| 212 options:NSLiteralSearch |
| 213 range:NSMakeRange(0,[dirtyStringMutant
length])]; |
| 214 [dirtyStringMutant replaceOccurrencesOfString:@"\"" |
| 215 withString:@""" |
| 216 options:NSLiteralSearch |
| 217 range:NSMakeRange(0,[dirtyStringMutant
length])]; |
| 218 [dirtyStringMutant replaceOccurrencesOfString:@"<" |
| 219 withString:@"<" |
| 220 options:NSLiteralSearch |
| 221 range:NSMakeRange(0,[dirtyStringMutant
length])]; |
| 222 [dirtyStringMutant replaceOccurrencesOfString:@">" |
| 223 withString:@">" |
| 224 options:NSLiteralSearch |
| 225 range:NSMakeRange(0,[dirtyStringMutant
length])]; |
| 226 return [NSString stringWithString:dirtyStringMutant]; |
| 227 } |
| 228 |
| 229 @end |
| 230 |
| 231 |
| 232 @implementation NSMutableString (ChimeraMutableStringUtils) |
| 233 |
| 234 - (void)truncateTo:(unsigned)maxCharacters at:(ETruncationType)truncationType |
| 235 { |
| 236 if ([self length] <= maxCharacters) |
| 237 return; |
| 238 |
| 239 NSRange replaceRange; |
| 240 replaceRange.length = [self length] - maxCharacters; |
| 241 |
| 242 switch (truncationType) { |
| 243 case kTruncateAtStart: |
| 244 replaceRange.location = 0; |
| 245 break; |
| 246 |
| 247 case kTruncateAtMiddle: |
| 248 replaceRange.location = maxCharacters / 2; |
| 249 break; |
| 250 |
| 251 case kTruncateAtEnd: |
| 252 replaceRange.location = maxCharacters; |
| 253 break; |
| 254 |
| 255 default: |
| 256 #if DEBUG |
| 257 NSLog(@"Unknown truncation type in stringByTruncatingTo::"); |
| 258 #endif |
| 259 replaceRange.location = maxCharacters; |
| 260 break; |
| 261 } |
| 262 |
| 263 [self replaceCharactersInRange:replaceRange withString:[NSString ellipsisStrin
g]]; |
| 264 } |
| 265 |
| 266 |
| 267 - (void)truncateToWidth:(float)maxWidth |
| 268 at:(ETruncationType)truncationType |
| 269 withAttributes:(NSDictionary *)attributes |
| 270 { |
| 271 // First check if we have to truncate at all. |
| 272 if ([self sizeWithAttributes:attributes].width <= maxWidth) |
| 273 return; |
| 274 |
| 275 // Essentially, we perform a binary search on the string length |
| 276 // which fits best into maxWidth. |
| 277 |
| 278 float width = maxWidth; |
| 279 int lo = 0; |
| 280 int hi = [self length]; |
| 281 int mid; |
| 282 |
| 283 // Make a backup copy of the string so that we can restore it if we fail low. |
| 284 NSMutableString *backup = [self mutableCopy]; |
| 285 |
| 286 while (hi >= lo) { |
| 287 mid = (hi + lo) / 2; |
| 288 |
| 289 // Cut to mid chars and calculate the resulting width |
| 290 [self truncateTo:mid at:truncationType]; |
| 291 width = [self sizeWithAttributes:attributes].width; |
| 292 |
| 293 if (width > maxWidth) { |
| 294 // Fail high - string is still to wide. For the next cut, we can simply |
| 295 // work on the already cut string, so we don't restore using the backup. |
| 296 hi = mid - 1; |
| 297 } |
| 298 else if (width == maxWidth) { |
| 299 // Perfect match, abort the search. |
| 300 break; |
| 301 } |
| 302 else { |
| 303 // Fail low - we cut off too much. Restore the string before cutting again
. |
| 304 lo = mid + 1; |
| 305 [self setString:backup]; |
| 306 } |
| 307 } |
| 308 // Perform the final cut (unless this was already a perfect match). |
| 309 if (width != maxWidth) |
| 310 [self truncateTo:hi at:truncationType]; |
| 311 [backup release]; |
| 312 } |
| 313 |
| 314 @end |
| 315 |
| 316 @implementation NSString (ChimeraFilePathStringUtils) |
| 317 |
| 318 - (NSString*)volumeNamePathComponent |
| 319 { |
| 320 // if the file doesn't exist, then componentsToDisplayForPath will return nil, |
| 321 // so back up to the nearest existing dir |
| 322 NSString* curPath = self; |
| 323 while (![[NSFileManager defaultManager] fileExistsAtPath:curPath]) |
| 324 { |
| 325 NSString* parentDirPath = [curPath stringByDeletingLastPathComponent]; |
| 326 if ([parentDirPath isEqualToString:curPath]) |
| 327 break; // avoid endless loop |
| 328 curPath = parentDirPath; |
| 329 } |
| 330 |
| 331 NSArray* displayComponents = [[NSFileManager defaultManager] componentsToDispl
ayForPath:curPath]; |
| 332 if ([displayComponents count] > 0) |
| 333 return [displayComponents objectAtIndex:0]; |
| 334 |
| 335 return self; |
| 336 } |
| 337 |
| 338 - (NSString*)displayNameOfLastPathComponent |
| 339 { |
| 340 return [[NSFileManager defaultManager] displayNameAtPath:self]; |
| 341 } |
| 342 |
| 343 @end |
| 344 |
| 345 @implementation NSString (CaminoURLStringUtils) |
| 346 |
| 347 - (BOOL)isBlankURL |
| 348 { |
| 349 return ([self isEqualToString:@"about:blank"] || [self isEqualToString:@""]); |
| 350 } |
| 351 |
| 352 // Excluded character list comes from RFC2396 and by examining Safari's behaviou
r |
| 353 - (NSString*)unescapedURI |
| 354 { |
| 355 NSString *unescapedURI = (NSString*)CFURLCreateStringByReplacingPercentEscapes
UsingEncoding(kCFAllocatorDefault, |
| 356 (CFS
tringRef)self, |
| 357 CFST
R(" \"\';/?:@&=+$,#"), |
| 358 kCFS
tringEncodingUTF8); |
| 359 return unescapedURI ? [unescapedURI autorelease] : self; |
| 360 } |
| 361 |
| 362 @end |
| OLD | NEW |