diff --git a/libraries/stdlib/src/kotlin/text/Strings.kt b/libraries/stdlib/src/kotlin/text/Strings.kt index 57c44f98542..6256563df74 100644 --- a/libraries/stdlib/src/kotlin/text/Strings.kt +++ b/libraries/stdlib/src/kotlin/text/Strings.kt @@ -95,3 +95,155 @@ public fun Array.join(separator: String = ", ", prefix: String = "", pos public fun Stream.join(separator: String = ", ", prefix: String = "", postfix: String = "", limit: Int = -1, truncated: String = "..."): String { return joinToString(separator, prefix, postfix, limit, truncated) } +/** + * Returns a substring before first occurrence of delimiter. In case of no delimiter, returns the whole string. + */ +public fun String.substringBefore(delimiter: Char): String { + val index = indexOf(delimiter) + return if (index == -1) this else substring(0, index) +} + +/** + * Returns a substring before first occurrence of delimiter. In case of no delimiter, returns the whole string. + */ +public fun String.substringBefore(delimiter: String): String { + val index = indexOf(delimiter) + return if (index == -1) this else substring(0, index) +} +/** + * Returns a substring after first occurrence of delimiter. In case of no delimiter, returns an empty string. + */ +public fun String.substringAfter(delimiter: Char): String { + val index = indexOf(delimiter) + return if (index == -1) "" else substring(index + 1, length) +} + +/** + * Returns a substring after first occurrence of delimiter. In case of no delimiter, returns an empty string. + */ +public fun String.substringAfter(delimiter: String): String { + val index = indexOf(delimiter) + return if (index == -1) "" else substring(index + delimiter.length, length) +} + +/** + * Returns a substring before last occurrence of delimiter. In case of no delimiter, returns the whole string. + */ +public fun String.substringBeforeLast(delimiter: Char): String { + val index = lastIndexOf(delimiter) + return if (index == -1) this else substring(0, index) +} + +/** + * Returns a substring before last occurrence of delimiter. In case of no delimiter, returns the whole string. + */ +public fun String.substringBeforeLast(delimiter: String): String { + val index = lastIndexOf(delimiter) + return if (index == -1) this else substring(0, index) +} + +/** + * Returns a substring after last occurrence of delimiter. In case of no delimiter, returns an empty string. + */ +public fun String.substringAfterLast(delimiter: Char): String { + val index = lastIndexOf(delimiter) + return if (index == -1) "" else substring(index + 1, length) +} + +/** + * Returns a substring after last occurrence of delimiter. In case of no delimiter, returns an empty string. + */ +public fun String.substringAfterLast(delimiter: String): String { + val index = lastIndexOf(delimiter) + return if (index == -1) "" else substring(index + delimiter.length, length) +} + +/** + * Replace part of string at given range with replacement string + */ +public fun String.replaceRange(firstIndex: Int, lastIndex: Int, replacement: String): String { + if (lastIndex < firstIndex) + throw IndexOutOfBoundsException("Last index ($lastIndex) is less than first index ($firstIndex)") + val sb = StringBuilder() + sb.append(this, 0, firstIndex) + sb.append(replacement) + sb.append(this, lastIndex, length) + return sb.toString() +} + +/** + * Replace part of string at given range with replacement string + */ +public fun String.replaceRange(range: IntRange, replacement: String): String { + if (range.end < range.start) + throw IndexOutOfBoundsException("Last index (${range.start}) is less than first index (${range.end})") + val sb = StringBuilder() + sb.append(this, 0, range.start) + sb.append(replacement) + sb.append(this, range.end, length) + return sb.toString() +} + +/** + * Replace part of string before first occurrence of given delimiter with replacement string + */ +public fun String.replaceBefore(delimiter: Char, replacement: String): String { + val index = indexOf(delimiter) + return if (index == -1) replacement else replaceRange(0, index, replacement) +} + +/** + * Replace part of string before first occurrence of given delimiter with replacement string + */ +public fun String.replaceBefore(delimiter: String, replacement: String): String { + val index = indexOf(delimiter) + return if (index == -1) replacement else replaceRange(0, index, replacement) +} + +/** + * Replace part of string after first occurrence of given delimiter with replacement string + */ +public fun String.replaceAfter(delimiter: Char, replacement: String): String { + val index = indexOf(delimiter) + return if (index == -1) this else replaceRange(index + 1, length, replacement) +} + +/** + * Replace part of string after first occurrence of given delimiter with replacement string + */ +public fun String.replaceAfter(delimiter: String, replacement: String): String { + val index = indexOf(delimiter) + return if (index == -1) this else replaceRange(index + delimiter.length, length, replacement) +} + +/** + * Replace part of string after last occurrence of given delimiter with replacement string + */ +public fun String.replaceAfterLast(delimiter: String, replacement: String): String { + val index = lastIndexOf(delimiter) + return if (index == -1) this else replaceRange(index + delimiter.length, length, replacement) +} + +/** + * Replace part of string after last occurrence of given delimiter with replacement string + */ +public fun String.replaceAfterLast(delimiter: Char, replacement: String): String { + val index = lastIndexOf(delimiter) + return if (index == -1) this else replaceRange(index + 1, length, replacement) +} + +/** + * Replace part of string before last occurrence of given delimiter with replacement string + */ +public fun String.replaceBeforeLast(delimiter: Char, replacement: String): String { + val index = lastIndexOf(delimiter) + return if (index == -1) replacement else replaceRange(0, index, replacement) +} + +/** + * Replace part of string before last occurrence of given delimiter with replacement string + */ +public fun String.replaceBeforeLast(delimiter: String, replacement: String): String { + val index = lastIndexOf(delimiter) + return if (index == -1) replacement else replaceRange(0, index, replacement) +} diff --git a/libraries/stdlib/test/text/StringTest.kt b/libraries/stdlib/test/text/StringTest.kt index a1d584ec0fc..d8d9a9942cb 100644 --- a/libraries/stdlib/test/text/StringTest.kt +++ b/libraries/stdlib/test/text/StringTest.kt @@ -66,6 +66,62 @@ class StringTest { test fun indices() { assertEquals(0..4, "abcde".indices) assertEquals(0..0, "a".indices) - assertEquals(IntRange.EMPTY, "".indices) + assertTrue("".indices.isEmpty()) } + + test fun replaceRange() { + val s = "sample text" + assertEquals("sa??e text", s.replaceRange(2, 5, "??")) + assertEquals("sa??e text", s.replaceRange(2..5, "??")) + fails { + s.replaceRange(5..2, "??") + } + fails { + s.replaceRange(5, 2, "??") + } + } + + test fun substringDelimited() { + val s = "-1,22,3+" + // chars + assertEquals("22,3+", s.substringAfter(',')) + assertEquals("3+", s.substringAfterLast(',')) + assertEquals("-1", s.substringBefore(',')) + assertEquals("-1,22", s.substringBeforeLast(',')) + + // strings + assertEquals("22,3+", s.substringAfter(",")) + assertEquals("3+", s.substringAfterLast(",")) + assertEquals("-1", s.substringBefore(",")) + assertEquals("-1,22", s.substringBeforeLast(",")) + + // non-existing delimiter + assertEquals("", s.substringAfter("+")) + assertEquals("", s.substringBefore("-")) + assertEquals(s, s.substringBefore("=")) + assertEquals("", s.substringAfter("=")) + + } + + test fun replaceDelimited() { + val s = "/user/folder/file.extension" + // chars + assertEquals("/user/folder/file.doc", s.replaceAfter('.', "doc")) + assertEquals("/user/folder/another.doc", s.replaceAfterLast('/', "another.doc")) + assertEquals("new name.extension", s.replaceBefore('.', "new name")) + assertEquals("/new/path/file.extension", s.replaceBeforeLast('/', "/new/path")) + + // strings + assertEquals("/user/folder/file.doc", s.replaceAfter(".", "doc")) + assertEquals("/user/folder/another.doc", s.replaceAfterLast("/", "another.doc")) + assertEquals("new name.extension", s.replaceBefore(".", "new name")) + assertEquals("/new/path/file.extension", s.replaceBeforeLast("/", "/new/path")) + + // non-existing delimiter + assertEquals("/user/folder/file.extension", s.replaceAfter("=", "doc")) + assertEquals("/user/folder/file.extension", s.replaceAfterLast("=", "another.doc")) + assertEquals("new name", s.replaceBefore("=", "new name")) + assertEquals("/new/path", s.replaceBeforeLast("=", "/new/path")) + } + }