• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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#include "url/gurl.h"
43
44
45@implementation NSString (ChimeraStringUtils)
46
47+ (id)ellipsisString
48{
49  static NSString* sEllipsisString = nil;
50  if (!sEllipsisString) {
51    unichar ellipsisChar = 0x2026;
52    sEllipsisString = [[NSString alloc] initWithCharacters:&ellipsisChar length:1];
53  }
54
55  return sEllipsisString;
56}
57
58+ (NSString*)stringWithUUID
59{
60  NSString* uuidString = nil;
61  CFUUIDRef newUUID = CFUUIDCreate(kCFAllocatorDefault);
62  if (newUUID) {
63    uuidString = (NSString *)CFUUIDCreateString(kCFAllocatorDefault, newUUID);
64    CFRelease(newUUID);
65  }
66  return [uuidString autorelease];
67}
68
69- (BOOL)isEqualToStringIgnoringCase:(NSString*)inString
70{
71  return ([self compare:inString options:NSCaseInsensitiveSearch] == NSOrderedSame);
72}
73
74- (BOOL)hasCaseInsensitivePrefix:(NSString*)inString
75{
76  if ([self length] < [inString length])
77    return NO;
78  return ([self compare:inString options:NSCaseInsensitiveSearch range:NSMakeRange(0, [inString length])] == NSOrderedSame);
79}
80
81- (BOOL)isLooselyValidatedURI
82{
83  return ([self hasCaseInsensitivePrefix:@"javascript:"] || [self hasCaseInsensitivePrefix:@"data:"]);
84}
85
86- (BOOL)isPotentiallyDangerousURI
87{
88  return ([self hasCaseInsensitivePrefix:@"javascript:"] || [self hasCaseInsensitivePrefix:@"data:"]);
89}
90
91- (BOOL)isValidURI
92{
93  // isValid() will only be true for valid, well-formed URI strings
94  GURL testURL([self UTF8String]);
95
96  // |javascript:| and |data:| URIs might not have passed the test,
97  // but spaces will work OK, so evaluate them separately.
98  if ((testURL.is_valid()) || [self isLooselyValidatedURI]) {
99    return YES;
100  }
101  return NO;
102}
103
104- (NSString *)stringByRemovingCharactersInSet:(NSCharacterSet*)characterSet
105{
106  NSScanner*       cleanerScanner = [NSScanner scannerWithString:self];
107  NSMutableString* cleanString    = [NSMutableString stringWithCapacity:[self length]];
108  // Make sure we don't skip whitespace, which NSScanner does by default
109  [cleanerScanner setCharactersToBeSkipped:[NSCharacterSet characterSetWithCharactersInString:@""]];
110
111  while (![cleanerScanner isAtEnd]) {
112    NSString* stringFragment;
113    if ([cleanerScanner scanUpToCharactersFromSet:characterSet intoString:&stringFragment])
114      [cleanString appendString:stringFragment];
115
116    [cleanerScanner scanCharactersFromSet:characterSet intoString:nil];
117  }
118
119  return cleanString;
120}
121
122- (NSString *)stringByReplacingCharactersInSet:(NSCharacterSet*)characterSet
123                                    withString:(NSString*)string
124{
125  NSScanner*       cleanerScanner = [NSScanner scannerWithString:self];
126  NSMutableString* cleanString    = [NSMutableString stringWithCapacity:[self length]];
127  // Make sure we don't skip whitespace, which NSScanner does by default
128  [cleanerScanner setCharactersToBeSkipped:[NSCharacterSet characterSetWithCharactersInString:@""]];
129
130  while (![cleanerScanner isAtEnd])
131  {
132    NSString* stringFragment;
133    if ([cleanerScanner scanUpToCharactersFromSet:characterSet intoString:&stringFragment])
134      [cleanString appendString:stringFragment];
135
136    if ([cleanerScanner scanCharactersFromSet:characterSet intoString:nil])
137      [cleanString appendString:string];
138  }
139
140  return cleanString;
141}
142
143- (NSString*)stringByTruncatingTo:(unsigned int)maxCharacters at:(ETruncationType)truncationType
144{
145  if ([self length] > maxCharacters)
146  {
147    NSMutableString *mutableCopy = [self mutableCopy];
148    [mutableCopy truncateTo:maxCharacters at:truncationType];
149    return [mutableCopy autorelease];
150  }
151
152  return self;
153}
154
155- (NSString *)stringByTruncatingToWidth:(float)inWidth at:(ETruncationType)truncationType
156                         withAttributes:(NSDictionary *)attributes
157{
158  if ([self sizeWithAttributes:attributes].width > inWidth)
159  {
160    NSMutableString *mutableCopy = [self mutableCopy];
161    [mutableCopy truncateToWidth:inWidth at:truncationType withAttributes:attributes];
162    return [mutableCopy autorelease];
163  }
164
165  return self;
166}
167
168- (NSString *)stringByTrimmingWhitespace
169{
170  return [self stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
171}
172
173-(NSString *)stringByRemovingAmpEscapes
174{
175  NSMutableString* dirtyStringMutant = [NSMutableString stringWithString:self];
176  [dirtyStringMutant replaceOccurrencesOfString:@"&amp;"
177                                     withString:@"&"
178                                        options:NSLiteralSearch
179                                          range:NSMakeRange(0,[dirtyStringMutant length])];
180  [dirtyStringMutant replaceOccurrencesOfString:@"&quot;"
181                                     withString:@"\""
182                                        options:NSLiteralSearch
183                                          range:NSMakeRange(0,[dirtyStringMutant length])];
184  [dirtyStringMutant replaceOccurrencesOfString:@"&lt;"
185                                     withString:@"<"
186                                        options:NSLiteralSearch
187                                          range:NSMakeRange(0,[dirtyStringMutant length])];
188  [dirtyStringMutant replaceOccurrencesOfString:@"&gt;"
189                                     withString:@">"
190                                        options:NSLiteralSearch
191                                          range:NSMakeRange(0,[dirtyStringMutant length])];
192  [dirtyStringMutant replaceOccurrencesOfString:@"&mdash;"
193                                     withString:@"-"
194                                        options:NSLiteralSearch
195                                          range:NSMakeRange(0,[dirtyStringMutant length])];
196  [dirtyStringMutant replaceOccurrencesOfString:@"&apos;"
197                                     withString:@"'"
198                                        options:NSLiteralSearch
199                                          range:NSMakeRange(0,[dirtyStringMutant length])];
200  // fix import from old Firefox versions, which exported &#39; instead of a plain apostrophe
201  [dirtyStringMutant replaceOccurrencesOfString:@"&#39;"
202                                     withString:@"'"
203                                        options:NSLiteralSearch
204                                          range:NSMakeRange(0,[dirtyStringMutant length])];
205  return [dirtyStringMutant stringByRemovingCharactersInSet:[NSCharacterSet controlCharacterSet]];
206}
207
208-(NSString *)stringByAddingAmpEscapes
209{
210  NSMutableString* dirtyStringMutant = [NSMutableString stringWithString:self];
211  [dirtyStringMutant replaceOccurrencesOfString:@"&"
212                                     withString:@"&amp;"
213                                        options:NSLiteralSearch
214                                          range:NSMakeRange(0,[dirtyStringMutant length])];
215  [dirtyStringMutant replaceOccurrencesOfString:@"\""
216                                     withString:@"&quot;"
217                                        options:NSLiteralSearch
218                                          range:NSMakeRange(0,[dirtyStringMutant length])];
219  [dirtyStringMutant replaceOccurrencesOfString:@"<"
220                                     withString:@"&lt;"
221                                        options:NSLiteralSearch
222                                          range:NSMakeRange(0,[dirtyStringMutant length])];
223  [dirtyStringMutant replaceOccurrencesOfString:@">"
224                                     withString:@"&gt;"
225                                        options:NSLiteralSearch
226                                          range:NSMakeRange(0,[dirtyStringMutant length])];
227  return [NSString stringWithString:dirtyStringMutant];
228}
229
230@end
231
232
233@implementation NSMutableString (ChimeraMutableStringUtils)
234
235- (void)truncateTo:(unsigned)maxCharacters at:(ETruncationType)truncationType
236{
237  if ([self length] <= maxCharacters)
238    return;
239
240  NSRange replaceRange;
241  replaceRange.length = [self length] - maxCharacters;
242
243  switch (truncationType) {
244    case kTruncateAtStart:
245      replaceRange.location = 0;
246      break;
247
248    case kTruncateAtMiddle:
249      replaceRange.location = maxCharacters / 2;
250      break;
251
252    case kTruncateAtEnd:
253      replaceRange.location = maxCharacters;
254      break;
255
256    default:
257#if DEBUG
258      NSLog(@"Unknown truncation type in stringByTruncatingTo::");
259#endif
260      replaceRange.location = maxCharacters;
261      break;
262  }
263
264  [self replaceCharactersInRange:replaceRange withString:[NSString ellipsisString]];
265}
266
267
268- (void)truncateToWidth:(float)maxWidth
269                     at:(ETruncationType)truncationType
270         withAttributes:(NSDictionary *)attributes
271{
272  // First check if we have to truncate at all.
273  if ([self sizeWithAttributes:attributes].width <= maxWidth)
274    return;
275
276  // Essentially, we perform a binary search on the string length
277  // which fits best into maxWidth.
278
279  float width = maxWidth;
280  int lo = 0;
281  int hi = [self length];
282  int mid;
283
284  // Make a backup copy of the string so that we can restore it if we fail low.
285  NSMutableString *backup = [self mutableCopy];
286
287  while (hi >= lo) {
288    mid = (hi + lo) / 2;
289
290    // Cut to mid chars and calculate the resulting width
291    [self truncateTo:mid at:truncationType];
292    width = [self sizeWithAttributes:attributes].width;
293
294    if (width > maxWidth) {
295      // Fail high - string is still to wide. For the next cut, we can simply
296      // work on the already cut string, so we don't restore using the backup.
297      hi = mid - 1;
298    }
299    else if (width == maxWidth) {
300      // Perfect match, abort the search.
301      break;
302    }
303    else {
304      // Fail low - we cut off too much. Restore the string before cutting again.
305      lo = mid + 1;
306      [self setString:backup];
307    }
308  }
309  // Perform the final cut (unless this was already a perfect match).
310  if (width != maxWidth)
311    [self truncateTo:hi at:truncationType];
312  [backup release];
313}
314
315@end
316
317@implementation NSString (ChimeraFilePathStringUtils)
318
319- (NSString*)volumeNamePathComponent
320{
321  // if the file doesn't exist, then componentsToDisplayForPath will return nil,
322  // so back up to the nearest existing dir
323  NSString* curPath = self;
324  while (![[NSFileManager defaultManager] fileExistsAtPath:curPath])
325  {
326    NSString* parentDirPath = [curPath stringByDeletingLastPathComponent];
327    if ([parentDirPath isEqualToString:curPath])
328      break;  // avoid endless loop
329    curPath = parentDirPath;
330  }
331
332  NSArray* displayComponents = [[NSFileManager defaultManager] componentsToDisplayForPath:curPath];
333  if ([displayComponents count] > 0)
334    return [displayComponents objectAtIndex:0];
335
336  return self;
337}
338
339- (NSString*)displayNameOfLastPathComponent
340{
341  return [[NSFileManager defaultManager] displayNameAtPath:self];
342}
343
344@end
345
346@implementation NSString (CaminoURLStringUtils)
347
348- (BOOL)isBlankURL
349{
350  return ([self isEqualToString:@"about:blank"] || [self isEqualToString:@""]);
351}
352
353// Excluded character list comes from RFC2396 and by examining Safari's behaviour
354- (NSString*)unescapedURI
355{
356  NSString *unescapedURI = (NSString*)CFURLCreateStringByReplacingPercentEscapesUsingEncoding(kCFAllocatorDefault,
357                                                                            (CFStringRef)self,
358                                                                            CFSTR(" \"\';/?:@&=+$,#"),
359                                                                            kCFStringEncodingUTF8);
360  return unescapedURI ? [unescapedURI autorelease] : self;
361}
362
363@end
364