![]() |
API 0.9.5
|
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;