API 0.9.5
Foundation/CPString.j
Go to the documentation of this file.
00001 /*
00002  * CPString.j
00003  * Foundation
00004  *
00005  * Created by Francisco Tolmasky.
00006  * Copyright 2008, 280 North, Inc.
00007  *
00008  * This library is free software; you can redistribute it and/or
00009  * modify it under the terms of the GNU Lesser General Public
00010  * License as published by the Free Software Foundation; either
00011  * version 2.1 of the License, or (at your option) any later version.
00012  *
00013  * This library is distributed in the hope that it will be useful,
00014  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00015  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
00016  * Lesser General Public License for more details.
00017  *
00018  * You should have received a copy of the GNU Lesser General Public
00019  * License along with this library; if not, write to the Free Software
00020  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
00021  */
00022 
00023 
00024 #define _CPMaxRange(aRange) ((aRange).location + (aRange).length)
00025 
00031 CPCaseInsensitiveSearch = 1;
00037 CPLiteralSearch         = 2;
00043 CPBackwardsSearch       = 4;
00048 CPAnchoredSearch        = 8;
00054 CPNumericSearch         = 64;
00060 CPDiacriticInsensitiveSearch = 128;
00061 
00062 var CPStringUIDs        = new CFMutableDictionary();
00063 
00064 var CPStringRegexSpecialCharacters = [
00065       '/', '.', '*', '+', '?', '|', '$', '^',
00066       '(', ')', '[', ']', '{', '}', '\\'
00067     ],
00068     CPStringRegexEscapeExpression = new RegExp("(\\" + CPStringRegexSpecialCharacters.join("|\\") + ")", 'g'),
00069     CPStringRegexTrimWhitespace = new RegExp("(^\\s+|\\s+$)", 'g');
00070 
00084 @implementation CPString : CPObject
00085 {
00086     id __doxygen__;
00087 }
00088 
00089 /*
00090     @ignore
00091 */
00092 + (id)alloc
00093 {
00094     if ([self class] !== CPString)
00095        return [super alloc];
00096 
00097     return new String;
00098 }
00099 
00103 + (id)string
00104 {
00105     return [[self alloc] init];
00106 }
00107 
00112 + (id)stringWithHash:(unsigned)aHash
00113 {
00114     var hashString = parseInt(aHash, 10).toString(16);
00115     return "000000".substring(0, MAX(6 - hashString.length, 0)) + hashString;
00116 }
00117 
00124 + (id)stringWithString:(CPString)aString
00125 {
00126     if (!aString)
00127         [CPException raise:CPInvalidArgumentException
00128                     reason:"stringWithString: the string can't be 'nil'"];
00129 
00130     return [[self alloc] initWithString:aString];
00131 }
00132 
00138 - (id)initWithString:(CPString)aString
00139 {
00140     if ([self class] === CPString)
00141         return String(aString);
00142 
00143     var result = new String(aString);
00144 
00145     result.isa = [self class];
00146 
00147     return result;
00148 }
00149 
00155 - (id)initWithFormat:(CPString)format, ...
00156 {
00157     if (!format)
00158         [CPException raise:CPInvalidArgumentException
00159                     reason:"initWithFormat: the format can't be 'nil'"];
00160 
00161     self = ObjectiveJ.sprintf.apply(this, Array.prototype.slice.call(arguments, 2));
00162     return self;
00163 }
00164 
00171 + (id)stringWithFormat:(CPString)format, ...
00172 {
00173     if (!format)
00174         [CPException raise:CPInvalidArgumentException
00175                     reason:"initWithFormat: the format can't be 'nil'"];
00176 
00177     return ObjectiveJ.sprintf.apply(this, Array.prototype.slice.call(arguments, 2));
00178 }
00179 
00183 - (CPString)description
00184 {
00185     return self;
00186 }
00187 
00191 - (int)length
00192 {
00193     return length;
00194 }
00195 
00200 - (CPString)characterAtIndex:(unsigned)anIndex
00201 {
00202     return charAt(anIndex);
00203 }
00204 
00205 // Combining strings
00206 
00213 - (CPString)stringByAppendingFormat:(CPString)format, ...
00214 {
00215     if (!format)
00216         [CPException raise:CPInvalidArgumentException reason:"initWithFormat: the format can't be 'nil'"];
00217 
00218     return self + ObjectiveJ.sprintf.apply(this, Array.prototype.slice.call(arguments, 2));
00219 }
00220 
00226 - (CPString)stringByAppendingString:(CPString)aString
00227 {
00228     return self + aString;
00229 }
00230 
00243 - (CPString)stringByPaddingToLength:(unsigned)aLength withString:(CPString)aString startingAtIndex:(unsigned)anIndex
00244 {
00245     if (length == aLength)
00246         return self;
00247 
00248     if (aLength < length)
00249         return substr(0, aLength);
00250 
00251     var string = self,
00252         substring = aString.substring(anIndex),
00253         difference = aLength - length;
00254 
00255     while ((difference -= substring.length) >= 0)
00256         string += substring;
00257 
00258     if (-difference < substring.length)
00259         string += substring.substring(0, -difference);
00260 
00261     return string;
00262 }
00263 
00264 //Dividing Strings
00276 - (CPArray)componentsSeparatedByString:(CPString)aString
00277 {
00278     return split(aString);
00279 }
00280 
00286 - (CPString)substringFromIndex:(unsigned)anIndex
00287 {
00288     return substr(anIndex);
00289 }
00290 
00296 - (CPString)substringWithRange:(CPRange)aRange
00297 {
00298     if (aRange.location < 0 || _CPMaxRange(aRange) > length)
00299         [CPException raise:CPRangeException reason:"aRange out of bounds"];
00300 
00301     return substr(aRange.location, aRange.length);
00302 }
00303 
00311 - (CPString)substringToIndex:(unsigned)anIndex
00312 {
00313     if (anIndex > length)
00314         [CPException raise:CPRangeException reason:"index out of bounds"];
00315 
00316     return substring(0, anIndex);
00317 }
00318 
00319 // Finding characters and substrings
00320 
00327 - (CPRange)rangeOfString:(CPString)aString
00328 {
00329    return [self rangeOfString:aString options:0];
00330 }
00331 
00349 - (CPRange)rangeOfString:(CPString)aString options:(int)aMask
00350 {
00351     return [self rangeOfString:aString options:aMask range:nil];
00352 }
00353 
00373 - (CPRange)rangeOfString:(CPString)aString options:(int)aMask range:(CPrange)aRange
00374 {
00375     // Searching for @"" always returns CPNotFound.
00376     if (!aString)
00377         return CPMakeRange(CPNotFound, 0);
00378 
00379     var string = (aRange == nil) ? self : [self substringWithRange:aRange],
00380         location = CPNotFound;
00381 
00382     if (aMask & CPCaseInsensitiveSearch)
00383     {
00384         string = string.toLowerCase();
00385         aString = aString.toLowerCase();
00386     }
00387 
00388     if (aMask & CPBackwardsSearch)
00389     {
00390         location = string.lastIndexOf(aString);
00391         if (aMask & CPAnchoredSearch && location + aString.length != string.length)
00392             location = CPNotFound;
00393     }
00394     else if (aMask & CPAnchoredSearch)
00395         location = string.substr(0, aString.length).indexOf(aString) != CPNotFound ? 0 : CPNotFound;
00396     else
00397         location = string.indexOf(aString);
00398 
00399     if (location == CPNotFound)
00400         return CPMakeRange(CPNotFound, 0);
00401 
00402     return CPMakeRange(location + (aRange ? aRange.location : 0), aString.length);
00403 }
00404 
00405 //Replacing Substrings
00406 
00407 - (CPString)stringByEscapingRegexControlCharacters
00408 {
00409     return self.replace(CPStringRegexEscapeExpression, "\\$1");
00410 }
00411 
00419 - (CPString)stringByReplacingOccurrencesOfString:(CPString)target withString:(CPString)replacement
00420 {
00421     return self.replace(new RegExp([target stringByEscapingRegexControlCharacters], "g"), replacement);
00422 }
00423 
00424 /*
00425     Returns a new string in which all occurrences of a target string in a specified range of the receiver
00426     are replaced by another given string.
00427     @param target The string to replace
00428     @param replacement the string with which to replace the \c target.
00429     @param options A mask of options to use when comparing \c target with the receiver. Pass 0 to specify no options
00430     @param searchRange The range in the receiver in which to search for \c target.
00431 */
00432 
00433 - (CPString)stringByReplacingOccurrencesOfString:(CPString)target withString:(CPString)replacement options:(int)options range:(CPRange)searchRange
00434 {
00435     var start = substring(0, searchRange.location),
00436         stringSegmentToSearch = substr(searchRange.location, searchRange.length),
00437         end = substring(searchRange.location + searchRange.length, self.length),
00438         target = [target stringByEscapingRegexControlCharacters],
00439         regExp;
00440 
00441     if (options & CPCaseInsensitiveSearch)
00442         regExp = new RegExp(target, "gi");
00443     else
00444         regExp = new RegExp(target, "g");
00445 
00446     return start + '' + stringSegmentToSearch.replace(regExp, replacement) + '' + end;
00447 }
00448 
00449 /*
00450    Returns a new string in which the characters in a specified range of the receiver
00451    are replaced by a given string.
00452    @param range A range of characters in the receiver.
00453    @param replacement The string with which to replace the characters in \c range.
00454 */
00455 
00456 - (CPString)stringByReplacingCharactersInRange:(CPRange)range withString:(CPString)replacement
00457 {
00458     return '' + substring(0, range.location) + replacement + substring(range.location + range.length, self.length);
00459 }
00460 
00464 - (CPString)stringByTrimmingWhitespace
00465 {
00466     return self.replace(CPStringRegexTrimWhitespace, "");
00467 }
00468 
00469 // Identifying and comparing strings
00470 
00476 - (CPComparisonResult)compare:(CPString)aString
00477 {
00478     return [self compare:aString options:nil];
00479 }
00480 
00481 /*
00482     Compares the receiver to the specified string.
00483     @param aString the string with which to compare
00484     @return the result of the comparison
00485 */
00486 - (CPComparisonResult)caseInsensitiveCompare:(CPString)aString
00487 {
00488     return [self compare:aString options:CPCaseInsensitiveSearch];
00489 }
00490 
00497 - (CPComparisonResult)compare:(CPString)aString options:(int)aMask
00498 {
00499     var lhs = self,
00500         rhs = aString;
00501 
00502     if (aMask & CPCaseInsensitiveSearch)
00503     {
00504         lhs = lhs.toLowerCase();
00505         rhs = rhs.toLowerCase();
00506     }
00507 
00508     if (aMask & CPDiacriticInsensitiveSearch)
00509     {
00510         lhs = lhs.stripDiacritics();
00511         rhs = rhs.stripDiacritics();
00512     }
00513 
00514     if (lhs < rhs)
00515         return CPOrderedAscending;
00516 
00517     if (lhs > rhs)
00518         return CPOrderedDescending;
00519 
00520     return CPOrderedSame;
00521 }
00522 
00530 - (CPComparisonResult)compare:(CPString)aString options:(int)aMask range:(CPRange)range
00531 {
00532     var lhs = [self substringWithRange:range],
00533         rhs = aString;
00534 
00535     return [lhs compare:rhs options:aMask];
00536 }
00537 
00543 - (BOOL)hasPrefix:(CPString)aString
00544 {
00545     return aString && aString != "" && indexOf(aString) == 0;
00546 }
00547 
00553 - (BOOL)hasSuffix:(CPString)aString
00554 {
00555     return aString && aString != "" && length >= aString.length && lastIndexOf(aString) == (length - aString.length);
00556 }
00557 
00558 - (BOOL)isEqual:(id)anObject
00559 {
00560     if (self === anObject)
00561         return YES;
00562 
00563     if (!anObject || ![anObject isKindOfClass:[CPString class]])
00564         return NO;
00565 
00566     return [self isEqualToString:anObject];
00567 }
00568 
00569 
00573 - (BOOL)isEqualToString:(CPString)aString
00574 {
00575     return self == String(aString);
00576 }
00577 
00581 - (unsigned)UID
00582 {
00583     var UID = CPStringUIDs.valueForKey(self);
00584 
00585     if (!UID)
00586     {
00587         UID = objj_generateObjectUID();
00588         CPStringUIDs.setValueForKey(self, UID);
00589     }
00590 
00591     return UID + "";
00592 }
00593 
00599 - (CPString)commonPrefixWithString:(CPString)aString
00600 {
00601     return [self commonPrefixWithString: aString options: 0];
00602 }
00603 
00610 - (CPString)commonPrefixWithString:(CPString)aString options:(int)aMask
00611 {
00612     var len = 0, // length of common prefix
00613         lhs = self,
00614         rhs = aString,
00615         min = MIN([lhs length], [rhs length]);
00616 
00617     if (aMask & CPCaseInsensitiveSearch)
00618     {
00619         lhs = [lhs lowercaseString];
00620         rhs = [rhs lowercaseString];
00621     }
00622 
00623     for (; len < min; len++ )
00624     {
00625         if ( [lhs characterAtIndex:len] !== [rhs characterAtIndex:len] )
00626             break;
00627     }
00628 
00629     return [self substringToIndex:len];
00630 }
00631 
00635 - (CPString)capitalizedString
00636 {
00637     var parts = self.split(/\b/g), // split on word boundaries
00638         i = 0,
00639         count = parts.length;
00640 
00641     for (; i < count; i++)
00642     {
00643         if (i == 0 || (/\s$/).test(parts[i - 1])) // only capitalize if previous token was whitespace
00644             parts[i] = parts[i].substring(0, 1).toUpperCase() + parts[i].substring(1).toLowerCase();
00645         else
00646             parts[i] = parts[i].toLowerCase();
00647     }
00648     return parts.join("");
00649 }
00650 
00654 - (CPString)lowercaseString
00655 {
00656     return toLowerCase();
00657 }
00658 
00662 - (CPString)uppercaseString
00663 {
00664     return toUpperCase();
00665 }
00666 
00670 - (double)doubleValue
00671 {
00672     return parseFloat(self, 10);
00673 }
00679 - (BOOL)boolValue
00680 {
00681     var replaceRegExp = new RegExp("^\\s*[\\+,\\-]?0*");
00682     return RegExp("^[Y,y,t,T,1-9]").test(self.replace(replaceRegExp, ''));
00683 }
00684 
00688 - (float)floatValue
00689 {
00690     return parseFloat(self, 10);
00691 }
00692 
00696 - (int)intValue
00697 {
00698     return parseInt(self, 10);
00699 }
00700 
00706 - (CPArray)pathComponents
00707 {
00708     var result = split('/');
00709     if (result[0] === "")
00710         result[0] = "/";
00711     if (result[result.length - 1] === "")
00712         result.pop();
00713     return result;
00714 }
00715 
00721 - (CPString)pathExtension
00722 {
00723     if (lastIndexOf('.') === CPNotFound)
00724         return "";
00725 
00726     return substr(lastIndexOf('.') + 1);
00727 }
00728 
00734 - (CPString)lastPathComponent
00735 {
00736     var components = [self pathComponents];
00737     return components[components.length - 1];
00738 }
00739 
00745 - (CPString)stringByDeletingLastPathComponent
00746 {
00747     var path = self,
00748         start = length - 1;
00749 
00750     while (path.charAt(start) === '/')
00751         start--;
00752 
00753     path = path.substr(0, path.lastIndexOf('/', start));
00754 
00755     if (path === "" && charAt(0) === '/')
00756         return '/';
00757 
00758     return path;
00759 }
00760 
00764 - (CPString)stringByDeletingPathExtension
00765 {
00766     var extension = [self pathExtension];
00767     if (extension === "")
00768         return self;
00769 
00770     if (lastIndexOf('.') < 1)
00771         return self;
00772 
00773     return substr(0, [self length] - (extension.length + 1));
00774 }
00775 
00776 - (CPString)stringByStandardizingPath
00777 {
00778     // FIXME: Expand tildes etc. in CommonJS?
00779     return [[CPURL URLWithString:self] absoluteString];
00780 }
00781 
00782 @end
00783 
00784 @implementation CPString (JSON)
00785 
00789 + (CPString)JSONFromObject:(JSObject)anObject
00790 {
00791     return JSON.stringify(anObject);
00792 }
00793 
00797 - (JSObject)objectFromJSON
00798 {
00799     return JSON.parse(self);
00800 }
00801 
00802 @end
00803 
00804 
00805 @implementation CPString (UUID)
00806 
00810 + (CPString)UUID
00811 {
00812     var g = @"",
00813         i = 0;
00814 
00815     for (; i < 32; i++)
00816         g += FLOOR(RAND() * 0xF).toString(0xF);
00817 
00818     return g;
00819 }
00820 
00821 @end
00822 
00823 var diacritics = [[192,198],[224,230],[231,231],[232,235],[236,239],[242,246],[249,252]], // Basic Latin ; Latin-1 Supplement.
00824     normalized = [65,97,99,101,105,111,117];
00825 
00826 String.prototype.stripDiacritics = function()
00827 {
00828     var output = "";
00829     for (var indexSource = 0; indexSource < this.length; indexSource++)
00830     {
00831         var code = this.charCodeAt(indexSource);
00832 
00833         for (var i = 0; i < diacritics.length; i++)
00834         {
00835             var drange = diacritics[i];
00836 
00837             if (code >= drange[0] && code <= drange[drange.length - 1])
00838             {
00839                 code = normalized[i];
00840                 break;
00841             }
00842         }
00843 
00844         output += String.fromCharCode(code);
00845     }
00846 
00847     return output;
00848 }
00849 
00850 String.prototype.isa = CPString;
 All Classes Files Functions Variables Defines