diff --git a/libraries/stdlib/src/kotlin/text/CharJVM.kt b/libraries/stdlib/src/kotlin/text/CharJVM.kt index 1c92652b348..9936dd98f71 100644 --- a/libraries/stdlib/src/kotlin/text/CharJVM.kt +++ b/libraries/stdlib/src/kotlin/text/CharJVM.kt @@ -59,9 +59,10 @@ deprecated("Please use Char.isJavaIdentifierPart() instead") public fun Char.isJavaLetterOrDigit(): Boolean = Character.isJavaLetterOrDigit(this) /** + * Determines whether a character is whitespace according to the Unicode standard. * Returns `true` if the character is whitespace. */ -public fun Char.isWhitespace(): Boolean = Character.isWhitespace(this) +public fun Char.isWhitespace(): Boolean = Character.isWhitespace(this) || Character.isSpaceChar(this) /** * Returns `true` if this character is upper case. diff --git a/libraries/stdlib/src/kotlin/text/Strings.kt b/libraries/stdlib/src/kotlin/text/Strings.kt index 7bfa4ab5eeb..cce8ec7a123 100644 --- a/libraries/stdlib/src/kotlin/text/Strings.kt +++ b/libraries/stdlib/src/kotlin/text/Strings.kt @@ -6,6 +6,56 @@ public fun String.trim(text: String): String = trimLeading(text).trimTrailing(te /** Returns the string with the prefix and postfix text trimmed */ public fun String.trim(prefix: String, postfix: String): String = trimLeading(prefix).trimTrailing(postfix) +/** + * Returns the string with leading and trailing characters matching the [predicate] trimmed. + */ +inline public fun String.trim(predicate: (Char) -> Boolean): String { + val indices = this.indices + for (startIndex in indices) + if (!predicate(this[startIndex])) + for (endIndex in indices.reversed()) + if (!predicate(this[endIndex])) + return substring(startIndex, endIndex + 1) + return "" +} + +/** + * Returns the string with leading characters matching the [predicate] trimmed. + */ +inline public fun String.trimLeading(predicate: (Char) -> Boolean): String { + for (index in this.indices) + if (!predicate(this[index])) + return substring(index) + + return "" +} + +/** + * Returns the string with trailing characters matching the [predicate] trimmed. + */ +inline public fun String.trimTrailing(predicate: (Char) -> Boolean): String { + for (index in this.indices.reversed()) + if (!predicate(this[index])) + return substring(0, index + 1) + + return "" +} + +/** + * Returns the string with leading and trailing characters in the [chars] array trimmed. + */ +public fun String.trim(vararg chars: Char): String = trim { it in chars } + +/** + * Returns the string with leading and trailing characters in the [chars] array trimmed. + */ +public fun String.trimLeading(vararg chars: Char): String = trimLeading { it in chars } + +/** + * Returns the string with trailing characters in the [chars] array trimmed. + */ +public fun String.trimTrailing(vararg chars: Char): String = trimTrailing { it in chars } + /** * If this string starts with the given [prefix], returns a copy of this string * with the prefix removed. Otherwise, returns this string. @@ -31,27 +81,56 @@ public fun String.trimTrailing(postfix: String): String { } /** - * Returns a copy of this String with leading whitespace removed. + * Returns a string with leading and trailing whitespace trimmed. */ -public fun String.trimLeading(): String { - var count = 0 +public fun String.trim(): String = trim { it.isWhitespace() } - while ((count < this.length) && (this[count] <= ' ')) { - count++ - } - return if (count > 0) substring(count) else this +/** + * Returns a string with leading whitespace removed. + */ +public fun String.trimLeading(): String = trimLeading { it.isWhitespace() } + +/** + * Returns a string with trailing whitespace removed. + */ +public fun String.trimTrailing(): String = trimTrailing { it.isWhitespace() } + +/** + * Left pad a String with a specified character or space. + * + * @param length the desired string length. + * @param padChar the character to pad string with, if it has length less than the [length] specified. Space is used by default. + */ +public fun String.padLeft(length: Int, padChar: Char = ' '): String { + if (length < 0) + throw IllegalArgumentException("String length $length is less than zero.") + if (length <= this.length()) + return this + + val sb = StringBuilder(length) + for (i in 1..(length - this.length())) + sb.append(padChar) + sb.append(this) + return sb.toString() } /** - * Returns a copy of this String with trailing whitespace removed. + * Right pad a String with a specified character or space. + * + * @param length the desired string length. + * @param padChar the character to pad string with, if it has length less than the [length] specified. Space is used by default. */ -public fun String.trimTrailing(): String { - var count = this.length +public fun String.padRight(length: Int, padChar: Char = ' '): String { + if (length < 0) + throw IllegalArgumentException("String length $length is less than zero.") + if (length <= this.length()) + return this - while (count > 0 && this[count - 1] <= ' ') { - count-- - } - return if (count < this.length) substring(0, count) else this + val sb = StringBuilder(length) + sb.append(this) + for (i in 1..(length - this.length())) + sb.append(padChar) + return sb.toString() } /** Returns true if the string is not null and not empty */ diff --git a/libraries/stdlib/src/kotlin/text/StringsJVM.kt b/libraries/stdlib/src/kotlin/text/StringsJVM.kt index 56048ec262b..04934a0c1cf 100644 --- a/libraries/stdlib/src/kotlin/text/StringsJVM.kt +++ b/libraries/stdlib/src/kotlin/text/StringsJVM.kt @@ -48,11 +48,6 @@ public fun String.replace(oldChar: Char, newChar: Char): String = (this as java. */ public fun String.replaceAll(regex: String, replacement: String): String = (this as java.lang.String).replaceAll(regex, replacement) -/** - * Returns a copy of this string with leading and trailing whitespace trimmed. - */ -public fun String.trim(): String = (this as java.lang.String).trim() - /** * Returns a copy of this string converted to upper case using the rules of the default locale. */ diff --git a/libraries/stdlib/test/text/StringTest.kt b/libraries/stdlib/test/text/StringTest.kt index 4b3982f3420..9f520fbd8f6 100644 --- a/libraries/stdlib/test/text/StringTest.kt +++ b/libraries/stdlib/test/text/StringTest.kt @@ -143,11 +143,15 @@ class StringTest { assertEquals("a ", " a ".trimLeading()) assertEquals("a b", " a b".trimLeading()) assertEquals("a b ", " a b ".trimLeading()) + assertEquals("a", " \u00A0 a".trimLeading()) assertEquals("a", "\ta".trimLeading()) assertEquals("a", "\t\ta".trimLeading()) assertEquals("a", "\ra".trimLeading()) assertEquals("a", "\na".trimLeading()) + + assertEquals("a=", "-=-=a=".trimLeading('-','=')) + assertEquals("123a", "ab123a".trimLeading { !it.isDigit() }) } test fun trimTrailing() { @@ -158,11 +162,15 @@ class StringTest { assertEquals(" a", " a ".trimTrailing()) assertEquals("a b", "a b ".trimTrailing()) assertEquals(" a b", " a b ".trimTrailing()) + assertEquals("a", "a \u00A0 ".trimTrailing()) assertEquals("a", "a\t".trimTrailing()) assertEquals("a", "a\t\t".trimTrailing()) assertEquals("a", "a\r".trimTrailing()) assertEquals("a", "a\n".trimTrailing()) + + assertEquals("=a", "=a=-=-".trimTrailing('-','=')) + assertEquals("ab123", "ab123a".trimTrailing { !it.isDigit() }) } test fun trimTrailingAndLeading() { @@ -174,12 +182,46 @@ class StringTest { "\ta\tb\t", "\t\ta\t\t", "\ra\r", - "\na\n" + "\na\n", + " \u00A0 a \u00A0 " ) for (example in examples) { assertEquals(example.trim(), example.trimTrailing().trimLeading()) assertEquals(example.trim(), example.trimLeading().trimTrailing()) } + + val examplesForPredicate = array( + "123", + "-=123=-" + ) + + val trimChars = charArray('-','=') + val trimPredicate = { (it: Char) -> !it.isDigit() } + for (example in examplesForPredicate) { + assertEquals(example.trimLeading(*trimChars).trimTrailing(*trimChars), example.trim(*trimChars)) + assertEquals(example.trimLeading(trimPredicate).trimTrailing(trimPredicate), example.trim(trimPredicate)) + } } + + test fun padLeft() { + assertEquals("s", "s".padLeft(0)) + assertEquals("s", "s".padLeft(1)) + assertEquals(" ", "".padLeft(2)) + assertEquals("--s", "s".padLeft(3, '-')) + fails { + "s".padLeft(-1) + } + } + + test fun padRight() { + assertEquals("s", "s".padRight(0)) + assertEquals("s", "s".padRight(1)) + assertEquals(" ", "".padRight(2)) + assertEquals("s--", "s".padRight(3, '-')) + fails { + "s".padRight(-1) + } + } + }