Locale-agnostic case conversions by default #KT-43023

This commit is contained in:
Abduqodiri Qurbonzoda
2020-10-02 06:46:42 +03:00
parent 80289e4a3f
commit 1314adb6f7
25 changed files with 805 additions and 142 deletions
+154 -9
View File
@@ -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.
@@ -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
}
/**
+36 -22
View File
@@ -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