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