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