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