[Sync] Fix invalidations on Android.
[chromium-blink-merge.git] / third_party / mozilla / NSString+Utils.mm
blob649f595d6b4ee9240fcb39d5263a28d18252728f
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 ***** */
39 #import <AppKit/AppKit.h>               // for NSStringDrawing.h
41 #import "NSString+Utils.h"
42 #include "url/gurl.h"
45 @implementation NSString (ChimeraStringUtils)
47 + (id)ellipsisString
49   static NSString* sEllipsisString = nil;
50   if (!sEllipsisString) {
51     unichar ellipsisChar = 0x2026;
52     sEllipsisString = [[NSString alloc] initWithCharacters:&ellipsisChar length:1];
53   }
55   return sEllipsisString;
58 + (NSString*)stringWithUUID
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];
69 - (BOOL)isEqualToStringIgnoringCase:(NSString*)inString
71   return ([self compare:inString options:NSCaseInsensitiveSearch] == NSOrderedSame);
74 - (BOOL)hasCaseInsensitivePrefix:(NSString*)inString
76   if ([self length] < [inString length])
77     return NO;
78   return ([self compare:inString options:NSCaseInsensitiveSearch range:NSMakeRange(0, [inString length])] == NSOrderedSame);
81 - (BOOL)isLooselyValidatedURI
83   return ([self hasCaseInsensitivePrefix:@"javascript:"] || [self hasCaseInsensitivePrefix:@"data:"]);
86 - (BOOL)isPotentiallyDangerousURI
88   return ([self hasCaseInsensitivePrefix:@"javascript:"] || [self hasCaseInsensitivePrefix:@"data:"]);
91 - (BOOL)isValidURI
93   // isValid() will only be true for valid, well-formed URI strings
94   GURL testURL([self UTF8String]);
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;
104 - (NSString *)stringByRemovingCharactersInSet:(NSCharacterSet*)characterSet
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:@""]];
111   while (![cleanerScanner isAtEnd]) {
112     NSString* stringFragment;
113     if ([cleanerScanner scanUpToCharactersFromSet:characterSet intoString:&stringFragment])
114       [cleanString appendString:stringFragment];
116     [cleanerScanner scanCharactersFromSet:characterSet intoString:nil];
117   }
119   return cleanString;
122 - (NSString *)stringByReplacingCharactersInSet:(NSCharacterSet*)characterSet
123                                     withString:(NSString*)string
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:@""]];
130   while (![cleanerScanner isAtEnd])
131   {
132     NSString* stringFragment;
133     if ([cleanerScanner scanUpToCharactersFromSet:characterSet intoString:&stringFragment])
134       [cleanString appendString:stringFragment];
136     if ([cleanerScanner scanCharactersFromSet:characterSet intoString:nil])
137       [cleanString appendString:string];
138   }
140   return cleanString;
143 - (NSString*)stringByTruncatingTo:(unsigned int)maxCharacters at:(ETruncationType)truncationType
145   if ([self length] > maxCharacters)
146   {
147     NSMutableString *mutableCopy = [self mutableCopy];
148     [mutableCopy truncateTo:maxCharacters at:truncationType];
149     return [mutableCopy autorelease];
150   }
152   return self;
155 - (NSString *)stringByTruncatingToWidth:(float)inWidth at:(ETruncationType)truncationType
156                          withAttributes:(NSDictionary *)attributes
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   }
165   return self;
168 - (NSString *)stringByTrimmingWhitespace
170   return [self stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
173 -(NSString *)stringByRemovingAmpEscapes
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]];
208 -(NSString *)stringByAddingAmpEscapes
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];
230 @end
233 @implementation NSMutableString (ChimeraMutableStringUtils)
235 - (void)truncateTo:(unsigned)maxCharacters at:(ETruncationType)truncationType
237   if ([self length] <= maxCharacters)
238     return;
240   NSRange replaceRange;
241   replaceRange.length = [self length] - maxCharacters;
243   switch (truncationType) {
244     case kTruncateAtStart:
245       replaceRange.location = 0;
246       break;
248     case kTruncateAtMiddle:
249       replaceRange.location = maxCharacters / 2;
250       break;
252     case kTruncateAtEnd:
253       replaceRange.location = maxCharacters;
254       break;
256     default:
257 #if DEBUG
258       NSLog(@"Unknown truncation type in stringByTruncatingTo::");
259 #endif
260       replaceRange.location = maxCharacters;
261       break;
262   }
264   [self replaceCharactersInRange:replaceRange withString:[NSString ellipsisString]];
268 - (void)truncateToWidth:(float)maxWidth
269                      at:(ETruncationType)truncationType
270          withAttributes:(NSDictionary *)attributes
272   // First check if we have to truncate at all.
273   if ([self sizeWithAttributes:attributes].width <= maxWidth)
274     return;
276   // Essentially, we perform a binary search on the string length
277   // which fits best into maxWidth.
279   float width = maxWidth;
280   int lo = 0;
281   int hi = [self length];
282   int mid;
284   // Make a backup copy of the string so that we can restore it if we fail low.
285   NSMutableString *backup = [self mutableCopy];
287   while (hi >= lo) {
288     mid = (hi + lo) / 2;
290     // Cut to mid chars and calculate the resulting width
291     [self truncateTo:mid at:truncationType];
292     width = [self sizeWithAttributes:attributes].width;
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];
315 @end
317 @implementation NSString (ChimeraFilePathStringUtils)
319 - (NSString*)volumeNamePathComponent
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   }
332   NSArray* displayComponents = [[NSFileManager defaultManager] componentsToDisplayForPath:curPath];
333   if ([displayComponents count] > 0)
334     return [displayComponents objectAtIndex:0];
336   return self;
339 - (NSString*)displayNameOfLastPathComponent
341   return [[NSFileManager defaultManager] displayNameAtPath:self];
344 @end
346 @implementation NSString (CaminoURLStringUtils)
348 - (BOOL)isBlankURL
350   return ([self isEqualToString:@"about:blank"] || [self isEqualToString:@""]);
353 // Excluded character list comes from RFC2396 and by examining Safari's behaviour
354 - (NSString*)unescapedURI
356   NSString *unescapedURI = (NSString*)CFURLCreateStringByReplacingPercentEscapesUsingEncoding(kCFAllocatorDefault,
357                                                                             (CFStringRef)self,
358                                                                             CFSTR(" \"\';/?:@&=+$,#"),
359                                                                             kCFStringEncodingUTF8);
360   return unescapedURI ? [unescapedURI autorelease] : self;
363 @end