1// Copyright 2006-2008 the V8 project authors. All rights reserved. 2// Redistribution and use in source and binary forms, with or without 3// modification, are permitted provided that the following conditions are 4// met: 5// 6// * Redistributions of source code must retain the above copyright 7// notice, this list of conditions and the following disclaimer. 8// * Redistributions in binary form must reproduce the above 9// copyright notice, this list of conditions and the following 10// disclaimer in the documentation and/or other materials provided 11// with the distribution. 12// * Neither the name of Google Inc. nor the names of its 13// contributors may be used to endorse or promote products derived 14// from this software without specific prior written permission. 15// 16// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 20// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 21// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 22// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 28 29// This file relies on the fact that the following declaration has been made 30// in runtime.js: 31// const $String = global.String; 32// const $NaN = 0/0; 33 34 35// Set the String function and constructor. 36%SetCode($String, function(x) { 37 var value = %_ArgumentsLength() == 0 ? '' : ToString(x); 38 if (%_IsConstructCall()) { 39 %_SetValueOf(this, value); 40 } else { 41 return value; 42 } 43}); 44 45%FunctionSetPrototype($String, new $String()); 46 47// ECMA-262 section 15.5.4.2 48function StringToString() { 49 if (!IS_STRING(this) && !IS_STRING_WRAPPER(this)) 50 throw new $TypeError('String.prototype.toString is not generic'); 51 return %_ValueOf(this); 52} 53 54 55// ECMA-262 section 15.5.4.3 56function StringValueOf() { 57 if (!IS_STRING(this) && !IS_STRING_WRAPPER(this)) 58 throw new $TypeError('String.prototype.valueOf is not generic'); 59 return %_ValueOf(this); 60} 61 62 63// ECMA-262, section 15.5.4.4 64function StringCharAt(pos) { 65 var char_code = %_FastCharCodeAt(this, index); 66 if (!%_IsSmi(char_code)) { 67 var subject = ToString(this); 68 var index = TO_INTEGER(pos); 69 if (index >= subject.length || index < 0) return ""; 70 char_code = %StringCharCodeAt(subject, index); 71 } 72 return %CharFromCode(char_code); 73} 74 75 76// ECMA-262 section 15.5.4.5 77function StringCharCodeAt(pos) { 78 var fast_answer = %_FastCharCodeAt(this, pos); 79 if (%_IsSmi(fast_answer)) { 80 return fast_answer; 81 } 82 var subject = ToString(this); 83 var index = TO_INTEGER(pos); 84 return %StringCharCodeAt(subject, index); 85} 86 87 88// ECMA-262, section 15.5.4.6 89function StringConcat() { 90 var len = %_ArgumentsLength(); 91 var parts = new $Array(len + 1); 92 parts[0] = ToString(this); 93 for (var i = 0; i < len; i++) 94 parts[i + 1] = ToString(%_Arguments(i)); 95 return parts.join(''); 96} 97 98// Match ES3 and Safari 99%FunctionSetLength(StringConcat, 1); 100 101 102// ECMA-262 section 15.5.4.7 103function StringIndexOf(searchString /* position */) { // length == 1 104 var subject_str = ToString(this); 105 var pattern_str = ToString(searchString); 106 var subject_str_len = subject_str.length; 107 var pattern_str_len = pattern_str.length; 108 var index = 0; 109 if (%_ArgumentsLength() > 1) { 110 var arg1 = %_Arguments(1); // position 111 index = TO_INTEGER(arg1); 112 } 113 if (index < 0) index = 0; 114 if (index > subject_str_len) index = subject_str_len; 115 if (pattern_str_len + index > subject_str_len) return -1; 116 return %StringIndexOf(subject_str, pattern_str, index); 117} 118 119 120// ECMA-262 section 15.5.4.8 121function StringLastIndexOf(searchString /* position */) { // length == 1 122 var sub = ToString(this); 123 var subLength = sub.length; 124 var pat = ToString(searchString); 125 var patLength = pat.length; 126 var index = subLength - patLength; 127 if (%_ArgumentsLength() > 1) { 128 var position = ToNumber(%_Arguments(1)); 129 if (!$isNaN(position)) { 130 position = TO_INTEGER(position); 131 if (position < 0) { 132 position = 0; 133 } 134 if (position + patLength < subLength) { 135 index = position 136 } 137 } 138 } 139 if (index < 0) { 140 return -1; 141 } 142 return %StringLastIndexOf(sub, pat, index); 143} 144 145 146// ECMA-262 section 15.5.4.9 147// 148// This function is implementation specific. For now, we do not 149// do anything locale specific. 150function StringLocaleCompare(other) { 151 if (%_ArgumentsLength() === 0) return 0; 152 153 var this_str = ToString(this); 154 var other_str = ToString(other); 155 return %StringLocaleCompare(this_str, other_str); 156} 157 158 159// ECMA-262 section 15.5.4.10 160function StringMatch(regexp) { 161 if (!IS_REGEXP(regexp)) regexp = new ORIGINAL_REGEXP(regexp); 162 var subject = ToString(this); 163 164 if (!regexp.global) return regexp.exec(subject); 165 %_Log('regexp', 'regexp-match,%0S,%1r', [subject, regexp]); 166 // lastMatchInfo is defined in regexp-delay.js. 167 return %StringMatch(subject, regexp, lastMatchInfo); 168} 169 170 171// SubString is an internal function that returns the sub string of 'string'. 172// If resulting string is of length 1, we use the one character cache 173// otherwise we call the runtime system. 174function SubString(string, start, end) { 175 // Use the one character string cache. 176 if (start + 1 == end) { 177 var char_code = %_FastCharCodeAt(string, start); 178 if (!%_IsSmi(char_code)) { 179 char_code = %StringCharCodeAt(string, start); 180 } 181 return %CharFromCode(char_code); 182 } 183 return %StringSlice(string, start, end); 184} 185 186 187// ECMA-262, section 15.5.4.11 188function StringReplace(search, replace) { 189 var subject = ToString(this); 190 191 // Delegate to one of the regular expression variants if necessary. 192 if (IS_REGEXP(search)) { 193 %_Log('regexp', 'regexp-replace,%0r,%1S', [search, subject]); 194 if (IS_FUNCTION(replace)) { 195 return StringReplaceRegExpWithFunction(subject, search, replace); 196 } else { 197 return StringReplaceRegExp(subject, search, replace); 198 } 199 } 200 201 // Convert the search argument to a string and search for it. 202 search = ToString(search); 203 var start = %StringIndexOf(subject, search, 0); 204 if (start < 0) return subject; 205 var end = start + search.length; 206 207 var builder = new ReplaceResultBuilder(subject); 208 // prefix 209 builder.addSpecialSlice(0, start); 210 211 // Compute the string to replace with. 212 if (IS_FUNCTION(replace)) { 213 builder.add(replace.call(null, search, start, subject)); 214 } else { 215 reusableMatchInfo[CAPTURE0] = start; 216 reusableMatchInfo[CAPTURE1] = end; 217 ExpandReplacement(ToString(replace), subject, reusableMatchInfo, builder); 218 } 219 220 // suffix 221 builder.addSpecialSlice(end, subject.length); 222 223 return builder.generate(); 224} 225 226 227// This has the same size as the lastMatchInfo array, and can be used for 228// functions that expect that structure to be returned. It is used when the 229// needle is a string rather than a regexp. In this case we can't update 230// lastMatchArray without erroneously affecting the properties on the global 231// RegExp object. 232var reusableMatchInfo = [2, "", "", -1, -1]; 233 234 235// Helper function for regular expressions in String.prototype.replace. 236function StringReplaceRegExp(subject, regexp, replace) { 237 replace = ToString(replace); 238 return %StringReplaceRegExpWithString(subject, 239 regexp, 240 replace, 241 lastMatchInfo); 242}; 243 244 245// Expand the $-expressions in the string and return a new string with 246// the result. 247function ExpandReplacement(string, subject, matchInfo, builder) { 248 var next = %StringIndexOf(string, '$', 0); 249 if (next < 0) { 250 builder.add(string); 251 return; 252 } 253 254 // Compute the number of captures; see ECMA-262, 15.5.4.11, p. 102. 255 var m = NUMBER_OF_CAPTURES(matchInfo) >> 1; // Includes the match. 256 257 if (next > 0) builder.add(SubString(string, 0, next)); 258 var length = string.length; 259 260 while (true) { 261 var expansion = '$'; 262 var position = next + 1; 263 if (position < length) { 264 var peek = %_FastCharCodeAt(string, position); 265 if (!%_IsSmi(peek)) { 266 peek = %StringCharCodeAt(string, position); 267 } 268 if (peek == 36) { // $$ 269 ++position; 270 builder.add('$'); 271 } else if (peek == 38) { // $& - match 272 ++position; 273 builder.addSpecialSlice(matchInfo[CAPTURE0], 274 matchInfo[CAPTURE1]); 275 } else if (peek == 96) { // $` - prefix 276 ++position; 277 builder.addSpecialSlice(0, matchInfo[CAPTURE0]); 278 } else if (peek == 39) { // $' - suffix 279 ++position; 280 builder.addSpecialSlice(matchInfo[CAPTURE1], subject.length); 281 } else if (peek >= 48 && peek <= 57) { // $n, 0 <= n <= 9 282 ++position; 283 var n = peek - 48; 284 if (position < length) { 285 peek = %_FastCharCodeAt(string, position); 286 if (!%_IsSmi(peek)) { 287 peek = %StringCharCodeAt(string, position); 288 } 289 // $nn, 01 <= nn <= 99 290 if (n != 0 && peek == 48 || peek >= 49 && peek <= 57) { 291 var nn = n * 10 + (peek - 48); 292 if (nn < m) { 293 // If the two digit capture reference is within range of 294 // the captures, we use it instead of the single digit 295 // one. Otherwise, we fall back to using the single 296 // digit reference. This matches the behavior of 297 // SpiderMonkey. 298 ++position; 299 n = nn; 300 } 301 } 302 } 303 if (0 < n && n < m) { 304 addCaptureString(builder, matchInfo, n); 305 } else { 306 // Because of the captures range check in the parsing of two 307 // digit capture references, we can only enter here when a 308 // single digit capture reference is outside the range of 309 // captures. 310 builder.add('$'); 311 --position; 312 } 313 } else { 314 builder.add('$'); 315 } 316 } else { 317 builder.add('$'); 318 } 319 320 // Go the the next $ in the string. 321 next = %StringIndexOf(string, '$', position); 322 323 // Return if there are no more $ characters in the string. If we 324 // haven't reached the end, we need to append the suffix. 325 if (next < 0) { 326 if (position < length) { 327 builder.add(SubString(string, position, length)); 328 } 329 return; 330 } 331 332 // Append substring between the previous and the next $ character. 333 builder.add(SubString(string, position, next)); 334 } 335}; 336 337 338// Compute the string of a given regular expression capture. 339function CaptureString(string, lastCaptureInfo, index) { 340 // Scale the index. 341 var scaled = index << 1; 342 // Compute start and end. 343 var start = lastCaptureInfo[CAPTURE(scaled)]; 344 var end = lastCaptureInfo[CAPTURE(scaled + 1)]; 345 // If either start or end is missing return undefined. 346 if (start < 0 || end < 0) return; 347 return SubString(string, start, end); 348}; 349 350 351// Add the string of a given regular expression capture to the 352// ReplaceResultBuilder 353function addCaptureString(builder, matchInfo, index) { 354 // Scale the index. 355 var scaled = index << 1; 356 // Compute start and end. 357 var start = matchInfo[CAPTURE(scaled)]; 358 var end = matchInfo[CAPTURE(scaled + 1)]; 359 // If either start or end is missing return. 360 if (start < 0 || end <= start) return; 361 builder.addSpecialSlice(start, end); 362}; 363 364 365// Helper function for replacing regular expressions with the result of a 366// function application in String.prototype.replace. The function application 367// must be interleaved with the regexp matching (contrary to ECMA-262 368// 15.5.4.11) to mimic SpiderMonkey and KJS behavior when the function uses 369// the static properties of the RegExp constructor. Example: 370// 'abcd'.replace(/(.)/g, function() { return RegExp.$1; } 371// should be 'abcd' and not 'dddd' (or anything else). 372function StringReplaceRegExpWithFunction(subject, regexp, replace) { 373 var lastMatchInfo = DoRegExpExec(regexp, subject, 0); 374 if (IS_NULL(lastMatchInfo)) return subject; 375 376 var result = new ReplaceResultBuilder(subject); 377 // There's at least one match. If the regexp is global, we have to loop 378 // over all matches. The loop is not in C++ code here like the one in 379 // RegExp.prototype.exec, because of the interleaved function application. 380 // Unfortunately, that means this code is nearly duplicated, here and in 381 // jsregexp.cc. 382 if (regexp.global) { 383 var previous = 0; 384 do { 385 result.addSpecialSlice(previous, lastMatchInfo[CAPTURE0]); 386 var startOfMatch = lastMatchInfo[CAPTURE0]; 387 previous = lastMatchInfo[CAPTURE1]; 388 result.add(ApplyReplacementFunction(replace, lastMatchInfo, subject)); 389 // Can't use lastMatchInfo any more from here, since the function could 390 // overwrite it. 391 // Continue with the next match. 392 // Increment previous if we matched an empty string, as per ECMA-262 393 // 15.5.4.10. 394 if (previous == startOfMatch) { 395 // Add the skipped character to the output, if any. 396 if (previous < subject.length) { 397 result.addSpecialSlice(previous, previous + 1); 398 } 399 previous++; 400 } 401 402 // Per ECMA-262 15.10.6.2, if the previous index is greater than the 403 // string length, there is no match 404 lastMatchInfo = (previous > subject.length) 405 ? null 406 : DoRegExpExec(regexp, subject, previous); 407 } while (!IS_NULL(lastMatchInfo)); 408 409 // Tack on the final right substring after the last match, if necessary. 410 if (previous < subject.length) { 411 result.addSpecialSlice(previous, subject.length); 412 } 413 } else { // Not a global regexp, no need to loop. 414 result.addSpecialSlice(0, lastMatchInfo[CAPTURE0]); 415 var endOfMatch = lastMatchInfo[CAPTURE1]; 416 result.add(ApplyReplacementFunction(replace, lastMatchInfo, subject)); 417 // Can't use lastMatchInfo any more from here, since the function could 418 // overwrite it. 419 result.addSpecialSlice(endOfMatch, subject.length); 420 } 421 422 return result.generate(); 423} 424 425 426// Helper function to apply a string replacement function once. 427function ApplyReplacementFunction(replace, lastMatchInfo, subject) { 428 // Compute the parameter list consisting of the match, captures, index, 429 // and subject for the replace function invocation. 430 var index = lastMatchInfo[CAPTURE0]; 431 // The number of captures plus one for the match. 432 var m = NUMBER_OF_CAPTURES(lastMatchInfo) >> 1; 433 if (m == 1) { 434 var s = CaptureString(subject, lastMatchInfo, 0); 435 // Don't call directly to avoid exposing the built-in global object. 436 return replace.call(null, s, index, subject); 437 } 438 var parameters = $Array(m + 2); 439 for (var j = 0; j < m; j++) { 440 parameters[j] = CaptureString(subject, lastMatchInfo, j); 441 } 442 parameters[j] = index; 443 parameters[j + 1] = subject; 444 return replace.apply(null, parameters); 445} 446 447 448// ECMA-262 section 15.5.4.12 449function StringSearch(re) { 450 var regexp = new ORIGINAL_REGEXP(re); 451 var s = ToString(this); 452 var last_idx = regexp.lastIndex; // keep old lastIndex 453 regexp.lastIndex = 0; // ignore re.global property 454 var result = regexp.exec(s); 455 regexp.lastIndex = last_idx; // restore lastIndex 456 if (result == null) 457 return -1; 458 else 459 return result.index; 460} 461 462 463// ECMA-262 section 15.5.4.13 464function StringSlice(start, end) { 465 var s = ToString(this); 466 var s_len = s.length; 467 var start_i = TO_INTEGER(start); 468 var end_i = s_len; 469 if (end !== void 0) 470 end_i = TO_INTEGER(end); 471 472 if (start_i < 0) { 473 start_i += s_len; 474 if (start_i < 0) 475 start_i = 0; 476 } else { 477 if (start_i > s_len) 478 start_i = s_len; 479 } 480 481 if (end_i < 0) { 482 end_i += s_len; 483 if (end_i < 0) 484 end_i = 0; 485 } else { 486 if (end_i > s_len) 487 end_i = s_len; 488 } 489 490 var num_c = end_i - start_i; 491 if (num_c < 0) 492 num_c = 0; 493 494 return SubString(s, start_i, start_i + num_c); 495} 496 497 498// ECMA-262 section 15.5.4.14 499function StringSplit(separator, limit) { 500 var subject = ToString(this); 501 limit = (limit === void 0) ? 0xffffffff : ToUint32(limit); 502 if (limit === 0) return []; 503 504 // ECMA-262 says that if separator is undefined, the result should 505 // be an array of size 1 containing the entire string. SpiderMonkey 506 // and KJS have this behaviour only when no separator is given. If 507 // undefined is explicitly given, they convert it to a string and 508 // use that. We do as SpiderMonkey and KJS. 509 if (%_ArgumentsLength() === 0) { 510 return [subject]; 511 } 512 513 var length = subject.length; 514 if (IS_REGEXP(separator)) { 515 %_Log('regexp', 'regexp-split,%0S,%1r', [subject, separator]); 516 } else { 517 separator = ToString(separator); 518 // If the separator string is empty then return the elements in the subject. 519 if (separator.length == 0) { 520 var result = $Array(length); 521 for (var i = 0; i < length; i++) result[i] = subject[i]; 522 return result; 523 } 524 } 525 526 if (length === 0) { 527 if (splitMatch(separator, subject, 0, 0) != null) return []; 528 return [subject]; 529 } 530 531 var currentIndex = 0; 532 var startIndex = 0; 533 var result = []; 534 535 while (true) { 536 537 if (startIndex === length) { 538 result[result.length] = subject.slice(currentIndex, length); 539 return result; 540 } 541 542 var lastMatchInfo = splitMatch(separator, subject, currentIndex, startIndex); 543 544 if (IS_NULL(lastMatchInfo)) { 545 result[result.length] = subject.slice(currentIndex, length); 546 return result; 547 } 548 549 var endIndex = lastMatchInfo[CAPTURE1]; 550 551 // We ignore a zero-length match at the currentIndex. 552 if (startIndex === endIndex && endIndex === currentIndex) { 553 startIndex++; 554 continue; 555 } 556 557 result[result.length] = SubString(subject, currentIndex, lastMatchInfo[CAPTURE0]); 558 if (result.length === limit) return result; 559 560 for (var i = 2; i < NUMBER_OF_CAPTURES(lastMatchInfo); i += 2) { 561 var start = lastMatchInfo[CAPTURE(i)]; 562 var end = lastMatchInfo[CAPTURE(i + 1)]; 563 if (start != -1 && end != -1) { 564 result[result.length] = SubString(subject, start, end); 565 } else { 566 result[result.length] = void 0; 567 } 568 if (result.length === limit) return result; 569 } 570 571 startIndex = currentIndex = endIndex; 572 } 573} 574 575 576// ECMA-262 section 15.5.4.14 577// Helper function used by split. This version returns the lastMatchInfo 578// instead of allocating a new array with basically the same information. 579function splitMatch(separator, subject, current_index, start_index) { 580 if (IS_REGEXP(separator)) { 581 var lastMatchInfo = DoRegExpExec(separator, subject, start_index); 582 if (lastMatchInfo == null) return null; 583 // Section 15.5.4.14 paragraph two says that we do not allow zero length 584 // matches at the end of the string. 585 if (lastMatchInfo[CAPTURE0] === subject.length) return null; 586 return lastMatchInfo; 587 } 588 589 var separatorIndex = subject.indexOf(separator, start_index); 590 if (separatorIndex === -1) return null; 591 592 reusableMatchInfo[CAPTURE0] = separatorIndex; 593 reusableMatchInfo[CAPTURE1] = separatorIndex + separator.length; 594 return reusableMatchInfo; 595}; 596 597 598// ECMA-262 section 15.5.4.15 599function StringSubstring(start, end) { 600 var s = ToString(this); 601 var s_len = s.length; 602 var start_i = TO_INTEGER(start); 603 var end_i = s_len; 604 if (!IS_UNDEFINED(end)) 605 end_i = TO_INTEGER(end); 606 607 if (start_i < 0) start_i = 0; 608 if (start_i > s_len) start_i = s_len; 609 if (end_i < 0) end_i = 0; 610 if (end_i > s_len) end_i = s_len; 611 612 if (start_i > end_i) { 613 var tmp = end_i; 614 end_i = start_i; 615 start_i = tmp; 616 } 617 618 return SubString(s, start_i, end_i); 619} 620 621 622// This is not a part of ECMA-262. 623function StringSubstr(start, n) { 624 var s = ToString(this); 625 var len; 626 627 // Correct n: If not given, set to string length; if explicitly 628 // set to undefined, zero, or negative, returns empty string. 629 if (n === void 0) { 630 len = s.length; 631 } else { 632 len = TO_INTEGER(n); 633 if (len <= 0) return ''; 634 } 635 636 // Correct start: If not given (or undefined), set to zero; otherwise 637 // convert to integer and handle negative case. 638 if (start === void 0) { 639 start = 0; 640 } else { 641 start = TO_INTEGER(start); 642 // If positive, and greater than or equal to the string length, 643 // return empty string. 644 if (start >= s.length) return ''; 645 // If negative and absolute value is larger than the string length, 646 // use zero. 647 if (start < 0) { 648 start += s.length; 649 if (start < 0) start = 0; 650 } 651 } 652 653 var end = start + len; 654 if (end > s.length) end = s.length; 655 656 return SubString(s, start, end); 657} 658 659 660// ECMA-262, 15.5.4.16 661function StringToLowerCase() { 662 return %StringToLowerCase(ToString(this)); 663} 664 665 666// ECMA-262, 15.5.4.17 667function StringToLocaleLowerCase() { 668 return %StringToLowerCase(ToString(this)); 669} 670 671 672// ECMA-262, 15.5.4.18 673function StringToUpperCase() { 674 return %StringToUpperCase(ToString(this)); 675} 676 677 678// ECMA-262, 15.5.4.19 679function StringToLocaleUpperCase() { 680 return %StringToUpperCase(ToString(this)); 681} 682 683 684// ECMA-262, section 15.5.3.2 685function StringFromCharCode(code) { 686 var n = %_ArgumentsLength(); 687 if (n == 1) return %CharFromCode(ToNumber(code) & 0xffff) 688 689 // NOTE: This is not super-efficient, but it is necessary because we 690 // want to avoid converting to numbers from within the virtual 691 // machine. Maybe we can find another way of doing this? 692 var codes = new $Array(n); 693 for (var i = 0; i < n; i++) codes[i] = ToNumber(%_Arguments(i)); 694 return %StringFromCharCodeArray(codes); 695} 696 697 698// Helper function for very basic XSS protection. 699function HtmlEscape(str) { 700 return ToString(str).replace(/</g, "<") 701 .replace(/>/g, ">") 702 .replace(/"/g, """) 703 .replace(/'/g, "'"); 704}; 705 706 707// Compatibility support for KJS. 708// Tested by mozilla/js/tests/js1_5/Regress/regress-276103.js. 709function StringLink(s) { 710 return "<a href=\"" + HtmlEscape(s) + "\">" + this + "</a>"; 711} 712 713 714function StringAnchor(name) { 715 return "<a name=\"" + HtmlEscape(name) + "\">" + this + "</a>"; 716} 717 718 719function StringFontcolor(color) { 720 return "<font color=\"" + HtmlEscape(color) + "\">" + this + "</font>"; 721} 722 723 724function StringFontsize(size) { 725 return "<font size=\"" + HtmlEscape(size) + "\">" + this + "</font>"; 726} 727 728 729function StringBig() { 730 return "<big>" + this + "</big>"; 731} 732 733 734function StringBlink() { 735 return "<blink>" + this + "</blink>"; 736} 737 738 739function StringBold() { 740 return "<b>" + this + "</b>"; 741} 742 743 744function StringFixed() { 745 return "<tt>" + this + "</tt>"; 746} 747 748 749function StringItalics() { 750 return "<i>" + this + "</i>"; 751} 752 753 754function StringSmall() { 755 return "<small>" + this + "</small>"; 756} 757 758 759function StringStrike() { 760 return "<strike>" + this + "</strike>"; 761} 762 763 764function StringSub() { 765 return "<sub>" + this + "</sub>"; 766} 767 768 769function StringSup() { 770 return "<sup>" + this + "</sup>"; 771} 772 773 774// StringBuilder support. 775 776function StringBuilder() { 777 this.elements = new $Array(); 778} 779 780 781function ReplaceResultBuilder(str) { 782 this.elements = new $Array(); 783 this.special_string = str; 784} 785 786 787ReplaceResultBuilder.prototype.add = 788StringBuilder.prototype.add = function(str) { 789 if (!IS_STRING(str)) str = ToString(str); 790 if (str.length > 0) { 791 var elements = this.elements; 792 elements[elements.length] = str; 793 } 794} 795 796 797ReplaceResultBuilder.prototype.addSpecialSlice = function(start, end) { 798 var len = end - start; 799 if (len == 0) return; 800 var elements = this.elements; 801 if (start >= 0 && len >= 0 && start < 0x80000 && len < 0x800) { 802 elements[elements.length] = (start << 11) + len; 803 } else { 804 elements[elements.length] = SubString(this.special_string, start, end); 805 } 806} 807 808 809StringBuilder.prototype.generate = function() { 810 return %StringBuilderConcat(this.elements, ""); 811} 812 813 814ReplaceResultBuilder.prototype.generate = function() { 815 return %StringBuilderConcat(this.elements, this.special_string); 816} 817 818 819function StringToJSON(key) { 820 return CheckJSONPrimitive(this.valueOf()); 821} 822 823 824// ------------------------------------------------------------------- 825 826function SetupString() { 827 // Setup the constructor property on the String prototype object. 828 %SetProperty($String.prototype, "constructor", $String, DONT_ENUM); 829 830 831 // Setup the non-enumerable functions on the String object. 832 InstallFunctions($String, DONT_ENUM, $Array( 833 "fromCharCode", StringFromCharCode 834 )); 835 836 837 // Setup the non-enumerable functions on the String prototype object. 838 InstallFunctionsOnHiddenPrototype($String.prototype, DONT_ENUM, $Array( 839 "valueOf", StringValueOf, 840 "toString", StringToString, 841 "charAt", StringCharAt, 842 "charCodeAt", StringCharCodeAt, 843 "concat", StringConcat, 844 "indexOf", StringIndexOf, 845 "lastIndexOf", StringLastIndexOf, 846 "localeCompare", StringLocaleCompare, 847 "match", StringMatch, 848 "replace", StringReplace, 849 "search", StringSearch, 850 "slice", StringSlice, 851 "split", StringSplit, 852 "substring", StringSubstring, 853 "substr", StringSubstr, 854 "toLowerCase", StringToLowerCase, 855 "toLocaleLowerCase", StringToLocaleLowerCase, 856 "toUpperCase", StringToUpperCase, 857 "toLocaleUpperCase", StringToLocaleUpperCase, 858 "link", StringLink, 859 "anchor", StringAnchor, 860 "fontcolor", StringFontcolor, 861 "fontsize", StringFontsize, 862 "big", StringBig, 863 "blink", StringBlink, 864 "bold", StringBold, 865 "fixed", StringFixed, 866 "italics", StringItalics, 867 "small", StringSmall, 868 "strike", StringStrike, 869 "sub", StringSub, 870 "sup", StringSup, 871 "toJSON", StringToJSON 872 )); 873} 874 875 876SetupString(); 877