1/* 2 * Copyright (c) 2021-2024 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16package std.core; 17 18const MAX_CODE_POINT = 0x10FFFF 19const MAX_CODE_UNIT = 0xFFFF 20 21type StringOrRegExp = String | RegExp 22 23/** 24 * Unicode string 25 */ 26export final class String extends Object implements Comparable<String>, JSONable<String>, Iterable<String> { 27 28 // The constructors below are implemented via initobj instruction 29 /** 30 * Constructs an empty String 31 */ 32 public constructor() {} 33 34 /** 35 * Constructs String from chars array initializer 36 * 37 * @param data initializer 38 */ 39 public constructor(data: char[]) {} 40 41 /** 42 * Constructs String from another String 43 * 44 * @param otherStr initializer 45 */ 46 public constructor(otherStr: String) {} 47 48 /** 49 * Constructs String from NullishType 50 * 51 * @param object initializer 52 */ 53 public constructor(object: NullishType) {} 54 55 /** 56 * Creates a new instance of a String 57 * 58 * @returns A new String instance 59 */ 60 static invoke(): string { 61 return new String(); 62 } 63 64 /** 65 * Creates a new instance of a String 66 * 67 * @param value The value to be converted to a string. Can be an Object or null. 68 * 69 * @returns A new String instance 70 */ 71 static invoke(value: Object | null | undefined): string { 72 return new String(value); 73 } 74 75 /** 76 * Returns an instance of string at passed index. 77 * 78 * @param index index to look at 79 * 80 * @returns a primitive at index 81 */ 82 public $_get(index: number): char { 83 return this.$_get(index as int); 84 } 85 86 /** 87 * Returns an instance of string at passed index. 88 * 89 * @param index index to look at 90 * 91 * @returns char value at index 92 * 93 * @throws StringIndexOutOfBoundsError if index is negative or >= length 94 */ 95 public $_get(index: int): char { 96 return this.charAt(index); 97 } 98 99 /** 100 * Checks equality of this string and another Object as String 101 * 102 * @param to another object to compare 103 * 104 * @returns true if strings are equal and false otherwise 105 * 106 * @remarks 107 * Implemented as native function, @see `equals()` intrinsic [declaration](https://gitee.com/openharmony-sig/arkcompiler_runtime_core/blob/master/plugins/ets/runtime/ets_libbase_runtime.yaml#L596). 108 */ 109 public native equals(to: NullishType): boolean; 110 111 /** 112 * Length of this string 113 * 114 * @returns length of this string 115 * 116 * @remarks 117 * Implemented as native function, @see `getLength()` intrinsic [declaration](https://gitee.com/openharmony-sig/arkcompiler_runtime_core/blob/master/plugins/ets/runtime/ets_libbase_runtime.yaml#L563). 118 */ 119 public native getLength(): int; 120 121 /** 122 * Length of this string 123 * 124 * @returns length of this string 125 */ 126 public get length(): number { 127 return this.getLength() as number 128 } 129 130 /** 131 * Getter for char at some index 132 * 133 * @param index index in char array inside String 134 * 135 * @returns char value at index 136 * 137 * @throws StringIndexOutOfBoundsError if index is negative or >= length 138 * 139 * @remarks 140 * Implemented as native function, @see `charAt()` intrinsic [declaration](https://gitee.com/openharmony-sig/arkcompiler_runtime_core/blob/master/plugins/ets/runtime/ets_libbase_runtime.yaml#L585). 141 */ 142 public charAt(index: number): String { 143 if (index < 0 || index >= this.getLength() || this.getLength() == 0) { 144 return ""; 145 } 146 let c = new char[1]; 147 c[0] = this.charAt(index as int) 148 return new String(c) 149 } 150 151 /** 152 * Getter for char at some index 153 * 154 * @param index index in char array inside String 155 * 156 * @returns char value at index 157 * 158 * @throws StringIndexOutOfBoundsError if index is negative or >= length 159 * 160 * @remarks 161 * Implemented as native function, @see `charAt()` intrinsic [declaration](https://gitee.com/openharmony-sig/arkcompiler_runtime_core/blob/master/plugins/ets/runtime/ets_libbase_runtime.yaml#L585). 162 */ 163 public native charAt(index: int): char; 164 165 /** 166 * Checks if this string is empty 167 * 168 * @returns true if empty and false otherwise 169 * @remarks 170 * Implemented as native function, @see `length()` intrinsic [declaration](https://gitee.com/openharmony-sig/arkcompiler_runtime_core/blob/master/plugins/ets/runtime/ets_libbase_runtime.yaml#L574). 171 */ 172 public native isEmpty(): boolean; 173 174 /** 175 * Gets the codepoint at the specified index in this string. 176 * Is similar to charAt(index), but if the character at the 177 * index is the beginning of a surrogate pair, the int 178 * representation for this codepoint is returned. Otherwise, 179 * the character at the index is returned. 180 * 181 * @param index index of the potential surrogate pair 182 * 183 * @returns the codepoint at the specified index 184 */ 185 public codePointAt(index: number): Number | undefined { 186 if (index < 0 || index >= this.getLength() || this.getLength() == 0) { 187 return undefined 188 } 189 return this.codePointAtInt(index as int) as number; 190 } 191 192 /** 193 * Gets the codepoint at the specified index in this string. 194 * Is similar to charAt(index), but if the character at the 195 * index is the beginning of a surrogate pair, the int 196 * representation for this codepoint is returned. Otherwise, 197 * the character at the index is returned. 198 * 199 * @param index index of the potential surrogate pair 200 * 201 * @returns the codepoint at the specified index 202 */ 203 public codePointAt(index: int): Number | undefined { 204 if (index < 0 || index >= this.getLength() || this.getLength() == 0) { 205 return undefined 206 } 207 return this.codePointAtInt(index) as number; 208 } 209 210 /** 211 * Gets the codepoint at the specified index in this string. 212 * Is similar to charAt(index), but if the character at the 213 * index is the beginning of a surrogate pair, the int 214 * representation for this codepoint is returned. Otherwise, 215 * the character at the index is returned. 216 * 217 * @param index index of the potential surrogate pair 218 * 219 * @returns the codepoint at the specified index 220 */ 221 private codePointAtInt(index: int): int { 222 assert index >= 0 && index < this.getLength(): "Index is out of bound"; 223 let highValue: char = this.charAt(index); 224 if (!Char.isHighSurrogate(highValue) || ++index == this.getLength()) { 225 return highValue as int; 226 } 227 let lowValue: char = this.charAt(index); 228 if (!Char.isLowSurrogate(lowValue)) { 229 return highValue as int; 230 } 231 return Char.charsToCodePoint(highValue, lowValue); 232 } 233 234 /** 235 * Returns the amount of full codepoints between begin and end 236 * indexes. Characters outside the range are not counted, even if 237 * the range ends in the middle of a surrogate pair. 238 * 239 * @param begin index to start from 240 * @param end past the ending index 241 * 242 * @throws StringIndexOutOfBoundsError if begin is negative or >= length 243 * @throws StringIndexOutOfBoundsError if end >= length 244 * @throws AssertionError if begin > end 245 * 246 * @returns the amount of completed codepoints 247 */ 248 public codePointCount(begin: number, end: number): number { 249 return this.codePointCount(begin as int, end as int) 250 } 251 252 /** 253 * Returns the amount of full codepoints between begin and end 254 * indexes. Characters outside the range are not counted, even if 255 * the range ends in the middle of a surrogate pair. 256 * 257 * @param begin index to start from 258 * @param end past the ending index 259 * 260 * @throws StringIndexOutOfBoundsError if begin is negative or >= length 261 * @throws StringIndexOutOfBoundsError if end >= length 262 * @throws AssertionError if begin > end 263 * 264 * @returns the amount of completed codepoints 265 */ 266 public codePointCount(begin: int, end: int): int { 267 assert begin <= end: "Begin idx must be less then end idx"; 268 let counter: int = 0; 269 for (let i: int = begin; i < end; i++) { 270 ++counter; 271 if (Char.isHighSurrogate(this.charAt(i)) && 272 (i + 1 < end) && 273 Char.isLowSurrogate(this.charAt(i + 1))) { 274 ++i; 275 } 276 } 277 return counter; 278 } 279 280 /** 281 * Gets the full char sequence that is representing 282 * this string. 283 * 284 * @returns char[] array 285 */ 286 public getChars(): char[] { 287 return this.getChars(0, this.getLength()); 288 } 289 290 /** 291 * Gets the char sequence that is representing the part of 292 * this string between begin and end indexes. The range is 293 * a half-interview [begin, end). 294 * 295 * @param begin index to start from 296 * @param end past the ending index 297 * 298 * @throws StringIndexOutOfBoundsError if begin > end 299 * @throws StringIndexOutOfBoundsError if begin is negative or >= length 300 * @throws StringIndexOutOfBoundsError if end > length 301 * 302 * @returns char[] array 303 * 304 * @remark 305 * Implemented as native function, @see `getChars()` intrinsic [declaration](https://gitee.com/openharmony-sig/arkcompiler_runtime_core/blob/master/plugins/ets/runtime/ets_libbase_runtime.yaml#L231). 306 */ 307 public native getChars(begin: int, end: int): char[]; 308 309 /** 310 * Gets the byte sequence that is representing the part of 311 * this string between begin and end indexes. The range is 312 * a half-interview [begin, end). 313 * 314 * @param begin index to start from 315 * @param end past the ending index 316 * 317 * @throws StringIndexOutOfBoundsError if begin is negative or >= length 318 * @throws StringIndexOutOfBoundsError if end >= length 319 * @throws AssertionError if begin > end 320 * 321 * @returns byte[] array 322 */ 323 public native getBytes(begin: int, end: int): byte[]; 324 325 /** 326 * Compares the given StringBuilder to this String. The 327 * result is true if the StringBuilder has the same content 328 * as this String at this moment and false otherwise. 329 * 330 * @param sb StringBuilder to compare to 331 * 332 * @throws NullPointerError if sb param is null 333 * 334 * @returns true if StringBuilder has the same String 335 */ 336 public contentEquals(sb: StringBuilder): boolean { 337 return this.equals(sb.toString()); 338 } 339 340 /** 341 * Lexicographical comparison between this String and another one. 342 * The result is less than 0 if this string sorts before the other, 343 * 0 if they are equal, and greater than 0 otherwise. 344 * 345 * @param other String to compare with 346 * 347 * @throws NullPointerError if to param is null 348 * 349 * @returns the comparison result 350 */ 351 public native override compareTo(other: String): int; 352 353 /** 354 * Comparison between this String and another one based on locale. 355 * The result is -1 if this string sorts before the another string, 356 * 0 if they are equal, and 1 otherwise. 357 * 358 * @param another String to compare with 359 * @param locale String representing the BCP47 language tag 360 * 361 * @throws RangeError if the locale tag is invalid or not found 362 * @throws NullPointerError if another or locale is null 363 * 364 * @returns the comparison result 365 */ 366 public native localeCompare(another: String, locale: String | null): number; 367 368 /** 369 * Comparison between this String and another one based on default 370 * host locale. The result is -1 if this string sorts before the 371 * another string, 0 if they are equal, and 1 otherwise. 372 * 373 * @param another String to compare with 374 * 375 * @throws RangeError if the locale tag is invalid or not found 376 * @throws NullPointerError if another param is null 377 * 378 * @returns the comparison result 379 */ 380 public localeCompare(another: String): number { 381 return this.localeCompare(another, null); 382 } 383 384 /** 385 * Checks that the substring of this string that starts from 386 * the specified index starts with the specified prefix. 387 * Negative `fromIndex` is treated as 0. Result is always true 388 * if `prefix` is empty. 389 * 390 * @param prefix prefix string 391 * @param fromIndex index to start from 392 * 393 * @returns true if the substring begins with prefix 394 */ 395 public native startsWith(prefix: String, fromIndex: int): boolean; 396 public startsWith(prefix: String, fromIndex?: Number): boolean { 397 if (fromIndex == undefined) { 398 return this.startsWith(prefix, 0) 399 } 400 return this.startsWith(prefix, fromIndex.intValue()) 401 } 402 403 /** 404 * Checks that this string ends with the specified suffix. 405 * Result is always true if `suffix` is empty. 406 * 407 * @param suffix suffix string 408 * 409 * @param endPosition at which suffix is expected to be found. Defaults to str.length. 410 * 411 * @returns true if this string ends with suffix 412 */ 413 public native endsWith(suffix: String, endPosition: int): boolean; 414 public endsWith(suffix: String, endPosition?: Number): boolean { 415 return this.endsWith(suffix, asIntOrDefault(endPosition, this.getLength())); 416 } 417 418 /** 419 * Computes the hashcode for this String. 420 * 421 * @returns hashcode value of this String 422 */ 423 public native override $_hashCode(): int; 424 425 /** 426 * Finds the first occurrence of a character in this String. 427 * 428 * @param ch to find 429 * 430 * @returns index of the character from the beginning of this string, or -1 if not found 431 */ 432 public native indexOf(ch: char): int; 433 434 /** 435 * Finds the first occurrence of a character in this String at position >= fromIndex. 436 * Negative fromIndex is equivalent to 0, and fromIndex >= length implies no match. 437 * 438 * @param ch to find 439 * @param fromIndex to start searching from 440 * 441 * @returns index of the character from the beginning of this string, or -1 if not found 442 */ 443 public native indexOf(ch: char, fromIndex: int): int; 444 445 /** 446 * Finds the first occurrence of another String in this String 447 * 448 * @param str to find 449 * 450 * @param fromIndex to start searching from 451 * 452 * @returns index of the str from the beginning of this string, or -1 if not found 453 */ 454 public indexOf(str: String, fromIndex?: Number): number { 455 if (fromIndex == undefined) { 456 return this.indexOf(str, 0) 457 } 458 return this.indexOf(str, fromIndex.intValue()) 459 } 460 461 /** 462 * Finds the first occurrence of another String in this String at position >= fromIndex. 463 * Negative fromIndex is equivalent to fromIndex = 0, and fromIndex >= length implies no match. 464 * 465 * @param str to find 466 * @param fromIndex to start searching from 467 * 468 * @returns index of the str from the beginning of this string, or -1 if not found 469 */ 470 public native indexOf(str: String, fromIndex: int): int; 471 472 /** 473 * Finds the last occurrence of a character in this String. 474 * 475 * @param ch to find 476 * 477 * @returns index of the character from the beginning of this string, or -1 if not found 478 */ 479 public lastIndexOf(ch: char): int { 480 return this.lastIndexOf(ch, this.getLength()); 481 } 482 483 /** 484 * Finds the last occurrence of a character in this String at position <= fromIndex. 485 * All values of fromIndex >= length are equivalent, and negative fromIndex implies no match. 486 * 487 * @param ch to find 488 * @param fromIndex to start searching from 489 * 490 * @returns index of the character from the beginning of this string, or -1 if not found 491 */ 492 public lastIndexOf(ch: char, fromIndex: int): int { 493 if (fromIndex >= this.getLength()) { 494 fromIndex = this.getLength() - 1; 495 } 496 for (let i: int = fromIndex; i >= 0; i--) { 497 if (this.charAt(i) == ch) { 498 return i; 499 } 500 } 501 return -1; 502 } 503 504 /** 505 * Finds the last occurrence of another String in this String. 506 * 507 * @param str to find 508 * 509 * @param fromIndex to start searching from 510 * 511 * @throws NullPointerError if str param is null 512 * 513 * @returns index of the str from the beginning of this string, or -1 if not found 514 */ 515 public lastIndexOf(str: String, fromIndex?: Number): number { 516 if (fromIndex == undefined || isNaN(fromIndex)) { 517 return this.lastIndexOf(str, Int.MAX_VALUE as int) 518 } 519 return this.lastIndexOf(str, fromIndex.intValue()) 520 } 521 522 /** 523 * Finds the last occurrence of another String in this String at position <= fromIndex. 524 * All values of fromIndex >= length are equivalent, and negative fromIndex implies no match. 525 * 526 * @param str to find 527 * @param fromIndex to start searching from 528 * 529 * @returns index of the str from the beginning of this string, or -1 if not found 530 */ 531 public native lastIndexOf(str: String, fromIndex: int): int; 532 533 /** 534 * Selects a substring of this String, starting at a specified index 535 * and ending at the end of this String. 536 * 537 * @param begin to start substring 538 * 539 * @returns new String which is a substring of this String 540 */ 541 public substring(begin: int): String { 542 return this.substring(begin, this.getLength()); 543 } 544 545 /** 546 * Selects a substring of this String, starting at a specified index 547 * and ending at index before another specified index. 548 * 549 * @param begin to start substring 550 * @param end to end before at 551 * 552 * @returns new String which is a substring of this String 553 */ 554 public substring(begin: number, end?: Number): String { 555 return this.substring(begin as int, asIntOrDefault(end, this.getLength())) 556 } 557 558 /** 559 * Selects a substring of this String, starting at a specified index 560 * and ending at index before another specified index. 561 * 562 * If 'begin' < 0, then 'begin' is assumed to be equal to zero. 563 * If 'begin' > this.length, then 'begin' is assumed to be equal to the this.length. 564 * If 'end' < 0, then 'end' is assumed to be equal to zero. 565 * If 'end' > this.length, then 'end' is assumed to be equal to this.length. 566 * If 'begin' > 'end', then these are swapped. 567 * If 'begin' == 'end', then an empty string is returned. 568 * 569 * @param begin to start substring 570 * @param end to end before at 571 * 572 * @returns new String which is a substring of this String 573 */ 574 public native substring(begin: int, end: int): String; 575 576 /** 577 * Concatenation of this and array of strings. 578 * 579 * @param strings strings to concat with 580 * 581 * @returns new String which is a concatenation of this + strings[0] + ... + strings[string.length - 1] 582 */ 583 public concat(...strings: String[]): String { 584 let length = strings.length 585 if (length == 0) { 586 return this 587 } 588 if (length == 1) { 589 return String.concat2(this, strings[0]); 590 } 591 if (length == 2) { 592 return String.concat3(this, strings[0], strings[1]); 593 } 594 if (length == 3) { 595 return String.concat4(this, strings[0], strings[1], strings[2]); 596 } 597 let res = new StringBuilder(this); 598 for (let i = 0; i < length; i++) { 599 res = res.append(strings[i]) 600 } 601 return res.toString(); 602 } 603 604 /** 605 * Concatenation of two, three of four strings 606 * 607 * @param strings strings to concat 608 * 609 * @returns new String is the concatenation of the 610 * arguments passed to the function: 611 * 612 * str1 + str2 - for concat2 613 * str1 + str3 + str3 - for concat3 614 * str1 + str2 + str3 + str4 - for concat4 615 */ 616 private static native concat2(str1: String, str2: String): String; 617 private static native concat3(str1: String, str2: String, str3: String): String; 618 private static native concat4(str1: String, str2: String, str3: String, str4: String): String; 619 620 /* 621 * Creates a String using substitutions 622 * 623 * @param d a template 624 * 625 * @param substs strings to be substituted 626 * 627 * @returns string with respected data being substituted 628 */ 629 // TODO(ivan-tyulyandin): uncomment when #12735 will be fixed 630 // public static raw(d: String, substs: ...String): String { 631 // throw new Error("String.raw: not implemented") 632 // } 633 634 /** 635 * Replaces all occurrences of the specified character 636 * with another specified character. If no specified 637 * character in this string is found then the original 638 * string will be returned 639 * 640 * @param oldCh character which occurrences will be replaced 641 * @param newCh character to replace 642 * 643 * @returns new String with replaced characters 644 */ 645 public replaceChar(oldCh: char, newCh: char): String { 646 let newChars: char[] = this.getChars(); 647 for (let i: int = 0; i < newChars.length; i++) { 648 if (newChars[i] == oldCh) { 649 newChars[i] = newCh; 650 } 651 } 652 return new String(newChars); 653 } 654 655 /** 656 * Checks if this String contains the specified string. 657 * The search starts from specified index (negative fromIndex is equivalent to fromIndex = 0, and fromIndex >= length implies no match). 658 * 659 * @param str string to search 660 * @param fromIndex index to start search from 661 * 662 * @throws NullPointerError if str param is null 663 * 664 * @returns true if this String contains str and false otherwise 665 */ 666 public contains(str: String, fromIndex: number): boolean { 667 return this.contains(str, fromIndex as int); 668 } 669 670 /** 671 * Checks if this String contains the specified string. 672 * The search starts from specified index. 673 * 674 * @param str string to search 675 * @param fromIndex index to start search from 676 * 677 * @throws NullPointerError if str param is null 678 * @throws StringIndexOutOfBoundsError if fromIndex param is negative or >= length 679 * 680 * @returns true if this String contains str and false otherwise 681 */ 682 public contains(str: String, fromIndex: int): boolean { 683 return this.indexOf(str, fromIndex) != -1; 684 } 685 686 private static splitMatch(s: String, q: int, r: String): int { 687 let rLength = r.getLength(); 688 if (q + rLength > s.getLength()) { 689 return -1; 690 } 691 if (rLength == 0) { 692 // For making algorithm compiler-friendly 693 // should be removed after BoundsAnalysis improvement 694 return q; 695 } 696 for (let i = 0; i < rLength - 1; ++i) { 697 if (s.codePointAtInt(q + i) != r.codePointAtInt(i)) { 698 return -1; 699 } 700 } 701 if (s.codePointAtInt(q + rLength - 1) != r.codePointAtInt(rLength - 1) as int) { 702 // For making algorithm compiler-friendly 703 // should be removed after BoundsAnalysis improvement 704 return -1; 705 } 706 return q + rLength; 707 } 708 709 /** 710 * Splits this String by pattern and returns ordered array of substrings. 711 * The order of the resulted array corresponds to the order of the 712 * passage of this String from beginning to end. The pattern is 713 * excluded from substrings. The array is limited by some specified value. 714 * 715 * @param pattern String or RegExp to split by 716 * @param limit max length of the returned array. If it's negative then there is no limit. 717 * 718 * @throws NullPointerError if pattern param is null 719 * 720 * @returns string array contains substrings from this String 721 */ 722 public split(separator: StringOrRegExp, limit?: Number): String[] { 723 if (separator instanceof RegExp) { 724 return (separator as RegExp).split(this, limit) 725 } 726 return this.split(separator as String, limit) 727 } 728 729 public split(separator: RegExp, limit?: Number): String[] { 730 return separator.split(this, limit) 731 } 732 733 public split(separator: String, limit?: Number): String[] { 734 let lim : long 735 if (limit == undefined) { 736 lim = (1 << 32) - 1 737 } else if (limit == 0) { 738 return new String[0] 739 } else { 740 lim = limit.unboxed() as long 741 } 742 let s = this.getLength() 743 if (s == 0) { 744 let z = String.splitMatch(this, 0, separator) 745 if (z != -1) { 746 return new String[0] 747 } 748 return [(this)] 749 } 750 let splittedStrings = new NullableStringArray(s) 751 let lastStart = 0 752 for (let lastEnd = 0; lastEnd < s;) { 753 let separatorRight = String.splitMatch(this, lastEnd, separator) 754 assert(separatorRight <= s) 755 if (separatorRight != -1 && separatorRight != lastStart) { 756 let substr = this.substring(lastStart, lastEnd) 757 splittedStrings.pushBack(substr) 758 if (splittedStrings.size() == lim) { 759 return splittedStrings.toArray() as String[] 760 } 761 lastStart = separatorRight 762 lastEnd = lastStart 763 } else { 764 ++lastEnd 765 } 766 } 767 let substr = this.substring(lastStart, s) 768 splittedStrings.pushBack(substr) 769 return splittedStrings.toArray() as String[] 770 } 771 772 /** 773 * Concatenates the specified string array by inserting 774 * the specified separator between all elements. 775 * 776 * @param strings string array 777 * @param delim separator between all elements 778 * 779 * @throws NullPointerError if strings param is null 780 * @throws NullPointerError if delim param is null 781 * 782 * @returns newly created string from string array and delimiter 783 */ 784 public static join(strings: String[], delim: String): String { 785 return String.join(strings, delim, "", ""); 786 } 787 788 /** 789 * Concatenates the specified string array by inserting 790 * the specified prefix before each element, the specified 791 * suffix after each element, and the specified separator 792 * between all elements. 793 * 794 * @param strings string array 795 * @param delim separator between all elements 796 * @param prefix prefix before each element 797 * @param suffix suffix after each element 798 * 799 * @throws NullPointerError if strings param is null 800 * @throws NullPointerError if delim param is null 801 * @throws NullPointerError if prefix param is null 802 * @throws NullPointerError if suffix param is null 803 * 804 * @returns newly created string from string array, prefix, suffix and delimiter 805 */ 806 public static join(strings: String[], delim: String, prefix: String, suffix: String): String { 807 let resStr: String = ""; 808 for (let i: int = 0; i < strings.length; i++) { 809 resStr += prefix + strings[i] + suffix; 810 if (i != strings.length - 1) { 811 resStr += delim; 812 } 813 } 814 return resStr; 815 } 816 817 /** 818 * Creates new string similar to this String but with 819 * all characters in lower case. 820 * 821 * @returns new string with all characters in lower case 822 */ 823 public native toLowerCase(): String; 824 825 /** 826 * Creates new string similar to this String but with 827 * all characters in upper case. 828 * 829 * @returns new string with all characters in upper case 830 */ 831 public native toUpperCase(): String; 832 833 /** 834 * Trims all whitespaces from the beginning and end of this String. 835 * 836 * @returns new trimmed string 837 */ 838 public native trim(): String; 839 840 /** 841 * Trims all whitespaces from the beginning of this String. 842 * 843 * @returns new left trimmed string 844 */ 845 public native trimLeft(): String; 846 847 /** 848 * Trims all whitespaces from the end of this String. 849 * 850 * @returns new right trimmed string 851 */ 852 public native trimRight(): String; 853 854 /** 855 * Checks whether the specified char is in the specified 856 * char array or not. 857 * 858 * @param ch character to search 859 * @param data char array to search in 860 * 861 * @throws NullPointerError if data param is null 862 * 863 * @returns true if ch is in the data and false otherwise 864 */ 865 private static isCharOneOf(ch: char, data: char[]): boolean { 866 for (let i: int = 0; i < data.length; i++) { 867 if (ch == data[i]) { 868 return true; 869 } 870 } 871 return false; 872 } 873 874 /** 875 * Trims all specified characters from the beginning and 876 * end of this String. 877 * 878 * @param remove that contains the characters to trim 879 * 880 * @throws NullPointerError if remove param is null 881 * 882 * @returns new trimmed string 883 */ 884 public trim(remove: char[]): String { 885 if (this.isEmpty()) { 886 return this; 887 } 888 let right: int = this.getLength() - 1; 889 let left: int = 0; 890 if (String.isCharOneOf(this.charAt(right), remove)) { 891 right--; 892 while (left <= right && String.isCharOneOf(this.charAt(right), remove)) { 893 right--; 894 } 895 } else { 896 if (right != 0) { 897 if (!String.isCharOneOf(this.charAt(left), remove)) { 898 return this; 899 } else { 900 left++; 901 } 902 } else { 903 return this; 904 } 905 } 906 907 while (left <= right && String.isCharOneOf(this.charAt(left), remove)) { 908 left++; 909 } 910 911 return this.substring(left, right + 1); 912 } 913 914 /** 915 * Trims all specified characters from the beginning this String. 916 * 917 * @param remove that contains the characters to trim 918 * 919 * @throws NullPointerError if remove param is null 920 * 921 * @returns new left trimmed string 922 */ 923 public trimLeft(remove: char[]): String { 924 if (this.isEmpty() || !String.isCharOneOf(this.charAt(0), remove)) { 925 return this; 926 } 927 let firstNotSpecCharIdx: int = 0; 928 for (let i: int = 1; i < this.getLength(); i++) { 929 if (!String.isCharOneOf(this.charAt(i), remove)) { 930 firstNotSpecCharIdx = i; 931 break; 932 } 933 } 934 return this.substring(firstNotSpecCharIdx, this.getLength()); 935 } 936 937 /** 938 * Trims all specified characters from the end of this String. 939 * 940 * @param remove that contains the characters to trim 941 * 942 * @throws NullPointerError if remove param is null 943 * 944 * @returns new right trimmed string 945 */ 946 public trimRight(remove: char[]): String { 947 let last: int = this.getLength() - 1; 948 if (this.isEmpty() || !String.isCharOneOf(this.charAt(last), remove)) { 949 return this; 950 } 951 let lastNotSpecCharIdx: int = 0; 952 for (let i: int = last - 1; i >= 0; i--) { 953 if (!String.isCharOneOf(this.charAt(i), remove)) { 954 lastNotSpecCharIdx = i; 955 break; 956 } 957 } 958 return this.substring(0, lastNotSpecCharIdx + 1); 959 } 960 961 /** 962 * Creates a new string of a specified length in which 963 * the beginning of this String is padded with a 964 * specified character. `padStart` is an alias of this method, 965 * except the parameter order. 966 * 967 * @param pad to repeat 968 * @param count of characters in the resulting string 969 * 970 * @returns new string with padding at the beginning 971 */ 972 public padLeft(pad: char, count: int): String { 973 return this.padStart(count, pad) 974 } 975 976 /** 977 * Creates a new string of a specified length in which 978 * the end of this String is padded with a specified 979 * character. `padEnd` is an alias of this method, 980 * except the parameter order. 981 * 982 * @param pad to repeat 983 * @param count of characters in the resulting string 984 * 985 * @returns new string with padding at the end 986 */ 987 public padRight(pad: char, count: int): String { 988 return this.padEnd(count, pad) 989 } 990 991 /** 992 * Repeats this string count times, i.e. 993 * a = "A", 994 * a.repeat(2) == "AA" 995 * 996 * @param count number of repetitions of this String 997 * 998 * @throws ArgumentOutOfRangeException if count < 0 999 * 1000 * @returns this string that is repeated count times 1001 */ 1002 public repeat(count: number): String { 1003 return this.repeat(count as int) 1004 } 1005 1006 /** 1007 * Repeats this string count times, i.e. 1008 * a = "A", 1009 * a.repeat(2) == "AA" 1010 * 1011 * @param count number of repetitions of this String 1012 * 1013 * @throws ArgumentOutOfRangeException if count < 0 1014 * 1015 * @returns this string that is repeated count times 1016 */ 1017 public native repeat(count: int): String; 1018 1019 /** 1020 * The `toString()` method returns the string representation of the given String 1021 * in the form of a copy of the original object. 1022 * 1023 * @returns a copy of the original String 1024 */ 1025 public override toString(): String { 1026 return this; 1027 } 1028 1029 /** 1030 * The `toString()` method returns the string representation of the given String 1031 * in the form of a copy of the original object. 1032 * 1033 * @returns a copy of the original String 1034 */ 1035 public override toLocaleString(): String { 1036 return this; 1037 } 1038 1039 /** 1040 * The at() method takes an integer value and returns a new String consisting of the single UTF-16 code unit located 1041 * at the specified offset. This method allows for positive and negative integers. Negative integers count back from the last string character. 1042 * 1043 * @returns A String consisting of the single UTF-16 code unit located at the specified position. 1044 * Returns undefined if the given index can not be found. 1045 */ 1046 public at(index: number): string | undefined { 1047 if (isNaN(index)) { 1048 index = 0; 1049 } 1050 const n = this.getLength(); 1051 if (index < 0) { 1052 index += n; 1053 } 1054 if (index < 0 || index >= n) { 1055 return undefined; 1056 } 1057 return new String(this.charAt(index as int)); 1058 } 1059 1060 private CreateHTMLString(tag: String, param: String): String{ 1061 return "<" + tag + param + ">" + (this) + "</" + tag + ">" 1062 } 1063 1064 /** 1065 * The anchor() method creates a string that embeds a string in an <a> element with a name (<a name="...">str</a>) 1066 * 1067 * @returns A string beginning with an <a name="name"> start tag (double quotes in name are replaced with "), 1068 * then the text str, and then an </a> end tag. 1069 */ 1070 public anchor(name: String): String { 1071 return this.CreateHTMLString("a", " name=\"" + name + "\"") 1072 } 1073 1074 /* 1075 * The big() method creates a string that embeds a string in a <big> element (<big>str</big>), which causes a string to be displayed in a big font. 1076 */ 1077 public big(): String{ 1078 return this.CreateHTMLString("big", "") 1079 } 1080 1081 1082 /* 1083 * The small() method creates a string that embeds a string in a <small> element (<small>str</small>), which causes a string to be displayed in a big font. 1084 */ 1085 public small(): String{ 1086 return this.CreateHTMLString("small", "") 1087 } 1088 1089 /* 1090 * The blink() method creates a string that embeds a string in a <blink> element (<blink>str</blink>), which causes a string to be displayed in a big font. 1091 */ 1092 public blink(): String{ 1093 return this.CreateHTMLString("blink", "") 1094 } 1095 1096 /* 1097 * The bold() method creates a string that embeds a string in a <bold> element (<b>str</b>), which causes a string to be displayed in a big font. 1098 */ 1099 public bold(): String{ 1100 return this.CreateHTMLString("b", "") 1101 } 1102 1103 /* 1104 * The italics() method creates a string that embeds a string in a <i> element (<i>str</i>), which causes a string to be displayed in a big font. 1105 */ 1106 public italics(): String{ 1107 return this.CreateHTMLString("i", "") 1108 } 1109 1110 /* 1111 * The strike() method creates a string that embeds a string in a <strike> element (<strike>str</strike>), which causes a string to be displayed in a big font. 1112 */ 1113 public strike(): String{ 1114 return this.CreateHTMLString("strike", "") 1115 } 1116 1117 /* 1118 * The sub() method creates a string that embeds a string in a <sub> element (<sub>str</sub>), which causes a string to be displayed in a big font. 1119 */ 1120 public sub(): String{ 1121 return this.CreateHTMLString("sub", "") 1122 } 1123 1124 /* 1125 * The sup() method creates a string that embeds a string in a <sup> element (<sup>str</sup>), which causes a string to be displayed in a big font. 1126 */ 1127 public sup(): String{ 1128 return this.CreateHTMLString("sup", "") 1129 } 1130 1131 /* 1132 * The fixed() method creates a string that embeds a string in a <tt> element (<tt>str</tt>), which causes a string to be displayed in a big font. 1133 */ 1134 public fixed(): String{ 1135 return this.CreateHTMLString("tt", "") 1136 } 1137 1138 /* 1139 * The fontcolor() method creates a string that embeds a string in a <font> element (<font color="...">str</font>), which causes a string to be displayed in the specified font color. 1140 */ 1141 public fontcolor(color: String): String{ 1142 return this.CreateHTMLString("font", " color=\"" + color + "\"") 1143 } 1144 1145 /* 1146 * The fontsize() method creates a string that embeds a string in a <font> element (<font size="...">str</font>), which causes a string to be displayed in the specified font size. 1147 */ 1148 public fontsize(size: String): String { 1149 return this.CreateHTMLString("font", " size=\"" + size.replaceAll("\"", """) + "\"") 1150 } 1151 1152 /* 1153 * The fontsize() method creates a string that embeds a string in a <font> element (<font size="...">str</font>), which causes a string to be displayed in the specified font size. 1154 */ 1155 public fontsize(size: number): String { 1156 return this.CreateHTMLString("font", " size=\"" + size + "\"") 1157 } 1158 1159 /* 1160 * The fontsize() method creates a string that embeds a string in a <font> element (<font size="...">str</font>), which causes a string to be displayed in the specified font size. 1161 */ 1162 public fontsize(size: int): String{ 1163 return this.CreateHTMLString("font", " size=\"" + size + "\"") 1164 } 1165 1166 /* 1167 * The link() method creates a string that embeds a string in an <a> element (<a href="...">str</a>), to be used as a hypertext link to another URL. 1168 */ 1169 public link(link: String): String{ 1170 return this.CreateHTMLString("a", " href=\"" + link + "\"") 1171 } 1172 1173 /* 1174 * The charCodeAt() method returns an integer between 0 and 65535 representing the UTF-16 code unit at the given index. 1175 */ 1176 public charCodeAt(index: number): number { 1177 if (index < 0 || index >= this.getLength() || this.getLength() == 0) { 1178 return NaN; 1179 } 1180 return this.charAt(index as int); 1181 } 1182 1183 /* 1184 * The charCodeAt() method returns an integer between 0 and 65535 representing the UTF-16 code unit at the given index. 1185 */ 1186 public charCodeAt(index: int): char { 1187 return this.charAt(index) as char; 1188 } 1189 1190 /** 1191 * The String.fromCharCode() static method returns a string created from the specified sequence of UTF-16 code units 1192 * 1193 * @param codes are numbers between 0 and 65535 (0xFFFF) representing a UTF-16 code unit or NaN 1194 * 1195 * @returns string consisting of specified UTF-16 code units. 1196 * 1197 */ 1198 public static fromCharCode(...codes: number[]): String { 1199 let res = new StringBuilder(); 1200 for (const cu of codes) { 1201 res.append((cu % (0x10000)) as char) 1202 } 1203 return res.toString() 1204 } 1205 1206 /* 1207 * The valueOf() method returns the primitive value of a String object. 1208 */ 1209 public valueOf(): String { 1210 return this; 1211 } 1212 1213 /** 1214 * The includes() method performs a case-sensitive search to determine whether one string may 1215 * be found within another string, returning true or false as appropriate. 1216 * 1217 * @param searchString to be searched 1218 * 1219 * @param position within the string at which to begin searching for searchString 1220 * 1221 * @returns true if the search string is found anywhere within the given string, 1222 * including when searchString is an empty string; otherwise, false 1223 */ 1224 public includes(searchString: String, position?: Number): boolean { 1225 if (position == undefined) { 1226 return this.indexOf(searchString) == -1 ? false : true; 1227 } 1228 return this.indexOf(searchString, position.intValue()) == -1 ? false : true; 1229 } 1230 1231 /** 1232 * The padEnd() method pads the current string with a given string (repeated, if needed) 1233 * so that the resulting string reaches a given length. 1234 * The padding is applied from the end of the current string. 1235 * 1236 * @param maxLength of the resulting string once the current str has been padded 1237 * 1238 * @param fillString to pad the current str with 1239 * 1240 * @returns string with the padString applied at the end of the current str 1241 */ 1242 public padEnd(maxLength: number, fillString?: String): String { 1243 const str = (fillString == undefined) ? "\u0020" : fillString 1244 return this.padEnd(maxLength as int, str) 1245 } 1246 1247 /* 1248 * The padEnd() method pads the current string with a given string (repeated, if needed) 1249 * so that the resulting string reaches a given length. 1250 * The padding is applied from the end of the current string. 1251 */ 1252 public padEnd(end: int, ch: char): String { 1253 if (this.getLength() >= end) { 1254 return this; 1255 } 1256 let arr = new char[end] 1257 for (let i: int = 0; i < this.getLength(); i++) { 1258 arr[i] = this.charAt(i) 1259 } 1260 for(let i = this.getLength(); i < end; i++){ 1261 arr[i] = ch 1262 } 1263 return new String(arr) 1264 } 1265 1266 public padEnd(end: int): String { 1267 return this.padEnd(end, ' ') 1268 } 1269 1270 public padEnd(end: int, str: String): String { 1271 if (this.getLength() >= end) { 1272 return this; 1273 } 1274 let arr = new char[end]; 1275 let length = this.getLength(); 1276 let strLength = str.getLength() 1277 for (let i: int = 0; i < length; i++) { 1278 arr[i] = this.charAt(i); 1279 } 1280 for (let i = 0; i < strLength && length + i < end; i++) { 1281 arr[length + i] = str.charAt(i); 1282 } 1283 for (let i = this.getLength() + strLength; i < end; i++) { 1284 arr[i] = arr[i - strLength]; 1285 } 1286 return new String(arr); 1287 } 1288 1289 /** 1290 * The padStart() method pads the current string with another string (multiple times, if needed) 1291 * until the resulting string reaches the given length. 1292 * The padding is applied from the start of the resulting string. 1293 * 1294 * @param maxLength of the resulting string once the current str has been padded 1295 * 1296 * @param fillString to pad the current str with 1297 * 1298 * @returns string with the padString applied at the end of the current str 1299 */ 1300 public padStart(maxLength: number, fillString?: String): String { 1301 const str = (fillString == undefined) ? "\u0020" : fillString 1302 return this.padStart(maxLength as int, str) 1303 } 1304 1305 /* 1306 * The padStart() method pads the current string with another string (multiple times, if needed) 1307 * until the resulting string reaches the given length. 1308 * The padding is applied from the start of the current string. 1309 */ 1310 public padStart(end: int, ch: char): String { 1311 let padLen = end - this.getLength(); 1312 if (padLen <= 0) { 1313 return this; 1314 } 1315 let arr: char[] = new char[end] 1316 for(let i = 0; i < padLen; i++){ 1317 arr[i] = ch 1318 } 1319 for (let i: int = 0; i < this.getLength(); i++) { 1320 arr[padLen + i] = this.charAt(i); 1321 } 1322 return new String(arr) 1323 } 1324 1325 public padStart(end: int): String { 1326 return this.padStart(end, ' ') 1327 } 1328 1329 public padStart(end: int, str: String): String { 1330 if (this.getLength() >= end) { 1331 return this; 1332 } 1333 let arr: char[] = new char[end]; 1334 let padLen: int = end - this.getLength(); 1335 for (let i: int = 0; i < this.getLength(); i++) { 1336 arr[padLen + i] = this.charAt(i); 1337 } 1338 let strLength = str.getLength() 1339 for (let i = 0; i < strLength && i < padLen; i++) { 1340 arr[i] = str.charAt(i); 1341 } 1342 for (let i = strLength; i < padLen; i++) { 1343 arr[i] = arr[i - strLength]; 1344 } 1345 return new String(arr); 1346 } 1347 1348 /** 1349 * The substr() method returns a portion of the string, starting at the specified 1350 * index and extending for a given number of characters afterwards. 1351 * 1352 * @param begin is index of the first character to include in the returned substring 1353 * 1354 * @param length is number of characters to extract 1355 * 1356 * @returns new string containing the specified part of the given string 1357 */ 1358 public substr(begin: number, length?: Number): String { 1359 return this.substr(begin as int, asIntOrDefault(length, this.getLength())); 1360 } 1361 1362 public substr(begin: number, length: number): String { 1363 return this.substr(begin as int, length as int); 1364 } 1365 1366 /* 1367 * The substr() method returns a portion of the string, starting at the specified 1368 * index and extending for a given number of characters afterwards. 1369 */ 1370 public substr(begin: int): String { 1371 return this.substr(begin, this.getLength() as int); 1372 } 1373 1374 public substr(begin: int, length: int): String { 1375 begin = normalizeIndex(begin, this.getLength()); 1376 let end = begin + length 1377 if (length > this.getLength() - begin) { 1378 end = this.getLength() 1379 } 1380 if (end <= begin) { 1381 return ""; 1382 } 1383 return this.substring(begin, end); 1384 } 1385 1386 /* 1387 * The trimEnd() method removes whitespace from the end of a string and returns a new string, 1388 * without modifying the original string. trimRight() is an alias of this method. 1389 */ 1390 public trimEnd(): String { 1391 return this.trimRight() 1392 } 1393 1394 /* 1395 * The trimStart() method removes whitespace from the beginning of a string and returns a new string, 1396 * without modifying the original string. trimLeft() is an alias of this method. 1397 */ 1398 public trimStart(): String { 1399 return this.trimLeft() 1400 } 1401 1402 /* 1403 * The slice() method extracts a section of a string and returns it as a new string, 1404 * without modifying the original string. 1405 */ 1406 public slice(begin?: Number, end?: Number): String { 1407 return this.slice(asIntOrDefault(begin, 0), asIntOrDefault(end, this.getLength())) 1408 } 1409 1410 /* 1411 * The slice() method extracts a section of a string and returns it as a new string, 1412 * without modifying the original string. 1413 */ 1414 public slice(begin: int): String { 1415 return this.substring(normalizeIndex(begin, this.getLength())) 1416 } 1417 1418 //public slice(begin: number, end: number): String { 1419 // return this.slice(begin as int, end as int) 1420 //} 1421 1422 public slice(begin: int, end: int): String { 1423 begin = normalizeIndex(begin, this.getLength()) 1424 end = normalizeIndex(end, this.getLength()) 1425 if (end <= begin) { 1426 return ""; 1427 } 1428 return this.substring(begin, end) 1429 } 1430 1431 //NOTE(kirill-mitkin): Replace namedCaptures type with record type when it will be possible 1432 static getSubstitution(matched: String, str: String, position: int, captures: String[], namedCaptures: Object | undefined, replacement: String) { 1433 let matchLength = matched.getLength() 1434 let stringLength = str.getLength() 1435 assert(position >= 0 && position <= stringLength) 1436 let tailPos = position + matchLength 1437 let m = captures.length 1438 let result = "" 1439 let doubleCapture = true; 1440 for (let i: int = 0; i < replacement.getLength();) { 1441 if (i + 1 < replacement.getLength() 1442 && replacement.charAt(i) == c'$' 1443 && replacement.charAt(i + 1) == c'$') { 1444 result += c'$' 1445 i += 2 1446 } else if (i + 1 < replacement.getLength() 1447 && replacement.charAt(i) == c'$' 1448 && replacement.charAt(i + 1) == c'&') { 1449 result += matched 1450 i += 2 1451 } else if (i + 1 < replacement.getLength() 1452 && replacement.charAt(i) == c'$' 1453 && replacement.charAt(i + 1) == c'`') { 1454 if (position != 0) { 1455 result += str.substring(0, position) 1456 } 1457 i += 2 1458 } else if (i + 1 < replacement.getLength() 1459 && replacement.charAt(i) == c'$' 1460 && replacement.charAt(i + 1) == c'\'') { 1461 if (tailPos < stringLength) { 1462 result += str.substring(tailPos, stringLength) 1463 } 1464 i += 2 1465 } else if (i + 2 < replacement.getLength() 1466 && doubleCapture 1467 && replacement.charAt(i) == c'$' 1468 && Char.isDecDigit(replacement.charAt(i + 1)) 1469 && Char.isDecDigit(replacement.charAt(i + 2))) { 1470 let firstDigit = replacement.charAt(i + 1) - c'0'; 1471 let secondDigit = replacement.charAt(i + 2) - c'0'; 1472 let digit = firstDigit * 10 + secondDigit; 1473 if (digit == 0 || digit > m) { 1474 doubleCapture = false; 1475 } else { 1476 result += captures[digit - 1]; 1477 i += 3 1478 } 1479 } else if (i + 2 < replacement.getLength() 1480 && replacement.charAt(i) == c'$' 1481 && Char.isDecDigit(replacement.charAt(i + 1)) 1482 && !Char.isDecDigit(replacement.charAt(i + 2)) 1483 || i + 1 < replacement.getLength() 1484 && replacement.charAt(i) == c'$' 1485 && Char.isDecDigit(replacement.charAt(i + 1))) { 1486 let digit = replacement.charAt(i + 1) - c'0'; 1487 if (digit == 0 || digit > m) { 1488 result += replacement.substring(i, i + 2) 1489 } else { 1490 result += captures[digit - 1]; 1491 } 1492 doubleCapture = true; 1493 i += 2 1494 } else if (i + 1 < replacement.getLength() 1495 && replacement.charAt(i) == c'$' 1496 && replacement.charAt(i + 1) == c'<') { 1497 if (namedCaptures == undefined) { 1498 result += "$<"; 1499 i += 2; 1500 } else { 1501 let j = i + 2; 1502 for (; j < replacement.getLength() && replacement.charAt(j) != c'>'; ++j) {} 1503 if (j < replacement.getLength()) { 1504 let groupName = replacement.substring(i + 2, j); 1505 /*let capture = namedCaptures[groupName]; 1506 if (capture != undefined) { 1507 result += capture 1508 } 1509 */ 1510 i = j; 1511 } else { 1512 result += "$<"; 1513 i += 2; 1514 } 1515 } 1516 } else { 1517 result += replacement.charAt(i); 1518 i += 1; 1519 } 1520 } 1521 return result 1522 } 1523 1524 /** 1525 * Returns a new string with one, some, or all matches of a pattern replaced by a replacement 1526 * 1527 * @param searchValue is pattern which can be a String or RegExp 1528 * 1529 * @param replaceValue is replacement String 1530 * 1531 * @returns a new replaced string 1532 */ 1533 public replace(searchValue: StringOrRegExp, replaceValue: String) : String { 1534 if (searchValue instanceof RegExp) { 1535 return (searchValue as RegExp).replace(this, replaceValue) 1536 } 1537 return this.replace(searchValue as String, replaceValue) 1538 } 1539 1540 public replace(searchValue: RegExp, replaceValue: String) : String { 1541 return searchValue.replace(this, replaceValue) 1542 } 1543 1544 public replace(searchValue: String, replaceValue: String) : String { 1545 let searchLength = searchValue.getLength() 1546 let position = this.indexOf(searchValue) 1547 if (position == -1) { 1548 return this 1549 } 1550 let preceding = this.substring(0, position) 1551 let following = this.substring(position + searchLength) 1552 let replacement = String.getSubstitution(searchValue, this, position as int, new String[0], undefined, replaceValue) 1553 return preceding + replacement + following 1554 } 1555 1556 /** 1557 * Returns a new string with one, some, or all matches of a pattern replaced by a replacement 1558 * 1559 * @param searchValue is pattern which can be a String or RegExp 1560 * 1561 * @param replacer is replacement function 1562 * 1563 * @returns a new replaced string 1564 */ 1565 public replace(searchValue: StringOrRegExp, replacer: (substr: String, args: Object[]) => String): String { 1566 if (searchValue instanceof RegExp) { 1567 return (searchValue as RegExp).replace(this, replacer) 1568 } 1569 return this.replace(searchValue as String, replacer) 1570 } 1571 1572 public replace(searchValue: RegExp, replacer: (substr: String, args: Object[]) => String): String { 1573 return searchValue.replace(this, replacer) 1574 } 1575 1576 public replace(searchValue: String, replacer: (substr: String, args: Object[]) => String): String { 1577 let searchLength = searchValue.getLength() 1578 let position = this.indexOf(searchValue) 1579 if (position == -1) { 1580 return this 1581 } 1582 let preceding = this.substring(0, position) 1583 let following = this.substring(position + searchLength) 1584 let replacement = replacer(searchValue, [new Double(position as double) as Object, this as Object] as Object[]) 1585 return preceding + replacement + following 1586 } 1587 1588 /** 1589 * Returns a new string with all matches of a pattern replaced by a replacement 1590 * 1591 * @param searchValue is pattern which can be a String or RegExp 1592 * 1593 * @param replaceValue is replacement String 1594 * 1595 * @returns a new replaced string 1596 */ 1597 public replaceAll(searchValue: StringOrRegExp, replaceValue: String): String { 1598 if (searchValue instanceof RegExp) { 1599 const re = searchValue as RegExp 1600 if (!re.global) { 1601 throw new Error("Global flag expected for regexp") 1602 } 1603 return re.replace(this, replaceValue) 1604 } 1605 return this.replaceAll(searchValue as String, replaceValue) 1606 } 1607 1608 public replaceAll(searchValue: RegExp, replaceValue: String): String { 1609 if (!searchValue.global) { 1610 throw new Error("Global flag expected for regexp") 1611 } 1612 return searchValue.replace(this, replaceValue) 1613 } 1614 1615 public replaceAll(searchValue: String, replaceValue: String): String { 1616 let searchLength = searchValue.getLength() 1617 let advanceBy = max(1, searchLength) 1618 let matchPositions = new ArrayAsListInt(); 1619 let position = this.indexOf(searchValue, 0) 1620 while (position != -1) { 1621 matchPositions.pushBack(position) 1622 if (position == this.getLength()) { 1623 break; 1624 } 1625 position = this.indexOf(searchValue, position + advanceBy) 1626 } 1627 let endOfLastMatch = 0 1628 let result = "" 1629 let arrayMatchPositions = matchPositions.toArray() 1630 for (let i = 0; i < arrayMatchPositions.length; ++i) { 1631 let p = arrayMatchPositions[i].unboxed() 1632 let preserved = this.substring(endOfLastMatch, p) 1633 let replacement = String.getSubstitution(searchValue, this, p, new String[0], undefined, replaceValue) 1634 result = result + preserved + replacement 1635 endOfLastMatch = p + searchLength 1636 } 1637 if (endOfLastMatch < this.getLength()) { 1638 result += this.substring(endOfLastMatch) 1639 } 1640 return result 1641 } 1642 1643 /** 1644 * Returns a new string with all matches of a pattern replaced by a replacement 1645 * 1646 * @param searchValue is pattern which can be a String or RegExp 1647 * 1648 * @param replacer is replacement function 1649 * 1650 * @returns a new replaced string 1651 */ 1652 public replaceAll(searchValue: StringOrRegExp, replacer: (substr: String, args: Object[]) => String): String { 1653 if (searchValue instanceof RegExp) { 1654 const re = searchValue as RegExp 1655 if (!re.global) { 1656 throw new Error("Global flag expected for regexp") 1657 } 1658 return re.replace(this, replacer) 1659 } 1660 return this.replaceAll(searchValue as String, replacer) 1661 } 1662 1663 public replaceAll(searchValue: RegExp, replacer: (substr: String, args: Object[]) => String): String { 1664 if (!searchValue.global) { 1665 throw new Error("Global flag expected for regexp") 1666 } 1667 return searchValue.replace(this, replacer) 1668 } 1669 1670 public replaceAll(searchValue: String, replacer: (substr: String, args: Object[]) => String): String { 1671 let searchLength = searchValue.getLength() 1672 let advanceBy = max(1, searchLength) 1673 let matchPositions = new ArrayAsListInt(); 1674 let position = this.indexOf(searchValue, 0) 1675 while (position != -1) { 1676 matchPositions.pushBack(position) 1677 position = this.indexOf(searchValue, position + advanceBy) 1678 } 1679 let endOfLastMatch = 0 1680 let result = "" 1681 let arrayMatchPositions = matchPositions.toArray() 1682 for (let i = 0; i < arrayMatchPositions.length; ++i) { 1683 let p = arrayMatchPositions[i].unboxed() 1684 let preserved = this.substring(endOfLastMatch, p) 1685 let args = new NullableObjectArray(2) 1686 args.pushBack(Double.valueOf(p)) 1687 args.pushBack(this) 1688 let replacement = replacer(searchValue, args.toArray() as Object[]) 1689 result = result + preserved + replacement 1690 endOfLastMatch = p + searchLength 1691 } 1692 if (endOfLastMatch < this.getLength()) { 1693 result += this.substring(endOfLastMatch) 1694 } 1695 return result 1696 } 1697 1698 /** 1699 * Executes a search for a match between a regular expression and this String object. 1700 * 1701 * @param regexp a regular expression object or implicit regular expression 1702 * 1703 * @returns the index of the first match between the regular expression and the given string, 1704 * or -1 if no match was found. 1705 */ 1706 public search(regexp: StringOrRegExp): number { 1707 if (regexp instanceof String) { 1708 return new RegExp(regexp as String).search(this) 1709 } 1710 return (regexp as RegExp).search(this) 1711 } 1712 1713 public search(implicitRegExp: String): number { 1714 return new RegExp(implicitRegExp).search(this) 1715 } 1716 1717 public search(regexp: RegExp): number { 1718 return regexp.search(this) 1719 } 1720 1721 /* 1722 * The toLocaleLowerCase() method returns the calling string value converted to lower case, 1723 * according to any locale-specific case mappings. 1724 */ 1725 public native toLocaleLowerCase(locale: String): String; 1726 1727 public toLocaleLowerCase(): String { 1728 return this.toLocaleLowerCase(""); 1729 } 1730 1731 /* 1732 * The toLocaleUpperCase() method returns the calling string value converted to upper case, 1733 * according to any locale-specific case mappings. 1734 */ 1735 public native toLocaleUpperCase(locale: String): String; 1736 1737 public toLocaleUpperCase(): String { 1738 return this.toLocaleUpperCase(""); 1739 } 1740 /** 1741 * Retrieves the result of matching a string against a regular expression 1742 * 1743 * @param regexp a regular expression object 1744 * 1745 * @returns 1746 * If the regexp.global is true, all results matching the complete regular expression will be returned, 1747 * but capturing groups are not included 1748 * Otherwise, only the first complete match and its related capturing groups are returned 1749 */ 1750 public match(regexp: StringOrRegExp): RegExpMatchArray | null { 1751 if (regexp instanceof String) { 1752 return new RegExp(regexp as String).match(this) 1753 } 1754 return (regexp as RegExp).match(this) 1755 } 1756 1757 public match(implicitRegExp: String): RegExpMatchArray | null { 1758 return new RegExp(implicitRegExp).match(this) 1759 } 1760 1761 public match(regexp: RegExp): RegExpMatchArray | null { 1762 return regexp.match(this) 1763 } 1764 1765 1766 /** 1767 * Returns an iterator of all results matching a string against a regular expression, 1768 * including capturing groups 1769 * 1770 * @param regexp a regular expression object 1771 */ 1772 public matchAll(reg: RegExp): IterableIterator<RegExpMatchArray> { 1773 let flags = reg.flags; 1774 if (!reg.global) { 1775 throw new Error("matchAll must be called with a global RegExp") 1776 } 1777 return reg.matchAll(this) 1778 } 1779 1780 public native normalizeNFC(): String; 1781 1782 public native normalizeNFD(): String; 1783 1784 public native normalizeNFKC(): String; 1785 1786 public native normalizeNFKD(): String; 1787 1788 /** 1789 * The normalize() method of String values returns the Unicode Normalization Form of this string 1790 * 1791 * @param form is "NFC" or "NFD" or "NFKC" or "NFKD" 1792 * 1793 * @throws RangeError if form is not "NFC" or "NFD" or "NFKC" or "NFKD" 1794 * 1795 * @returns the Unicode Normalization Form of the string 1796 */ 1797 public normalize(form?: String): String { 1798 const f = (form == undefined) ? "NFC" : form 1799 switch (f) { 1800 case "NFC": 1801 return this.normalizeNFC() 1802 case "NFD": 1803 return this.normalizeNFD() 1804 case "NFKC": 1805 return this.normalizeNFKC() 1806 case "NFKD": 1807 return this.normalizeNFKD() 1808 default: 1809 throw new RangeError("The normalization form should be one of NFC, NFD, NFKC, NFKD.") 1810 } 1811 } 1812 1813 /* 1814 * The toWellFormed() method of String values returns a string where all lone surrogates of 1815 * this string are replaced with the Unicode replacement character U+FFFD. 1816 */ 1817 public toWellFormed(): String { 1818 return this.normalize() 1819 } 1820 1821 private static native codePointToChar(cp: int): int 1822 1823 /** 1824 * The String.fromCodePoint() static method returns a string created by using the specified sequence of code points 1825 * 1826 * @param codePoints are integers between 0 and 0x10FFFF (inclusive) representing a Unicode code point 1827 * 1828 * @throws RangeError if codePoints[i] is less than 0, or is greater than 0x10FFFF 1829 * 1830 * @returns string created by using the specified sequence of code points 1831 */ 1832 public static fromCodePoint(...codePoints: number[]): String { 1833 let res = new StringBuilder(); 1834 for (const cp of codePoints) { 1835 if (cp < 0 || cp > MAX_CODE_POINT || isNaN(cp) || !Number.isInteger(cp)) { 1836 throw new RangeError("Invalid code point: " + new Number(cp).toString()) 1837 } 1838 let chrs = String.codePointToChar(cp as int); 1839 res.append((chrs & 0xffff) as char) 1840 chrs = (chrs >> 16) & 0xffff 1841 if (chrs > 0) { 1842 res.append(chrs as char); 1843 } 1844 } 1845 return res.toString() 1846 } 1847 1848 /* 1849 * The isWellFormed() method of String values returns a boolean indicating whether this string contains any lone surrogates. 1850 */ 1851 public native isWellFormed(): boolean; 1852 1853 /** 1854 * Creates a String instance based on JSONValue 1855 * 1856 * @param json: JSONValue - a JSON representation 1857 * 1858 * @throws JSONTypeError if json does not encode a valid String 1859 * 1860 * @returns String - string value decoded from JSON 1861 */ 1862 public createFromJSONValue(json: JSONValue): String { 1863 if (json instanceof JSONString) { 1864 return (json as JSONString).value 1865 } 1866 throw new JSONTypeError("Cannot create String from JSON", new ErrorOptions(json as Object)) 1867 } 1868 1869 /** 1870 * Check if a string is compressed 1871 * 1872 * @param s string to be checked 1873 * 1874 * @returns true - if s is compressed, false - otherwise 1875 */ 1876 public native isCompressed(): boolean; 1877 1878 public override $_iterator(): IterableIterator<String> { 1879 return new StringIterator(this) 1880 } 1881} 1882 1883class StringIterator implements IterableIterator<string> { 1884 private s: String 1885 private idx: int = 0 1886 1887 constructor(s: String) { 1888 this.s = s 1889 } 1890 1891 public override next(): IteratorResult<String> { 1892 if (this.idx == this.s.getLength()) { 1893 return new IteratorResult<String>() 1894 } 1895 let highValue: char = this.s.charAt(this.idx); 1896 this.idx += 1 1897 if (!Char.isHighSurrogate(highValue) || this.idx == this.s.getLength()) { 1898 return new IteratorResult<String>(String.fromCharCode(highValue as number)); 1899 } 1900 let lowValue: char = this.s.charAt(this.idx); 1901 if (!Char.isLowSurrogate(lowValue)) { 1902 return new IteratorResult<String>(String.fromCharCode(highValue as number)); 1903 } 1904 this.idx += 1 1905 return new IteratorResult<String>(String.fromCharCode(highValue as number, lowValue as number)); 1906 } 1907} 1908