diff --git a/libraries/stdlib/api/js-v1/kotlin.text.kt b/libraries/stdlib/api/js-v1/kotlin.text.kt index be5c956b7d8..dd3c70a7674 100644 --- a/libraries/stdlib/api/js-v1/kotlin.text.kt +++ b/libraries/stdlib/api/js-v1/kotlin.text.kt @@ -382,6 +382,21 @@ public fun kotlin.CharSequence.lineSequence(): kotlin.sequences.Sequence +@kotlin.SinceKotlin(version = "1.4") +@kotlin.ExperimentalStdlibApi +@kotlin.internal.InlineOnly +public inline fun kotlin.Char.lowercase(): kotlin.String + +@kotlin.SinceKotlin(version = "1.4") +@kotlin.ExperimentalStdlibApi +@kotlin.internal.InlineOnly +public inline fun kotlin.String.lowercase(): kotlin.String + +@kotlin.SinceKotlin(version = "1.4") +@kotlin.ExperimentalStdlibApi +@kotlin.internal.InlineOnly +public inline fun kotlin.Char.lowercaseChar(): kotlin.Char + public inline fun kotlin.CharSequence.map(transform: (kotlin.Char) -> R): kotlin.collections.List public inline fun kotlin.CharSequence.mapIndexed(transform: (index: kotlin.Int, kotlin.Char) -> R): kotlin.collections.List @@ -659,6 +674,20 @@ public fun kotlin.String.replaceFirst(oldChar: kotlin.Char, newChar: kotlin.Char public fun kotlin.String.replaceFirst(oldValue: kotlin.String, newValue: kotlin.String, ignoreCase: kotlin.Boolean = ...): kotlin.String +@kotlin.SinceKotlin(version = "1.4") +@kotlin.ExperimentalStdlibApi +@kotlin.OverloadResolutionByLambdaReturnType +@kotlin.jvm.JvmName(name = "replaceFirstCharWithChar") +@kotlin.internal.InlineOnly +public inline fun kotlin.String.replaceFirstChar(transform: (kotlin.Char) -> kotlin.Char): kotlin.String + +@kotlin.SinceKotlin(version = "1.4") +@kotlin.ExperimentalStdlibApi +@kotlin.OverloadResolutionByLambdaReturnType +@kotlin.jvm.JvmName(name = "replaceFirstCharWithCharSequence") +@kotlin.internal.InlineOnly +public inline fun kotlin.String.replaceFirstChar(transform: (kotlin.Char) -> kotlin.CharSequence): kotlin.String + public fun kotlin.String.replaceIndent(newIndent: kotlin.String = ...): kotlin.String public fun kotlin.String.replaceIndentByMargin(newIndent: kotlin.String = ..., marginPrefix: kotlin.String = ...): kotlin.String @@ -1079,6 +1108,20 @@ public inline fun kotlin.String.trimStart(predicate: (kotlin.Char) -> kotlin.Boo public fun kotlin.String.trimStart(vararg chars: kotlin.Char): kotlin.String +@kotlin.SinceKotlin(version = "1.4") +@kotlin.ExperimentalStdlibApi +@kotlin.internal.InlineOnly +public inline fun kotlin.Char.uppercase(): kotlin.String + +@kotlin.SinceKotlin(version = "1.4") +@kotlin.ExperimentalStdlibApi +@kotlin.internal.InlineOnly +public inline fun kotlin.String.uppercase(): kotlin.String + +@kotlin.SinceKotlin(version = "1.4") +@kotlin.ExperimentalStdlibApi +public fun kotlin.Char.uppercaseChar(): kotlin.Char + @kotlin.SinceKotlin(version = "1.2") public fun kotlin.CharSequence.windowed(size: kotlin.Int, step: kotlin.Int = ..., partialWindows: kotlin.Boolean = ...): kotlin.collections.List diff --git a/libraries/stdlib/api/js/kotlin.text.kt b/libraries/stdlib/api/js/kotlin.text.kt index be5c956b7d8..dd3c70a7674 100644 --- a/libraries/stdlib/api/js/kotlin.text.kt +++ b/libraries/stdlib/api/js/kotlin.text.kt @@ -382,6 +382,21 @@ public fun kotlin.CharSequence.lineSequence(): kotlin.sequences.Sequence +@kotlin.SinceKotlin(version = "1.4") +@kotlin.ExperimentalStdlibApi +@kotlin.internal.InlineOnly +public inline fun kotlin.Char.lowercase(): kotlin.String + +@kotlin.SinceKotlin(version = "1.4") +@kotlin.ExperimentalStdlibApi +@kotlin.internal.InlineOnly +public inline fun kotlin.String.lowercase(): kotlin.String + +@kotlin.SinceKotlin(version = "1.4") +@kotlin.ExperimentalStdlibApi +@kotlin.internal.InlineOnly +public inline fun kotlin.Char.lowercaseChar(): kotlin.Char + public inline fun kotlin.CharSequence.map(transform: (kotlin.Char) -> R): kotlin.collections.List public inline fun kotlin.CharSequence.mapIndexed(transform: (index: kotlin.Int, kotlin.Char) -> R): kotlin.collections.List @@ -659,6 +674,20 @@ public fun kotlin.String.replaceFirst(oldChar: kotlin.Char, newChar: kotlin.Char public fun kotlin.String.replaceFirst(oldValue: kotlin.String, newValue: kotlin.String, ignoreCase: kotlin.Boolean = ...): kotlin.String +@kotlin.SinceKotlin(version = "1.4") +@kotlin.ExperimentalStdlibApi +@kotlin.OverloadResolutionByLambdaReturnType +@kotlin.jvm.JvmName(name = "replaceFirstCharWithChar") +@kotlin.internal.InlineOnly +public inline fun kotlin.String.replaceFirstChar(transform: (kotlin.Char) -> kotlin.Char): kotlin.String + +@kotlin.SinceKotlin(version = "1.4") +@kotlin.ExperimentalStdlibApi +@kotlin.OverloadResolutionByLambdaReturnType +@kotlin.jvm.JvmName(name = "replaceFirstCharWithCharSequence") +@kotlin.internal.InlineOnly +public inline fun kotlin.String.replaceFirstChar(transform: (kotlin.Char) -> kotlin.CharSequence): kotlin.String + public fun kotlin.String.replaceIndent(newIndent: kotlin.String = ...): kotlin.String public fun kotlin.String.replaceIndentByMargin(newIndent: kotlin.String = ..., marginPrefix: kotlin.String = ...): kotlin.String @@ -1079,6 +1108,20 @@ public inline fun kotlin.String.trimStart(predicate: (kotlin.Char) -> kotlin.Boo public fun kotlin.String.trimStart(vararg chars: kotlin.Char): kotlin.String +@kotlin.SinceKotlin(version = "1.4") +@kotlin.ExperimentalStdlibApi +@kotlin.internal.InlineOnly +public inline fun kotlin.Char.uppercase(): kotlin.String + +@kotlin.SinceKotlin(version = "1.4") +@kotlin.ExperimentalStdlibApi +@kotlin.internal.InlineOnly +public inline fun kotlin.String.uppercase(): kotlin.String + +@kotlin.SinceKotlin(version = "1.4") +@kotlin.ExperimentalStdlibApi +public fun kotlin.Char.uppercaseChar(): kotlin.Char + @kotlin.SinceKotlin(version = "1.2") public fun kotlin.CharSequence.windowed(size: kotlin.Int, step: kotlin.Int = ..., partialWindows: kotlin.Boolean = ...): kotlin.collections.List diff --git a/libraries/stdlib/common/src/kotlin/TextH.kt b/libraries/stdlib/common/src/kotlin/TextH.kt index 6c4c9a7172f..10e88020a10 100644 --- a/libraries/stdlib/common/src/kotlin/TextH.kt +++ b/libraries/stdlib/common/src/kotlin/TextH.kt @@ -5,8 +5,6 @@ package kotlin.text -import kotlin.internal.LowPriorityInOverloadResolution - expect class Regex { constructor(pattern: String) constructor(pattern: String, option: RegexOption) @@ -66,8 +64,6 @@ expect enum class RegexOption { // From char.kt expect fun Char.isWhitespace(): Boolean -expect fun Char.toLowerCase(): Char -expect fun Char.toUpperCase(): Char expect fun Char.isHighSurrogate(): Boolean expect fun Char.isLowSurrogate(): Boolean @@ -195,39 +191,6 @@ internal expect fun String.nativeLastIndexOf(str: String, fromIndex: Int): Int public expect fun String.substring(startIndex: Int): String public expect fun String.substring(startIndex: Int, endIndex: Int): String -/** - * Returns a copy of this string converted to upper case using the rules of the default locale. - * - * @sample samples.text.Strings.toUpperCase - */ -public expect fun String.toUpperCase(): String - -/** - * Returns a copy of this string converted to lower case using the rules of the default locale. - * - * @sample samples.text.Strings.toLowerCase - */ -public expect fun String.toLowerCase(): String - -/** - * Returns a copy of this string having its first letter titlecased using the rules of the default locale, - * or the original string if it's empty or already starts with a title case letter. - * - * The title case of a character is usually the same as its upper case with several exceptions. - * The particular list of characters with the special title case form depends on the underlying platform. - * - * @sample samples.text.Strings.capitalize - */ -public expect fun String.capitalize(): String - -/** - * Returns a copy of this string having its first letter lowercased using the rules of the default locale, - * or the original string if it's empty or already starts with a lower case letter. - * - * @sample samples.text.Strings.decapitalize - */ -public expect fun String.decapitalize(): String - public expect fun CharSequence.repeat(n: Int): String diff --git a/libraries/stdlib/js/src/kotlin/text/char.kt b/libraries/stdlib/js/src/kotlin/text/char.kt index 0fb61f511fb..654e619a8dc 100644 --- a/libraries/stdlib/js/src/kotlin/text/char.kt +++ b/libraries/stdlib/js/src/kotlin/text/char.kt @@ -8,11 +8,79 @@ package kotlin.text // actually \s is enough to match all whitespace, but \xA0 added because of different regexp behavior of Rhino used in Selenium tests public actual fun Char.isWhitespace(): Boolean = toString().matches("[\\s\\xA0]") +/** + * Converts this character to lower case using Unicode mapping rules of the invariant locale. + */ +@OptIn(ExperimentalStdlibApi::class) @kotlin.internal.InlineOnly -public actual inline fun Char.toLowerCase(): Char = js("String.fromCharCode")(toInt()).toLowerCase().charCodeAt(0).unsafeCast().toChar() +public actual inline fun Char.toLowerCase(): Char = lowercaseChar() +/** + * Converts this character to lower case using Unicode mapping rules of the invariant locale. + * + * This function performs one-to-one character mapping. + * To support one-to-many character mapping use the [lowercase] function. + * If this character has no mapping equivalent, the character itself is returned. + * + * @sample samples.text.Chars.lowercase + */ +@SinceKotlin("1.4") +@ExperimentalStdlibApi @kotlin.internal.InlineOnly -public actual inline fun Char.toUpperCase(): Char = js("String.fromCharCode")(toInt()).toUpperCase().charCodeAt(0).unsafeCast().toChar() +public actual inline fun Char.lowercaseChar(): Char = toString().asDynamic().toLowerCase().charCodeAt(0).unsafeCast().toChar() + +/** + * Converts this character to lower case using Unicode mapping rules of the invariant locale. + * + * This function supports one-to-many character mapping, thus the length of the returned string can be greater than one. + * For example, `'\u0130'.lowercase()` returns `"\u0069\u0307"`, + * where `'\u0130'` is the LATIN CAPITAL LETTER I WITH DOT ABOVE character (`İ`). + * If this character has no lower case mapping, the result of `toString()` of this char is returned. + * + * @sample samples.text.Chars.lowercase + */ +@SinceKotlin("1.4") +@ExperimentalStdlibApi +@kotlin.internal.InlineOnly +public actual inline fun Char.lowercase(): String = toString().asDynamic().toLowerCase() as String + +/** + * Converts this character to upper case using Unicode mapping rules of the invariant locale. + */ +@OptIn(ExperimentalStdlibApi::class) +@kotlin.internal.InlineOnly +public actual inline fun Char.toUpperCase(): Char = uppercaseChar() + +/** + * Converts this character to upper case using Unicode mapping rules of the invariant locale. + * + * This function performs one-to-one character mapping. + * To support one-to-many character mapping use the [uppercase] function. + * If this character has no mapping equivalent, the character itself is returned. + * + * @sample samples.text.Chars.uppercase + */ +@SinceKotlin("1.4") +@ExperimentalStdlibApi +public actual fun Char.uppercaseChar(): Char { + val uppercase = uppercase() + return if (uppercase.length > 1) this else uppercase[0] +} + +/** + * Converts this character to upper case using Unicode mapping rules of the invariant locale. + * + * This function supports one-to-many character mapping, thus the length of the returned string can be greater than one. + * For example, `'\uFB00'.uppercase()` returns `"\u0046\u0046"`, + * where `'\uFB00'` is the LATIN SMALL LIGATURE FF character (`ff`). + * If this character has no upper case mapping, the result of `toString()` of this char is returned. + * + * @sample samples.text.Chars.uppercase + */ +@SinceKotlin("1.4") +@ExperimentalStdlibApi +@kotlin.internal.InlineOnly +public actual inline fun Char.uppercase(): String = toString().asDynamic().toUpperCase() as String /** * Returns `true` if this character is a Unicode high-surrogate code unit (also known as leading-surrogate code unit). diff --git a/libraries/stdlib/js/src/kotlin/text/numberConversions.kt b/libraries/stdlib/js/src/kotlin/text/numberConversions.kt index d69754fbd6a..9596dfeaa20 100644 --- a/libraries/stdlib/js/src/kotlin/text/numberConversions.kt +++ b/libraries/stdlib/js/src/kotlin/text/numberConversions.kt @@ -17,8 +17,9 @@ public actual inline fun String.toBoolean(): Boolean = this.toBoolean() /** * Returns `true` if this string is not `null` and its content is equal to the word "true", ignoring case, and `false` otherwise. */ +@OptIn(ExperimentalStdlibApi::class) @SinceKotlin("1.4") -public actual fun String?.toBoolean(): Boolean = this != null && this.toLowerCase() == "true" +public actual fun String?.toBoolean(): Boolean = this != null && this.lowercase() == "true" /** * Parses the string as a signed [Byte] number and returns the result. @@ -130,7 +131,8 @@ public actual inline fun Short.toString(radix: Int): String = this.toInt().toStr @SinceKotlin("1.2") public actual fun Int.toString(radix: Int): String = asDynamic().toString(checkRadix(radix)) -private fun String.isNaN(): Boolean = when (this.toLowerCase()) { +@OptIn(ExperimentalStdlibApi::class) +private fun String.isNaN(): Boolean = when (this.lowercase()) { "nan", "+nan", "-nan" -> true else -> false } diff --git a/libraries/stdlib/js/src/kotlin/text/string.kt b/libraries/stdlib/js/src/kotlin/text/string.kt index d079686da63..80ca2d47ae5 100644 --- a/libraries/stdlib/js/src/kotlin/text/string.kt +++ b/libraries/stdlib/js/src/kotlin/text/string.kt @@ -168,20 +168,42 @@ public actual fun String.encodeToByteArray( /** * Returns a copy of this string converted to upper case using the rules of the default locale. - * - * @sample samples.text.Strings.toUpperCase */ @kotlin.internal.InlineOnly public actual inline fun String.toUpperCase(): String = asDynamic().toUpperCase() /** - * Returns a copy of this string converted to lower case using the rules of the default locale. + * Returns a copy of this string converted to upper case using Unicode mapping rules of the invariant locale. * - * @sample samples.text.Strings.toLowerCase + * This function supports one-to-many and many-to-one character mapping, + * thus the length of the returned string can be different from the length of the original string. + * + * @sample samples.text.Strings.uppercase + */ +@SinceKotlin("1.4") +@ExperimentalStdlibApi +@kotlin.internal.InlineOnly +public actual inline fun String.uppercase(): String = asDynamic().toUpperCase() + +/** + * Returns a copy of this string converted to lower case using the rules of the default locale. */ @kotlin.internal.InlineOnly public actual inline fun String.toLowerCase(): String = asDynamic().toLowerCase() +/** + * Returns a copy of this string converted to lower case using Unicode mapping rules of the invariant locale. + * + * This function supports one-to-many and many-to-one character mapping, + * thus the length of the returned string can be different from the length of the original string. + * + * @sample samples.text.Strings.lowercase + */ +@SinceKotlin("1.4") +@ExperimentalStdlibApi +@kotlin.internal.InlineOnly +public actual inline fun String.lowercase(): String = asDynamic().toLowerCase() + @kotlin.internal.InlineOnly internal actual inline fun String.nativeIndexOf(str: String, fromIndex: Int): Int = asDynamic().indexOf(str, fromIndex) @@ -212,6 +234,7 @@ public inline fun String.match(regex: String): Array? = asDynamic().matc @kotlin.internal.InlineOnly internal inline fun String.nativeReplace(pattern: RegExp, replacement: String): String = asDynamic().replace(pattern, replacement) +@OptIn(ExperimentalStdlibApi::class) @SinceKotlin("1.2") @Suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS") public actual fun String.compareTo(other: String, ignoreCase: Boolean = false): Int { @@ -226,11 +249,11 @@ public actual fun String.compareTo(other: String, ignoreCase: Boolean = false): var s1 = this.substring(start, end) var s2 = other.substring(start, end) if (s1 != s2) { - s1 = s1.toUpperCase() - s2 = s2.toUpperCase() + s1 = s1.uppercase() + s2 = s2.uppercase() if (s1 != s2) { - s1 = s1.toLowerCase() - s2 = s2.toLowerCase() + s1 = s1.lowercase() + s2 = s2.lowercase() if (s1 != s2) { return s1.compareTo(s2) } diff --git a/libraries/stdlib/js/src/kotlin/text/stringsCode.kt b/libraries/stdlib/js/src/kotlin/text/stringsCode.kt index 94d90d9acd1..371f8366f4d 100644 --- a/libraries/stdlib/js/src/kotlin/text/stringsCode.kt +++ b/libraries/stdlib/js/src/kotlin/text/stringsCode.kt @@ -54,6 +54,7 @@ public fun String.matches(regex: String): Boolean { public actual fun CharSequence.isBlank(): Boolean = length == 0 || (if (this is String) this else this.toString()).matches("^[\\s\\xA0]+$") +@OptIn(ExperimentalStdlibApi::class) @Suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS") public actual fun String?.equals(other: String?, ignoreCase: Boolean = false): Boolean = if (this == null) @@ -61,7 +62,7 @@ public actual fun String?.equals(other: String?, ignoreCase: Boolean = false): B else if (!ignoreCase) this == other else - other != null && this.toLowerCase() == other.toLowerCase() + other != null && this.lowercase() == other.lowercase() @Suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS") diff --git a/libraries/stdlib/jvm/src/kotlin/text/CharJVM.kt b/libraries/stdlib/jvm/src/kotlin/text/CharJVM.kt index 1122dd1c0b9..23489d81e19 100644 --- a/libraries/stdlib/jvm/src/kotlin/text/CharJVM.kt +++ b/libraries/stdlib/jvm/src/kotlin/text/CharJVM.kt @@ -8,6 +8,8 @@ package kotlin.text +import java.util.Locale + /** * Returns `true` if this character (Unicode code point) is defined in Unicode. */ @@ -86,18 +88,104 @@ public inline fun Char.isUpperCase(): Boolean = Character.isUpperCase(this) public inline fun Char.isLowerCase(): Boolean = Character.isLowerCase(this) /** - * Converts this character to uppercase. - * @sample samples.text.Chars.toUpperCase + * Converts this character to lower case using Unicode mapping rules of the invariant locale. */ +@OptIn(ExperimentalStdlibApi::class) @kotlin.internal.InlineOnly -public actual inline fun Char.toUpperCase(): Char = Character.toUpperCase(this) +public actual inline fun Char.toUpperCase(): Char = uppercaseChar() /** - * Converts this character to lowercase. - * @sample samples.text.Chars.toLowerCase + * Converts this character to upper case using Unicode mapping rules of the invariant locale. + * + * This function performs one-to-one character mapping. + * To support one-to-many character mapping use the [uppercase] function. + * If this character has no mapping equivalent, the character itself is returned. + * + * @sample samples.text.Chars.uppercase */ +@SinceKotlin("1.4") +@ExperimentalStdlibApi @kotlin.internal.InlineOnly -public actual inline fun Char.toLowerCase(): Char = Character.toLowerCase(this) +public actual inline fun Char.uppercaseChar(): Char = Character.toUpperCase(this) + +/** + * Converts this character to upper case using Unicode mapping rules of the invariant locale. + * + * This function supports one-to-many character mapping, thus the length of the returned string can be greater than one. + * For example, `'\uFB00'.uppercase()` returns `"\u0046\u0046"`, + * where `'\uFB00'` is the LATIN SMALL LIGATURE FF character (`ff`). + * If this character has no upper case mapping, the result of `toString()` of this char is returned. + * + * @sample samples.text.Chars.uppercase + */ +@SinceKotlin("1.4") +@ExperimentalStdlibApi +@kotlin.internal.InlineOnly +public actual inline fun Char.uppercase(): String = toString().uppercase() + +/** + * Converts this character to upper case using Unicode mapping rules of the specified [locale]. + * + * This function supports one-to-many character mapping, thus the length of the returned string can be greater than one. + * For example, `'\uFB00'.uppercase(Locale.US)` returns `"\u0046\u0046"`, + * where `'\uFB00'` is the LATIN SMALL LIGATURE FF character (`ff`). + * If this character has no upper case mapping, the result of `toString()` of this char is returned. + * + * @sample samples.text.Chars.uppercaseLocale + */ +@SinceKotlin("1.4") +@ExperimentalStdlibApi +public fun Char.uppercase(locale: Locale): String = toString().uppercase(locale) + +/** + * Converts this character to lower case using Unicode mapping rules of the invariant locale. + */ +@OptIn(ExperimentalStdlibApi::class) +@kotlin.internal.InlineOnly +public actual inline fun Char.toLowerCase(): Char = lowercaseChar() + +/** + * Converts this character to lower case using Unicode mapping rules of the invariant locale. + * + * This function performs one-to-one character mapping. + * To support one-to-many character mapping use the [lowercase] function. + * If this character has no mapping equivalent, the character itself is returned. + * + * @sample samples.text.Chars.lowercase + */ +@SinceKotlin("1.4") +@ExperimentalStdlibApi +@kotlin.internal.InlineOnly +public actual inline fun Char.lowercaseChar(): Char = Character.toLowerCase(this) + +/** + * Converts this character to lower case using Unicode mapping rules of the invariant locale. + * + * This function supports one-to-many character mapping, thus the length of the returned string can be greater than one. + * For example, `'\u0130'.lowercase()` returns `"\u0069\u0307"`, + * where `'\u0130'` is the LATIN CAPITAL LETTER I WITH DOT ABOVE character (`İ`). + * If this character has no lower case mapping, the result of `toString()` of this char is returned. + * + * @sample samples.text.Chars.lowercase + */ +@SinceKotlin("1.4") +@ExperimentalStdlibApi +@kotlin.internal.InlineOnly +public actual inline fun Char.lowercase(): String = toString().lowercase() + +/** + * Converts this character to lower case using Unicode mapping rules of the specified [locale]. + * + * This function supports one-to-many character mapping, thus the length of the returned string can be greater than one. + * For example, `'\u0130'.lowercase(Locale.US)` returns `"\u0069\u0307"`, + * where `'\u0130'` is the LATIN CAPITAL LETTER I WITH DOT ABOVE character (`İ`). + * If this character has no lower case mapping, the result of `toString()` of this char is returned. + * + * @sample samples.text.Chars.lowercaseLocale + */ +@SinceKotlin("1.4") +@ExperimentalStdlibApi +public fun Char.lowercase(locale: Locale): String = toString().lowercase(locale) /** * Returns `true` if this character is a titlecase character. @@ -107,13 +195,70 @@ public actual inline fun Char.toLowerCase(): Char = Character.toLowerCase(this) public inline fun Char.isTitleCase(): Boolean = Character.isTitleCase(this) /** - * Converts this character to titlecase. + * Converts this character to title case using Unicode mapping rules of the invariant locale. * * @see Character.toTitleCase - * @sample samples.text.Chars.toTitleCase */ +@OptIn(ExperimentalStdlibApi::class) @kotlin.internal.InlineOnly -public inline fun Char.toTitleCase(): Char = Character.toTitleCase(this) +public inline fun Char.toTitleCase(): Char = titlecaseChar() + +/** + * Converts this character to title case using Unicode mapping rules of the invariant locale. + * + * This function performs one-to-one character mapping. + * To support one-to-many character mapping use the [titlecase] function. + * If this character has no mapping equivalent, the result of calling [uppercaseChar] is returned. + * + * @sample samples.text.Chars.titlecase + */ +@SinceKotlin("1.4") +@ExperimentalStdlibApi +@kotlin.internal.InlineOnly +public inline fun Char.titlecaseChar(): Char = Character.toTitleCase(this) + +/** + * Converts this character to title case using Unicode mapping rules of the invariant locale. + * + * This function supports one-to-many character mapping, thus the length of the returned string can be greater than one. + * For example, `'\uFB00'.titlecase()` returns `"\u0046\u0066"`, + * where `'\uFB00'` is the LATIN SMALL LIGATURE FF character (`ff`). + * If this character has no title case mapping, the result of [uppercase] is returned instead. + * + * @sample samples.text.Chars.titlecase + */ +@SinceKotlin("1.4") +@ExperimentalStdlibApi +public fun Char.titlecase(): String { + val uppercase = uppercase() + if (uppercase.length > 1) { + return if (this == '\u0149') uppercase else uppercase[0] + uppercase.substring(1).lowercase() + } + return titlecaseChar().toString() +} + +/** + * Converts this character to title case using Unicode mapping rules of the specified [locale]. + * + * This function supports one-to-many character mapping, thus the length of the returned string can be greater than one. + * For example, `'\uFB00'.titlecase(Locale.US)` returns `"\u0046\u0066"`, + * where `'\uFB00'` is the LATIN SMALL LIGATURE FF character (`ff`). + * If this character has no title case mapping, the result of `uppercase(locale)` is returned instead. + * + * @sample samples.text.Chars.titlecaseLocale + */ +@SinceKotlin("1.4") +@ExperimentalStdlibApi +public fun Char.titlecase(locale: Locale): String { + val localizedUppercase = uppercase(locale) + if (localizedUppercase.length > 1) { + return if (this == '\u0149') localizedUppercase else localizedUppercase[0] + localizedUppercase.substring(1).lowercase() + } + if (localizedUppercase != uppercase()) { + return localizedUppercase + } + return titlecaseChar().toString() +} /** * Returns a value indicating a character's general category. diff --git a/libraries/stdlib/jvm/src/kotlin/text/StringsJVM.kt b/libraries/stdlib/jvm/src/kotlin/text/StringsJVM.kt index e90a0146368..65cd26b38a4 100644 --- a/libraries/stdlib/jvm/src/kotlin/text/StringsJVM.kt +++ b/libraries/stdlib/jvm/src/kotlin/text/StringsJVM.kt @@ -120,20 +120,42 @@ public actual fun String.replaceFirst(oldValue: String, newValue: String, ignore /** * Returns a copy of this string converted to upper case using the rules of the default locale. - * - * @sample samples.text.Strings.toUpperCase */ @kotlin.internal.InlineOnly public actual inline fun String.toUpperCase(): String = (this as java.lang.String).toUpperCase() /** - * Returns a copy of this string converted to lower case using the rules of the default locale. + * Returns a copy of this string converted to upper case using Unicode mapping rules of the invariant locale. * - * @sample samples.text.Strings.toLowerCase + * This function supports one-to-many and many-to-one character mapping, + * thus the length of the returned string can be different from the length of the original string. + * + * @sample samples.text.Strings.uppercase + */ +@SinceKotlin("1.4") +@ExperimentalStdlibApi +@kotlin.internal.InlineOnly +public actual inline fun String.uppercase(): String = (this as java.lang.String).toUpperCase(Locale.ROOT) + +/** + * Returns a copy of this string converted to lower case using the rules of the default locale. */ @kotlin.internal.InlineOnly public actual inline fun String.toLowerCase(): String = (this as java.lang.String).toLowerCase() +/** + * Returns a copy of this string converted to lower case using Unicode mapping rules of the invariant locale. + * + * This function supports one-to-many and many-to-one character mapping, + * thus the length of the returned string can be different from the length of the original string. + * + * @sample samples.text.Strings.lowercase + */ +@SinceKotlin("1.4") +@ExperimentalStdlibApi +@kotlin.internal.InlineOnly +public actual inline fun String.lowercase(): String = (this as java.lang.String).toLowerCase(Locale.ROOT) + /** * Concatenates characters in this [CharArray] into a String. */ @@ -588,14 +610,42 @@ public fun String.regionMatches(thisOffset: Int, other: String, otherOffset: Int /** * Returns a copy of this string converted to lower case using the rules of the specified locale. */ +@OptIn(ExperimentalStdlibApi::class) @kotlin.internal.InlineOnly -public inline fun String.toLowerCase(locale: java.util.Locale): String = (this as java.lang.String).toLowerCase(locale) +public inline fun String.toLowerCase(locale: java.util.Locale): String = lowercase(locale) + +/** + * Returns a copy of this string converted to lower case using the rules of the specified [locale]. + * + * This function supports one-to-many and many-to-one character mapping, + * thus the length of the returned string can be different from the length of the original string. + * + * @sample samples.text.Strings.lowercaseLocale + */ +@SinceKotlin("1.4") +@ExperimentalStdlibApi +@kotlin.internal.InlineOnly +public inline fun String.lowercase(locale: Locale): String = (this as java.lang.String).toLowerCase(locale) /** * Returns a copy of this string converted to upper case using the rules of the specified locale. */ +@OptIn(ExperimentalStdlibApi::class) @kotlin.internal.InlineOnly -public inline fun String.toUpperCase(locale: java.util.Locale): String = (this as java.lang.String).toUpperCase(locale) +public inline fun String.toUpperCase(locale: java.util.Locale): String = uppercase(locale) + +/** + * Returns a copy of this string converted to upper case using the rules of the specified [locale]. + * + * This function supports one-to-many and many-to-one character mapping, + * thus the length of the returned string can be different from the length of the original string. + * + * @sample samples.text.Strings.uppercaseLocale + */ +@SinceKotlin("1.4") +@ExperimentalStdlibApi +@kotlin.internal.InlineOnly +public inline fun String.uppercase(locale: Locale): String = (this as java.lang.String).toUpperCase(locale) /** * Encodes the contents of this string using the specified character set and returns the resulting byte array. @@ -634,19 +684,20 @@ public actual fun String.capitalize(): String { * The title case of a character is usually the same as its upper case with several exceptions. * The particular list of characters with the special title case form depends on the underlying platform. */ +@OptIn(ExperimentalStdlibApi::class) @SinceKotlin("1.4") @WasExperimental(ExperimentalStdlibApi::class) -@kotlin.internal.LowPriorityInOverloadResolution +@kotlin.internal.LowPriorityInOverloadResolution // To avoid conflicts in function references, as this function was introduced later than common capitalize() public fun String.capitalize(locale: Locale): String { if (isNotEmpty()) { val firstChar = this[0] if (firstChar.isLowerCase()) { return buildString { - val titleChar = firstChar.toTitleCase() - if (titleChar != firstChar.toUpperCase()) { + val titleChar = firstChar.titlecaseChar() + if (titleChar != firstChar.uppercaseChar()) { append(titleChar) } else { - append(this@capitalize.substring(0, 1).toUpperCase(locale)) + append(this@capitalize.substring(0, 1).uppercase(locale)) } append(this@capitalize.substring(1)) } @@ -669,11 +720,12 @@ public actual fun String.decapitalize(): String { * Returns a copy of this string having its first letter lowercased using the rules of the specified [locale], * or the original string, if it's empty or already starts with a lower case letter. */ +@OptIn(ExperimentalStdlibApi::class) @SinceKotlin("1.4") @WasExperimental(ExperimentalStdlibApi::class) -@kotlin.internal.LowPriorityInOverloadResolution +@kotlin.internal.LowPriorityInOverloadResolution // To avoid conflicts in function references, as this function was introduced later than common decapitalize() public fun String.decapitalize(locale: Locale): String { - return if (isNotEmpty() && !this[0].isLowerCase()) substring(0, 1).toLowerCase(locale) + substring(1) else this + return if (isNotEmpty() && !this[0].isLowerCase()) substring(0, 1).lowercase(locale) + substring(1) else this } /** diff --git a/libraries/stdlib/jvm/test/text/StringJVMTest.kt b/libraries/stdlib/jvm/test/text/StringJVMTest.kt index d2fc5aa6fb6..0e07ff1c7f6 100644 --- a/libraries/stdlib/jvm/test/text/StringJVMTest.kt +++ b/libraries/stdlib/jvm/test/text/StringJVMTest.kt @@ -75,53 +75,67 @@ class StringJVMTest { } @Test fun capitalize() { + fun testCapitalize(expected: String, string: String) { + assertEquals(expected, string.capitalize()) + assertEquals(expected, string.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() }) + } // Case mapping that results in multiple characters (validating Character.toUpperCase was not used). assertEquals("SSßß", "ßßß".capitalize()) + assertEquals("Ssßß", "ßßß".replaceFirstChar { if (it.isLowerCase()) it.titlecase() else it.toString() }) // Case mapping where title case is different than uppercase and so Character.toTitleCase is preferred. - assertEquals("Dzdzdz", "dzdzdz".capitalize()) - assertEquals("DZDZDZ", "DZDZDZ".capitalize()) + testCapitalize("Dzdzdz", "dzdzdz") + testCapitalize("DZDZDZ", "DZDZDZ") } @Test fun decapitalize() { - assertEquals("aBC", "ABC".decapitalize()) - assertEquals("abc", "Abc".decapitalize()) - assertEquals("abc", "abc".decapitalize()) - + fun testDecapitalize(expected: String, string: String) { + assertEquals(expected, string.decapitalize()) + assertEquals(expected, string.replaceFirstChar { it.lowercase(Locale.getDefault()) }) + } // Case mapping where title case is different than uppercase. - assertEquals("dzdzdz", "DZdzdz".decapitalize()) - assertEquals("dzdzdz", "Dzdzdz".decapitalize()) + testDecapitalize("dzdzdz", "DZdzdz") + testDecapitalize("dzdzdz", "Dzdzdz") } @Test fun capitalizeLocale() { - assertEquals("ABC", "ABC".capitalize(Locale.US)) - assertEquals("Abc", "Abc".capitalize(Locale.US)) - assertEquals("Abc", "abc".capitalize(Locale.US)) + fun testCapitalizeLocale(expected: String, string: String, locale: Locale) { + assertEquals(expected, string.capitalize(locale)) + assertEquals(expected, string.replaceFirstChar { if (it.isLowerCase()) it.titlecase(locale) else it.toString() }) + } + testCapitalizeLocale("ABC", "ABC", Locale.US) + testCapitalizeLocale("Abc", "Abc", Locale.US) + testCapitalizeLocale("Abc", "abc", Locale.US) // Locale-specific case mappings. - assertEquals("İii", "iii".capitalize(Locale("tr", "TR"))) - assertEquals("Iii", "iii".capitalize(Locale.US)) + testCapitalizeLocale("İii", "iii", Locale("tr", "TR")) + testCapitalizeLocale("Iii", "iii", Locale.US) // Case mapping that results in multiple characters (validating Character.toUpperCase was not used). assertEquals("SSßß", "ßßß".capitalize(Locale.US)) + assertEquals("Ssßß", "ßßß".replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.US) else it.toString() }) // Case mapping where title case is different than uppercase and so Character.toTitleCase is preferred. - assertEquals("Dzdzdz", "dzdzdz".capitalize(Locale.US)) - assertEquals("DZDZDZ", "DZDZDZ".capitalize(Locale.US)) + testCapitalizeLocale("Dzdzdz", "dzdzdz", Locale.US) + testCapitalizeLocale("DZDZDZ", "DZDZDZ", Locale.US) } @Test fun decapitalizeLocale() { - assertEquals("aBC", "ABC".decapitalize(Locale.US)) - assertEquals("abc", "Abc".decapitalize(Locale.US)) - assertEquals("abc", "abc".decapitalize(Locale.US)) + fun testDecapitalizeLocale(expected: String, string: String, locale: Locale) { + assertEquals(expected, string.decapitalize(locale)) + assertEquals(expected, string.replaceFirstChar { it.lowercase(locale) }) + } + testDecapitalizeLocale("aBC", "ABC", Locale.US) + testDecapitalizeLocale("abc", "Abc", Locale.US) + testDecapitalizeLocale("abc", "abc", Locale.US) // Locale-specific case mappings. - assertEquals("ıII", "III".decapitalize(Locale("tr", "TR"))) - assertEquals("iII", "III".decapitalize(Locale.US)) + testDecapitalizeLocale("ıII", "III", Locale("tr", "TR")) + testDecapitalizeLocale("iII", "III", Locale.US) // Case mapping where title case is different than uppercase. - assertEquals("dzdzdz", "DZdzdz".decapitalize(Locale.US)) - assertEquals("dzdzdz", "Dzdzdz".decapitalize(Locale.US)) + testDecapitalizeLocale("dzdzdz", "DZdzdz", Locale.US) + testDecapitalizeLocale("dzdzdz", "Dzdzdz", Locale.US) } @Test diff --git a/libraries/stdlib/samples/test/samples/text/char.kt b/libraries/stdlib/samples/test/samples/text/char.kt index bbee1094508..493cf8815a6 100644 --- a/libraries/stdlib/samples/test/samples/text/char.kt +++ b/libraries/stdlib/samples/test/samples/text/char.kt @@ -1,8 +1,8 @@ package samples.text import samples.* -import kotlin.test.* import java.util.* +import kotlin.test.* class Chars { @@ -83,17 +83,41 @@ class Chars { } @Sample - fun toUpperCase() { - val chars = listOf('a', 'ω', '1', 'A', '+') - val upperCases = chars.map { it.toUpperCase() } - assertPrints(upperCases, "[A, Ω, 1, A, +]") + fun uppercase() { + val chars = listOf('a', 'ω', '1', 'ʼn', 'A', '+', 'ß') + val uppercaseChar = chars.map { it.uppercaseChar() } + val uppercase = chars.map { it.uppercase() } + assertPrints(uppercaseChar, "[A, Ω, 1, ʼn, A, +, ß]") + assertPrints(uppercase, "[A, Ω, 1, ʼN, A, +, SS]") } @Sample - fun toLowerCase() { - val chars = listOf('A', 'Ω', '1', 'a', '+') - val lowerCases = chars.map { it.toLowerCase() } - assertPrints(lowerCases, "[a, ω, 1, a, +]") + fun uppercaseLocale() { + val chars = listOf('a', '1', 'ʼn', 'A', '+', 'i') + val uppercase = chars.map { it.uppercase() } + val turkishLocale = Locale.forLanguageTag("tr") + val uppercaseTurkish = chars.map { it.uppercase(turkishLocale) } + assertPrints(uppercase, "[A, 1, ʼN, A, +, I]") + assertPrints(uppercaseTurkish, "[A, 1, ʼN, A, +, İ]") + } + + @Sample + fun lowercase() { + val chars = listOf('A', 'Ω', '1', 'a', '+', 'İ') + val lowercaseChar = chars.map { it.lowercaseChar() } + val lowercase = chars.map { it.lowercase() } + assertPrints(lowercaseChar, "[a, ω, 1, a, +, i]") + assertPrints(lowercase, "[a, ω, 1, a, +, \u0069\u0307]") + } + + @Sample + fun lowercaseLocale() { + val chars = listOf('A', 'Ω', '1', 'a', '+', 'İ') + val lowercase = chars.map { it.lowercase() } + val turkishLocale = Locale.forLanguageTag("tr") + val lowercaseTurkish = chars.map { it.lowercase(turkishLocale) } + assertPrints(lowercase, "[a, ω, 1, a, +, \u0069\u0307]") + assertPrints(lowercaseTurkish, "[a, ω, 1, a, +, i]") } @Sample @@ -105,10 +129,22 @@ class Chars { } @Sample - fun toTitleCase() { - val chars = listOf('a', 'Dž', '1', '+') - val titleCases = chars.map { it.toTitleCase() } - assertPrints(titleCases, "[A, Dž, 1, +]") + fun titlecase() { + val chars = listOf('a', 'Dž', 'ʼn', '+', 'ß') + val titlecaseChar = chars.map { it.titlecaseChar() } + val titlecase = chars.map { it.titlecase() } + assertPrints(titlecaseChar, "[A, Dž, ʼn, +, ß]") + assertPrints(titlecase, "[A, Dž, ʼN, +, Ss]") + } + + @Sample + fun titlecaseLocale() { + val chars = listOf('a', 'Dž', 'ʼn', '+', 'ß', 'i') + val titlecase = chars.map { it.titlecase() } + val turkishLocale = Locale.forLanguageTag("tr") + val titlecaseTurkish = chars.map { it.titlecase(turkishLocale) } + assertPrints(titlecase, "[A, Dž, ʼN, +, Ss, I]") + assertPrints(titlecaseTurkish, "[A, Dž, ʼN, +, Ss, İ]") } @Sample diff --git a/libraries/stdlib/samples/test/samples/text/strings.kt b/libraries/stdlib/samples/test/samples/text/strings.kt index a30b571ce47..6faa1ef3826 100644 --- a/libraries/stdlib/samples/test/samples/text/strings.kt +++ b/libraries/stdlib/samples/test/samples/text/strings.kt @@ -1,6 +1,7 @@ package samples.text import samples.* +import java.util.Locale import kotlin.test.* class Strings { @@ -17,6 +18,15 @@ class Strings { assertPrints("Abcd".decapitalize(), "abcd") } + @Sample + fun replaceFirstChar() { + assertPrints("kotlin".replaceFirstChar { it.uppercase() }, "Kotlin") + + val sentence = "Welcome to Kotlin!" + val words = sentence.split(' '); + assertPrints(words.joinToString(separator = "_") { word -> word.replaceFirstChar { it.lowercase() } }, "welcome_to_kotlin!") + } + @Sample fun repeat() { assertPrints("Word".repeat(4), "WordWordWordWord") @@ -205,13 +215,27 @@ class Strings { } @Sample - fun toLowerCase() { - assertPrints("Iced frappé!".toLowerCase(), "iced frappé!") + fun lowercase() { + assertPrints("Iced frappé!".lowercase(), "iced frappé!") } @Sample - fun toUpperCase() { - assertPrints("Iced frappé!".toUpperCase(), "ICED FRAPPÉ!") + fun lowercaseLocale() { + assertPrints("KOTLIN".lowercase(), "kotlin") + val turkishLocale = Locale.forLanguageTag("tr") + assertPrints("KOTLIN".lowercase(turkishLocale), "kotlın") + } + + @Sample + fun uppercase() { + assertPrints("Iced frappé!".uppercase(), "ICED FRAPPÉ!") + } + + @Sample + fun uppercaseLocale() { + assertPrints("Kotlin".uppercase(), "KOTLIN") + val turkishLocale = Locale.forLanguageTag("tr") + assertPrints("Kotlin".uppercase(turkishLocale), "KOTLİN") } @Sample diff --git a/libraries/stdlib/src/kotlin/text/Char.kt b/libraries/stdlib/src/kotlin/text/Char.kt index d422e6ae8ea..e8de0008915 100644 --- a/libraries/stdlib/src/kotlin/text/Char.kt +++ b/libraries/stdlib/src/kotlin/text/Char.kt @@ -8,6 +8,70 @@ package kotlin.text +/** + * Converts this character to lower case using Unicode mapping rules of the invariant locale. + */ +public expect fun Char.toLowerCase(): Char + +/** + * Converts this character to lower case using Unicode mapping rules of the invariant locale. + * + * This function performs one-to-one character mapping. + * To support one-to-many character mapping use the [lowercase] function. + * If this character has no mapping equivalent, the character itself is returned. + * + * @sample samples.text.Chars.lowercase + */ +@SinceKotlin("1.4") +@ExperimentalStdlibApi +public expect fun Char.lowercaseChar(): Char + +/** + * Converts this character to lower case using Unicode mapping rules of the invariant locale. + * + * This function supports one-to-many character mapping, thus the length of the returned string can be greater than one. + * For example, `'\u0130'.lowercase()` returns `"\u0069\u0307"`, + * where `'\u0130'` is the LATIN CAPITAL LETTER I WITH DOT ABOVE character (`İ`). + * If this character has no lower case mapping, the result of `toString()` of this char is returned. + * + * @sample samples.text.Chars.lowercase + */ +@SinceKotlin("1.4") +@ExperimentalStdlibApi +public expect fun Char.lowercase(): String + +/** + * Converts this character to upper case using Unicode mapping rules of the invariant locale. + */ +public expect fun Char.toUpperCase(): Char + +/** + * Converts this character to upper case using Unicode mapping rules of the invariant locale. + * + * This function performs one-to-one character mapping. + * To support one-to-many character mapping use the [uppercase] function. + * If this character has no mapping equivalent, the character itself is returned. + * + * @sample samples.text.Chars.uppercase + */ +@SinceKotlin("1.4") +@ExperimentalStdlibApi +public expect fun Char.uppercaseChar(): Char + +/** + * Converts this character to upper case using Unicode mapping rules of the invariant locale. + * + * This function supports one-to-many character mapping, thus the length of the returned string can be greater than one. + * For example, `'\uFB00'.uppercase()` returns `"\u0046\u0046"`, + * where `'\uFB00'` is the LATIN SMALL LIGATURE FF character (`ff`). + * If this character has no upper case mapping, the result of `toString()` of this char is returned. + * + * @sample samples.text.Chars.uppercase + */ +@SinceKotlin("1.4") +@ExperimentalStdlibApi +public expect fun Char.uppercase(): String + /** * Concatenates this Char and a String. * @@ -23,17 +87,18 @@ public inline operator fun Char.plus(other: String): String = this.toString() + * * Two characters are considered the same ignoring case if at least one of the following is `true`: * - The two characters are the same (as compared by the == operator) - * - Applying the method [toUpperCase] to each character produces the same result - * - Applying the method [toLowerCase] to each character produces the same result + * - Applying the method [uppercaseChar] to each character produces the same result + * - Applying the method [lowercaseChar] to each character produces the same result * * @sample samples.text.Chars.equals */ +@OptIn(ExperimentalStdlibApi::class) public fun Char.equals(other: Char, ignoreCase: Boolean = false): Boolean { if (this == other) return true if (!ignoreCase) return false - if (this.toUpperCase() == other.toUpperCase()) return true - if (this.toLowerCase() == other.toLowerCase()) return true + if (this.uppercaseChar() == other.uppercaseChar()) return true + if (this.lowercaseChar() == other.lowercaseChar()) return true return false } diff --git a/libraries/stdlib/src/kotlin/text/Strings.kt b/libraries/stdlib/src/kotlin/text/Strings.kt index fbdc097e20c..a0d00a730e3 100644 --- a/libraries/stdlib/src/kotlin/text/Strings.kt +++ b/libraries/stdlib/src/kotlin/text/Strings.kt @@ -9,6 +9,60 @@ package kotlin.text import kotlin.contracts.contract +import kotlin.jvm.JvmName + +/** + * Returns a copy of this string converted to upper case using the rules of the default locale. + */ +public expect fun String.toUpperCase(): String + +/** + * Returns a copy of this string converted to upper case using Unicode mapping rules of the invariant locale. + * + * This function supports one-to-many and many-to-one character mapping, + * thus the length of the returned string can be different from the length of the original string. + * + * @sample samples.text.Strings.uppercase + */ +@SinceKotlin("1.4") +@ExperimentalStdlibApi +public expect fun String.uppercase(): String + +/** + * Returns a copy of this string converted to lower case using the rules of the default locale. + */ +public expect fun String.toLowerCase(): String + +/** + * Returns a copy of this string converted to lower case using Unicode mapping rules of the invariant locale. + * + * This function supports one-to-many and many-to-one character mapping, + * thus the length of the returned string can be different from the length of the original string. + * + * @sample samples.text.Strings.lowercase + */ +@SinceKotlin("1.4") +@ExperimentalStdlibApi +public expect fun String.lowercase(): String + +/** + * Returns a copy of this string having its first letter titlecased using the rules of the default locale, + * or the original string if it's empty or already starts with a title case letter. + * + * The title case of a character is usually the same as its upper case with several exceptions. + * The particular list of characters with the special title case form depends on the underlying platform. + * + * @sample samples.text.Strings.capitalize + */ +public expect fun String.capitalize(): String + +/** + * Returns a copy of this string having its first letter lowercased using the rules of the default locale, + * or the original string if it's empty or already starts with a lower case letter. + * + * @sample samples.text.Strings.decapitalize + */ +public expect fun String.decapitalize(): String /** * Returns a sub sequence of this char sequence having leading and trailing characters matching the [predicate] removed. @@ -710,6 +764,42 @@ public inline fun CharSequence.replace(regex: Regex, noinline transform: (MatchR @kotlin.internal.InlineOnly public inline fun CharSequence.replaceFirst(regex: Regex, replacement: String): String = regex.replaceFirst(this, replacement) +/** + * Returns a copy of this string having its first character replaced with the result of the specified [transform], + * or the original string if it's empty. + * + * @param transform function that takes the first character and returns the result of the transform applied to the character. + * + * @sample samples.text.Strings.replaceFirstChar + */ +@SinceKotlin("1.4") +@ExperimentalStdlibApi +@OptIn(kotlin.experimental.ExperimentalTypeInference::class) +@OverloadResolutionByLambdaReturnType +@JvmName("replaceFirstCharWithChar") +@kotlin.internal.InlineOnly +public inline fun String.replaceFirstChar(transform: (Char) -> Char): String { + return if (isNotEmpty()) transform(this[0]) + substring(1) else this +} + +/** + * Returns a copy of this string having its first character replaced with the result of the specified [transform], + * or the original string if it's empty. + * + * @param transform function that takes the first character and returns the result of the transform applied to the character. + * + * @sample samples.text.Strings.replaceFirstChar + */ +@SinceKotlin("1.4") +@ExperimentalStdlibApi +@OptIn(kotlin.experimental.ExperimentalTypeInference::class) +@OverloadResolutionByLambdaReturnType +@JvmName("replaceFirstCharWithCharSequence") +@kotlin.internal.InlineOnly +public inline fun String.replaceFirstChar(transform: (Char) -> CharSequence): String { + return if (isNotEmpty()) transform(this[0]).toString() + substring(1) else this +} + /** * Returns `true` if this char sequence matches the given regular expression. diff --git a/libraries/stdlib/test/OrderingTest.kt b/libraries/stdlib/test/OrderingTest.kt index 727a16d2d11..48fa0a3af79 100644 --- a/libraries/stdlib/test/OrderingTest.kt +++ b/libraries/stdlib/test/OrderingTest.kt @@ -16,7 +16,7 @@ data class Item(val name: String, val rating: Int) : Comparable { @SharedImmutable val STRING_CASE_INSENSITIVE_ORDER: Comparator = - compareBy { it: String -> it.toUpperCase() }.thenBy { it.toLowerCase() }.thenBy { it } + compareBy { it: String -> it.uppercase() }.thenBy { it.lowercase() }.thenBy { it } class OrderingTest { val v1 = Item("wine", 9) diff --git a/libraries/stdlib/test/collections/ArraysTest.kt b/libraries/stdlib/test/collections/ArraysTest.kt index b65223050d3..2037f47b34c 100644 --- a/libraries/stdlib/test/collections/ArraysTest.kt +++ b/libraries/stdlib/test/collections/ArraysTest.kt @@ -1369,7 +1369,7 @@ class ArraysTest { assertEquals(mapOf("Alice" to 5, "Bob" to 3, "Carol" to 5), itemsWithTheirLength) val updatedLength = items.copyOfRange(1, 3) - .associateWithTo(itemsWithTheirLength.toMutableMap()) { name -> name.toLowerCase().count { it in "aeuio" } } + .associateWithTo(itemsWithTheirLength.toMutableMap()) { name -> name.lowercase().count { it in "aeuio" } } assertEquals(mapOf("Alice" to 5, "Bob" to 1, "Carol" to 2), updatedLength) } diff --git a/libraries/stdlib/test/collections/CollectionTest.kt b/libraries/stdlib/test/collections/CollectionTest.kt index d23e1a714fe..193ed2a8a0f 100644 --- a/libraries/stdlib/test/collections/CollectionTest.kt +++ b/libraries/stdlib/test/collections/CollectionTest.kt @@ -489,7 +489,7 @@ class CollectionTest { assertEquals(mapOf("Alice" to 5, "Bob" to 3, "Carol" to 5), itemsWithTheirLength) val updatedLength = - items.drop(1).associateWithTo(itemsWithTheirLength.toMutableMap()) { name -> name.toLowerCase().count { it in "aeuio" }} + items.drop(1).associateWithTo(itemsWithTheirLength.toMutableMap()) { name -> name.lowercase().count { it in "aeuio" }} assertEquals(mapOf("Alice" to 5, "Bob" to 1, "Carol" to 2), updatedLength) } @@ -1099,7 +1099,7 @@ class CollectionTest { } @Test fun sortedWith() { - val comparator = compareBy { it.toUpperCase().reversed() } + val comparator = compareBy { it.uppercase().reversed() } val data = listOf("cat", "dad", "BAD") expect(listOf("BAD", "dad", "cat")) { data.sortedWith(comparator) } diff --git a/libraries/stdlib/test/collections/IterableTests.kt b/libraries/stdlib/test/collections/IterableTests.kt index c0ab3cb0f85..5b215343c70 100644 --- a/libraries/stdlib/test/collections/IterableTests.kt +++ b/libraries/stdlib/test/collections/IterableTests.kt @@ -485,7 +485,7 @@ abstract class IterableTests>(val createFrom: (Array name.toLowerCase().count { it in "aeuio" }} + items.drop(1).associateWithTo(itemsWithTheirLength.toMutableMap()) { name -> name.lowercase().count { it in "aeuio" }} assertEquals(mapOf("Alice" to 5, "Bob" to 1, "Carol" to 2), updatedLength) } diff --git a/libraries/stdlib/test/text/RegexTest.kt b/libraries/stdlib/test/text/RegexTest.kt index 72b7ef919c8..523252b5f21 100644 --- a/libraries/stdlib/test/text/RegexTest.kt +++ b/libraries/stdlib/test/text/RegexTest.kt @@ -56,7 +56,7 @@ class RegexTest { @Test fun matchIgnoreCase() { for (input in listOf("ascii", "shrödinger")) - assertTrue(input.toUpperCase().matches(input.toLowerCase().toRegex(RegexOption.IGNORE_CASE))) + assertTrue(input.uppercase().matches(input.lowercase().toRegex(RegexOption.IGNORE_CASE))) } @Test fun matchSequence() { diff --git a/libraries/stdlib/test/text/StringNumberConversionTest.kt b/libraries/stdlib/test/text/StringNumberConversionTest.kt index adc6d60ff9b..a53206ac515 100644 --- a/libraries/stdlib/test/text/StringNumberConversionTest.kt +++ b/libraries/stdlib/test/text/StringNumberConversionTest.kt @@ -289,7 +289,7 @@ class StringNumberConversionTest { } @Test fun shortToStringWithRadix() { - assertEquals("7FFF", 0x7FFF.toShort().toString(radix = 16).toUpperCase()) + assertEquals("7FFF", 0x7FFF.toShort().toString(radix = 16).uppercase()) assertEquals("-8000", (-0x8000).toShort().toString(radix = 16)) assertEquals("-sfs", (-29180).toShort().toString(radix = 32)) @@ -328,7 +328,7 @@ class StringNumberConversionTest { } @Test fun ushortToStringWithRadix() { - assertEquals("7FFF", 0x7FFF.toUShort().toString(radix = 16).toUpperCase()) + assertEquals("7FFF", 0x7FFF.toUShort().toString(radix = 16).uppercase()) assertEquals("8000", 0x8000.toUShort().toString(radix = 16)) assertEquals("ffff", UShort.MAX_VALUE.toString(radix = 16)) diff --git a/libraries/stdlib/test/text/StringTest.kt b/libraries/stdlib/test/text/StringTest.kt index 5d0665a237c..ba26d28d94c 100644 --- a/libraries/stdlib/test/text/StringTest.kt +++ b/libraries/stdlib/test/text/StringTest.kt @@ -8,7 +8,6 @@ package test.text import kotlin.test.* import test.* import test.collections.behaviors.iteratorBehavior -import test.collections.behaviors.setBehavior import test.collections.compare import kotlin.math.sign import kotlin.native.concurrent.SharedImmutable @@ -290,18 +289,26 @@ class StringTest { } @Test fun capitalize() { - assertEquals("A", "A".capitalize()) - assertEquals("A", "a".capitalize()) - assertEquals("Abcd", "abcd".capitalize()) - assertEquals("Abcd", "Abcd".capitalize()) + fun testCapitalize(expected: String, string: String) { + assertEquals(expected, string.capitalize()) + assertEquals(expected, string.replaceFirstChar { it.uppercase() }) + } + testCapitalize("A", "A") + testCapitalize("A", "a") + testCapitalize("Abcd", "abcd") + testCapitalize("Abcd", "Abcd") } @Test fun decapitalize() { - assertEquals("a", "A".decapitalize()) - assertEquals("a", "a".decapitalize()) - assertEquals("abcd", "abcd".decapitalize()) - assertEquals("abcd", "Abcd".decapitalize()) - assertEquals("uRL", "URL".decapitalize()) + fun testDecapitalize(expected: String, string: String) { + assertEquals(expected, string.decapitalize()) + assertEquals(expected, string.replaceFirstChar { it.lowercase() }) + } + testDecapitalize("a", "A") + testDecapitalize("a", "a") + testDecapitalize("abcd", "abcd") + testDecapitalize("abcd", "Abcd") + testDecapitalize("uRL", "URL") } @Test fun slice() { @@ -1450,7 +1457,7 @@ class StringTest { assertEquals("[v-e-r-y-l-o-n-g-s-t-r-oops]", result2) val data3 = "a1/b".toList() - val result3 = data3.joinToString() { it.toUpperCase().toString() } + val result3 = data3.joinToString() { it.uppercase() } assertEquals("A, 1, /, B", result3) } diff --git a/libraries/stdlib/wasm/src/kotlin/Text.kt b/libraries/stdlib/wasm/src/kotlin/Text.kt index 9e2efe0528e..4d0c8fcc931 100644 --- a/libraries/stdlib/wasm/src/kotlin/Text.kt +++ b/libraries/stdlib/wasm/src/kotlin/Text.kt @@ -64,11 +64,73 @@ actual enum class RegexOption { // From char.kt actual fun Char.isWhitespace(): Boolean = TODO("Wasm stdlib: Text") -actual fun Char.toLowerCase(): Char = TODO("Wasm stdlib: Text") -actual fun Char.toUpperCase(): Char = TODO("Wasm stdlib: Text") actual fun Char.isHighSurrogate(): Boolean = TODO("Wasm stdlib: Text") actual fun Char.isLowSurrogate(): Boolean = TODO("Wasm stdlib: Text") +/** + * Converts this character to lower case using Unicode mapping rules of the invariant locale. + */ +public actual fun Char.toLowerCase(): Char = TODO("Wasm stdlib: Text") + +/** + * Converts this character to lower case using Unicode mapping rules of the invariant locale. + * + * This function performs one-to-one character mapping. + * To support one-to-many character mapping use the [lowercase] function. + * If this character has no mapping equivalent, the character itself is returned. + * + * @sample samples.text.Chars.lowercase + */ +@SinceKotlin("1.4") +@ExperimentalStdlibApi +public actual fun Char.lowercaseChar(): Char = TODO("Wasm stdlib: Text") + +/** + * Converts this character to lower case using Unicode mapping rules of the invariant locale. + * + * This function supports one-to-many character mapping, thus the length of the returned string can be greater than one. + * For example, `'\u0130'.lowercase()` returns `"\u0069\u0307"`, + * where `'\u0130'` is the LATIN CAPITAL LETTER I WITH DOT ABOVE character (`İ`). + * If this character has no lower case mapping, the result of `toString()` of this char is returned. + * + * @sample samples.text.Chars.lowercase + */ +@SinceKotlin("1.4") +@ExperimentalStdlibApi +public actual fun Char.lowercase(): String = TODO("Wasm stdlib: Text") + +/** + * Converts this character to upper case using Unicode mapping rules of the invariant locale. + */ +public actual fun Char.toUpperCase(): Char = TODO("Wasm stdlib: Text") + +/** + * Converts this character to upper case using Unicode mapping rules of the invariant locale. + * + * This function performs one-to-one character mapping. + * To support one-to-many character mapping use the [uppercase] function. + * If this character has no mapping equivalent, the character itself is returned. + * + * @sample samples.text.Chars.uppercase + */ +@SinceKotlin("1.4") +@ExperimentalStdlibApi +public actual fun Char.uppercaseChar(): Char = TODO("Wasm stdlib: Text") + +/** + * Converts this character to upper case using Unicode mapping rules of the invariant locale. + * + * This function supports one-to-many character mapping, thus the length of the returned string can be greater than one. + * For example, `'\uFB00'.uppercase()` returns `"\u0046\u0046"`, + * where `'\uFB00'` is the LATIN SMALL LIGATURE FF character (`ff`). + * If this character has no upper case mapping, the result of `toString()` of this char is returned. + * + * @sample samples.text.Chars.uppercase + */ +@SinceKotlin("1.4") +@ExperimentalStdlibApi +public actual fun Char.uppercase(): String = TODO("Wasm stdlib: Text") + // From string.kt @@ -193,17 +255,38 @@ public actual fun String.substring(startIndex: Int, endIndex: Int): String = TOD /** * Returns a copy of this string converted to upper case using the rules of the default locale. - * - * @sample samples.text.Strings.toUpperCase */ public actual fun String.toUpperCase(): String = TODO("Wasm stdlib: Text") /** - * Returns a copy of this string converted to lower case using the rules of the default locale. + * Returns a copy of this string converted to upper case using Unicode mapping rules of the invariant locale. * - * @sample samples.text.Strings.toLowerCase + * This function supports one-to-many and many-to-one character mapping, + * thus the length of the returned string can be different from the length of the original string. + * + * @sample samples.text.Strings.uppercase + */ +@SinceKotlin("1.4") +@ExperimentalStdlibApi +public actual fun String.uppercase(): String = TODO("Wasm stdlib: Text") + +/** + * Returns a copy of this string converted to lower case using the rules of the default locale. */ public actual fun String.toLowerCase(): String = TODO("Wasm stdlib: Text") + +/** + * Returns a copy of this string converted to lower case using Unicode mapping rules of the invariant locale. + * + * This function supports one-to-many and many-to-one character mapping, + * thus the length of the returned string can be different from the length of the original string. + * + * @sample samples.text.Strings.lowercase + */ +@SinceKotlin("1.4") +@ExperimentalStdlibApi +public actual fun String.lowercase(): String = TODO("Wasm stdlib: Text") + public actual fun String.capitalize(): String = TODO("Wasm stdlib: Text") public actual fun String.decapitalize(): String = TODO("Wasm stdlib: Text") public actual fun CharSequence.repeat(n: Int): String = TODO("Wasm stdlib: Text") diff --git a/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt b/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt index 8e87548bb66..e31d9550e55 100644 --- a/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt +++ b/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt @@ -5113,6 +5113,10 @@ public final class kotlin/text/CharsKt { public static final fun getDirectionality (C)Lkotlin/text/CharDirectionality; public static final fun isSurrogate (C)Z public static final fun isWhitespace (C)Z + public static final fun lowercase (CLjava/util/Locale;)Ljava/lang/String; + public static final fun titlecase (C)Ljava/lang/String; + public static final fun titlecase (CLjava/util/Locale;)Ljava/lang/String; + public static final fun uppercase (CLjava/util/Locale;)Ljava/lang/String; } public final class kotlin/text/Charsets {