00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023 @import "CPObject.j"
00024 @import "CPException.j"
00025 @import "CPSortDescriptor.j"
00026
00027
00033 CPCaseInsensitiveSearch = 1;
00039 CPLiteralSearch = 2;
00045 CPBackwardsSearch = 4;
00050 CPAnchoredSearch = 8;
00056 CPNumericSearch = 64;
00057
00058 var CPStringHashes = new objj_dictionary();
00059
00069 @implementation CPString : CPObject
00070
00071
00072
00073
00074 + (id)alloc
00075 {
00076 return new String;
00077 }
00078
00082 + (id)string
00083 {
00084 return [[self alloc] init];
00085 }
00086
00091 + (id)stringWithHash:(unsigned)aHash
00092 {
00093 return sprintf("%06x", aHash);
00094 }
00095
00102 + (id)stringWithString:(CPString)aString
00103 {
00104 if (!aString)
00105 [CPException raise:CPInvalidArgumentException
00106 reason:"stringWithString: the string can't be 'nil'"];
00107
00108 return [[self alloc] initWithString:aString];
00109 }
00110
00116 - (id)initWithString:(CPString)aString
00117 {
00118 return String(aString);
00119 }
00120
00126 - (id)initWithFormat:(CPString)format, ...
00127 {
00128 if (!format)
00129 [CPException raise:CPInvalidArgumentException
00130 reason:"initWithFormat: the format can't be 'nil'"];
00131
00132 self = sprintf.apply(this, Array.prototype.slice.call(arguments, 2));
00133 return self;
00134 }
00135
00142 + (id)stringWithFormat:(CPString)format, ...
00143 {
00144 if (!format)
00145 [CPException raise:CPInvalidArgumentException
00146 reason:"initWithFormat: the format can't be 'nil'"];
00147
00148 return sprintf.apply(this, Array.prototype.slice.call(arguments, 2));
00149 }
00150
00154 - (CPString)description
00155 {
00156 return "<" + self.isa.name + " 0x" + [CPString stringWithHash:[self hash]] + " \"" + self + "\">";
00157 }
00158
00162 - (int)length
00163 {
00164 return length;
00165 }
00166
00171 - (CPString)characterAtIndex:(unsigned)anIndex
00172 {
00173 return charAt(anIndex);
00174 }
00175
00176
00177
00184 - (CPString)stringByAppendingFormat:(CPString)format, ...
00185 {
00186 if (!format)
00187 [CPException raise:CPInvalidArgumentException reason:"initWithFormat: the format can't be 'nil'"];
00188
00189 return self + sprintf.apply(this, Array.prototype.slice.call(arguments, 2));
00190 }
00191
00197 - (CPString)stringByAppendingString:(CPString)aString
00198 {
00199 return self + aString;
00200 }
00201
00214 - (CPString)stringByPaddingToLength:(unsigned)aLength withString:(CPString)aString startingAtIndex:(unsigned)anIndex
00215 {
00216 if (length == aLength)
00217 return self;
00218
00219 if (aLength < length)
00220 return substr(0, aLength);
00221
00222 var string = self,
00223 substring = aString.substr(anIndex),
00224 difference = aLength - length;
00225
00226 while ((difference -= substring.length) > 0)
00227 string += substring;
00228
00229 if (difference) string += substring.substr(difference + substring.length);
00230 }
00231
00232
00244 - (CPArray)componentsSeparatedByString:(CPString)aString
00245 {
00246 return split(aString);
00247 }
00248
00254 - (CPString)substringFromIndex:(unsigned)anIndex
00255 {
00256 return substr(anIndex);
00257 }
00258
00264 - (CPString)substringWithRange:(CPRange)aRange
00265 {
00266 return substr(aRange.location, aRange.length);
00267 }
00268
00274 - (CPString)substringToIndex:(unsigned)anIndex
00275 {
00276 return substring(0, anIndex);
00277 }
00278
00279
00280
00287 - (CPRange)rangeOfString:(CPString)aString
00288 {
00289 return [self rangeOfString:aString options:0];
00290 }
00291
00309 - (CPRange)rangeOfString:(CPString)aString options:(int)aMask
00310 {
00311 return [self rangeOfString:aString options:aMask range:nil];
00312 }
00313
00332 - (CPRange)rangeOfString:(CPString)aString options:(int)aMask range:(CPrange)aRange
00333 {
00334 var string = (aRange == nil) ? self : [self substringWithRange:aRange],
00335 location = CPNotFound;
00336
00337 if (aMask & CPCaseInsensitiveSearch)
00338 {
00339 string = string.toLowerCase();
00340 aString = aString.toLowerCase();
00341 }
00342
00343 if (aMask & CPBackwardsSearch)
00344 location = string.lastIndexOf(aString, aMask & CPAnchoredSearch ? length - aString.length : 0);
00345 else if (aMask & CPAnchoredSearch)
00346 location = string.substr(0, aString.length).indexOf(aString) != CPNotFound ? 0 : CPNotFound;
00347 else
00348 location = string.indexOf(aString);
00349
00350 return CPMakeRange(location, location == CPNotFound ? 0 : aString.length);
00351 }
00352
00353
00354
00362 - (CPString)stringByReplacingOccurrencesOfString:(CPString)target withString:(CPString)replacement
00363 {
00364 return self.replace(new RegExp(target, "g"), replacement);
00365 }
00366
00367
00368
00369
00370
00371
00372
00373
00374
00375
00376 - (CPString)stringByReplacingOccurrencesOfString:(CPString)target withString:(CPString)replacement options:(int)options range:(CPRange)searchRange
00377 {
00378 var start = substring(0, searchRange.location),
00379 stringSegmentToSearch = substr(searchRange.location, searchRange.length),
00380 end = substring(searchRange.location + searchRange.length, self.length),
00381 regExp;
00382
00383 if (options & CPCaseInsensitiveSearch)
00384 regExp = new RegExp(target, "gi");
00385 else
00386 regExp = new RegExp(target, "g");
00387
00388 return start + '' + stringSegmentToSearch.replace(regExp, replacement) + '' + end;
00389 }
00390
00391
00392
00393
00394
00395
00396
00397
00398 - (CPString)stringByReplacingCharactersInRange:(CPRange)range withString:(CPString)replacement
00399 {
00400 return '' + substring(0, range.location) + replacement + substring(range.location + range.length, self.length);
00401 }
00402
00403
00404
00405
00411 - (CPComparisonResult)compare:(CPString)aString
00412 {
00413 return [self compare:aString options:nil];
00414 }
00415
00416
00417
00418
00419
00420
00421
00422 - (CPComparisonResult)caseInsensitiveCompare:(CPString)aString
00423 {
00424 return [self compare:aString options:CPCaseInsensitiveSearch];
00425 }
00426
00433 - (CPComparisonResult)compare:(CPString)aString options:(int)aMask
00434 {
00435 var lhs = self,
00436 rhs = aString;
00437
00438 if (aMask & CPCaseInsensitiveSearch)
00439 {
00440 lhs = lhs.toLowerCase();
00441 rhs = rhs.toLowerCase();
00442 }
00443
00444 if (lhs < rhs)
00445 return CPOrderedAscending;
00446 else if (lhs > rhs)
00447 return CPOrderedDescending;
00448
00449 return CPOrderedSame;
00450 }
00451
00459 - (CPComparisonResult)compare:(CPString)aString options:(int)aMask range:(CPRange)range
00460 {
00461 var lhs = [self substringWithRange:range],
00462 rhs = aString;
00463
00464 return [lhs compare:rhs options:aMask];
00465 }
00466
00472 - (BOOL)hasPrefix:(CPString)aString
00473 {
00474 return aString && aString != "" && indexOf(aString) == 0;
00475 }
00476
00482 - (BOOL)hasSuffix:(CPString)aString
00483 {
00484 return aString && aString != "" && lastIndexOf(aString) == (length - aString.length);
00485 }
00486
00490 - (BOOL)isEqualToString:(CPString)aString
00491 {
00492 return self == aString;
00493 }
00494
00498 - (unsigned)hash
00499 {
00500 var hash = dictionary_getValue(CPStringHashes, self);
00501
00502 if (!hash)
00503 {
00504 hash = _objj_generateObjectHash();
00505 dictionary_setValue(CPStringHashes, self, hash);
00506 }
00507
00508 return hash;
00509 }
00510
00514 - (CPString)capitalizedString
00515 {
00516 var parts = self.split(/\b/g);
00517 for (var i = 0; i < parts.length; i++)
00518 {
00519 if (i == 0 || (/\s$/).test(parts[i-1]))
00520 parts[i] = parts[i].substring(0, 1).toUpperCase() + parts[i].substring(1).toLowerCase();
00521 else
00522 parts[i] = parts[i].toLowerCase();
00523 }
00524 return parts.join("");
00525 }
00526
00530 - (CPString)lowercaseString
00531 {
00532 return toLowerCase();
00533 }
00534
00538 - (CPString)uppercaseString
00539 {
00540 return toUpperCase();
00541 }
00542
00546 - (double)doubleValue
00547 {
00548 return parseFloat(self, 10);
00549 }
00556 - (BOOL)boolValue
00557 {
00558 var replaceRegExp = new RegExp("^\\s*[\\+,\\-]*0*");
00559 return RegExp("^[Y,y,t,T,1-9]").test(self.replace(replaceRegExp, ''));
00560 }
00561
00565 - (float)floatValue
00566 {
00567 return parseFloat(self, 10);
00568 }
00569
00573 - (int)intValue
00574 {
00575 return parseInt(self, 10);
00576 }
00577
00583 - (CPArray)pathComponents
00584 {
00585 return split('/');
00586 }
00587
00593 - (CPString)pathExtension
00594 {
00595 return substr(lastIndexOf('.') + 1);
00596 }
00597
00603 - (CPString)lastPathComponent
00604 {
00605 var components = [self pathComponents];
00606 return components[components.length -1];
00607 }
00608
00614 - (CPString)stringByDeletingLastPathComponent
00615 {
00616 var path = self,
00617 start = length - 1;
00618
00619 while (path.charAt(start) === '/')
00620 start--;
00621
00622 path = path.substr(0, path.lastIndexOf('/', start));
00623
00624 if (path === "" && charAt(0) === '/')
00625 return '/';
00626
00627 return path;
00628 }
00629
00630 - (CPString)stringByStandardizingPath
00631 {
00632 return objj_standardize_path(self);
00633 }
00634
00635 - (CPString)copy
00636 {
00637 return new String(self);
00638 }
00639
00640 @end
00641
00642
00643 String.prototype.isa = CPString;
00644
00645
00646
00647
00648 var sprintfFormatRegex = new RegExp("([^%]+|%[\\+\\-\\ \\#0]*[0-9\\*]*(.[0-9\\*]+)?[hlL]?[cbBdieEfgGosuxXpn%@])", "g");
00649 var sprintfTagRegex = new RegExp("(%)([\\+\\-\\ \\#0]*)([0-9\\*]*)((.[0-9\\*]+)?)([hlL]?)([cbBdieEfgGosuxXpn%@])");
00650
00656 function sprintf(format)
00657 {
00658 var format = arguments[0],
00659 tokens = format.match(sprintfFormatRegex),
00660 index = 0,
00661 result = "",
00662 arg = 1;
00663
00664 for (var i = 0; i < tokens.length; i++)
00665 {
00666 var t = tokens[i];
00667 if (format.substring(index, index + t.length) != t)
00668 {
00669 return result;
00670 }
00671 index += t.length;
00672
00673 if (t.charAt(0) != "%")
00674 {
00675 result += t;
00676 }
00677 else
00678 {
00679 var subtokens = t.match(sprintfTagRegex);
00680 if (subtokens.length != 8 || subtokens[0] != t)
00681 {
00682 return result;
00683 }
00684
00685 var percentSign = subtokens[1],
00686 flags = subtokens[2],
00687 widthString = subtokens[3],
00688 precisionString = subtokens[4],
00689 length = subtokens[6],
00690 specifier = subtokens[7];
00691
00692 var width = null;
00693 if (widthString == "*")
00694 width = arguments[arg++];
00695 else if (widthString != "")
00696 width = Number(widthString);
00697
00698 var precision = null;
00699 if (precisionString == ".*")
00700 precision = arguments[arg++];
00701 else if (precisionString != "")
00702 precision = Number(precisionString.substring(1));
00703
00704 var leftJustify = (flags.indexOf("-") >= 0);
00705 var padZeros = (flags.indexOf("0") >= 0);
00706
00707 var subresult = "";
00708
00709 if (RegExp("[bBdiufeExXo]").test(specifier))
00710 {
00711 var num = Number(arguments[arg++]);
00712
00713 var sign = "";
00714 if (num < 0)
00715 {
00716 sign = "-";
00717 }
00718 else
00719 {
00720 if (flags.indexOf("+") >= 0)
00721 sign = "+";
00722 else if (flags.indexOf(" ") >= 0)
00723 sign = " ";
00724 }
00725
00726 if (specifier == "d" || specifier == "i" || specifier == "u")
00727 {
00728 var number = String(Math.abs(Math.floor(num)));
00729
00730 subresult = sprintf_justify(sign, "", number, "", width, leftJustify, padZeros)
00731 }
00732
00733 if (specifier == "f")
00734 {
00735 var number = String((precision != null) ? Math.abs(num).toFixed(precision) : Math.abs(num));
00736 var suffix = (flags.indexOf("#") >= 0 && number.indexOf(".") < 0) ? "." : "";
00737
00738 subresult = sprintf_justify(sign, "", number, suffix, width, leftJustify, padZeros);
00739 }
00740
00741 if (specifier == "e" || specifier == "E")
00742 {
00743 var number = String(Math.abs(num).toExponential(precision != null ? precision : 21));
00744 var suffix = (flags.indexOf("#") >= 0 && number.indexOf(".") < 0) ? "." : "";
00745
00746 subresult = sprintf_justify(sign, "", number, suffix, width, leftJustify, padZeros);
00747 }
00748
00749 if (specifier == "x" || specifier == "X")
00750 {
00751 var number = String(Math.abs(num).toString(16));
00752 var prefix = (flags.indexOf("#") >= 0 && num != 0) ? "0x" : "";
00753
00754 subresult = sprintf_justify(sign, prefix, number, "", width, leftJustify, padZeros);
00755 }
00756
00757 if (specifier == "b" || specifier == "B")
00758 {
00759 var number = String(Math.abs(num).toString(2));
00760 var prefix = (flags.indexOf("#") >= 0 && num != 0) ? "0b" : "";
00761
00762 subresult = sprintf_justify(sign, prefix, number, "", width, leftJustify, padZeros);
00763 }
00764
00765 if (specifier == "o")
00766 {
00767 var number = String(Math.abs(num).toString(8));
00768 var prefix = (flags.indexOf("#") >= 0 && num != 0) ? "0" : "";
00769
00770 subresult = sprintf_justify(sign, prefix, number, "", width, leftJustify, padZeros);
00771 }
00772
00773 if (RegExp("[A-Z]").test(specifier))
00774 subresult = subresult.toUpperCase();
00775 else
00776 subresult = subresult.toLowerCase();
00777 }
00778 else
00779 {
00780 var subresult = "";
00781
00782 if (specifier == "%")
00783 subresult = "%";
00784 else if (specifier == "c")
00785 subresult = String(arguments[arg++]).charAt(0);
00786 else if (specifier == "s" || specifier == "@")
00787 subresult = String(arguments[arg++]);
00788 else if (specifier == "p" || specifier == "n")
00789 {
00790 arg++;
00791 subresult = "";
00792 }
00793
00794 subresult = sprintf_justify("", "", subresult, "", width, leftJustify, false);
00795 }
00796
00797 result += subresult;
00798 }
00799 }
00800 return result;
00801 }
00802
00803 var sprintf_justify = function(sign, prefix, string, suffix, width, leftJustify, padZeros)
00804 {
00805 var length = (sign.length + prefix.length + string.length + suffix.length);
00806 if (leftJustify)
00807 {
00808 return sign + prefix + string + suffix + sprintf_pad(width - length, " ");
00809 }
00810 else
00811 {
00812 if (padZeros)
00813 return sign + prefix + sprintf_pad(width - length, "0") + string + suffix;
00814 else
00815 return sprintf_pad(width - length, " ") + sign + prefix + string + suffix;
00816 }
00817 }
00818
00819 var sprintf_pad = function(n, ch)
00820 {
00821 var result = "";
00822 for (var i = 0; i < n; i++)
00823 result += ch;
00824 return result;
00825 }