- add sources.
[platform/framework/web/crosswalk.git] / src / third_party / mozilla / NSString+Utils.mm
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