From 2d3c02cdc815de3fef7c5eb5eb40c6e7b0f5e92c Mon Sep 17 00:00:00 2001 From: Andrey Kuleshov Date: Sat, 7 Jan 2023 11:14:51 +0800 Subject: [PATCH] Support for special symbols with Char type (#179) ### What's done: - Previously ktoml did not support special escaped characters while decoding of Char type - Added extra tests for literal strings - Moved logic related to the escaped characters to new utils file --- README.md | 130 ++++++++++-------- .../ktoml/decoders/TomlAbstractDecoder.kt | 6 +- .../ktoml/exceptions/TomlDecodingException.kt | 8 +- .../nodes/pairs/values/TomlBasicString.kt | 110 +-------------- .../ktoml/utils/SpecialCharacters.kt | 129 +++++++++++++++++ .../akuleshov7/ktoml/writers/TomlEmitter.kt | 11 +- .../ktoml/decoders/CharDecoderTest.kt | 23 ++-- .../ktoml/decoders/IntegersDecoderTest.kt | 8 ++ .../ktoml/decoders/ReadMeExampleTest.kt | 15 +- .../ktoml/decoders/StringsDecoderTest.kt | 54 ++++++++ .../ktoml/parsers/StringUtilsTest.kt | 3 +- .../commonTest/resources/simple_example.toml | 2 +- 12 files changed, 309 insertions(+), 190 deletions(-) create mode 100644 ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/utils/SpecialCharacters.kt diff --git a/README.md b/README.md index fde875f0..f7ad9615 100644 --- a/README.md +++ b/README.md @@ -53,17 +53,17 @@ We are still developing and testing this library, so it has several limitations: :white_check_mark: deserialization (with some parsing limitations) \ :white_check_mark: serialization (with tree-related limitations) -**Parsing** \ +**Parsing and decoding** \ :white_check_mark: Table sections (single and dotted) \ :white_check_mark: Key-value pairs (single and dotted) \ -:white_check_mark: Integer type \ -:white_check_mark: Float type \ -:white_check_mark: String type \ -:white_check_mark: Float type \ +:white_check_mark: Long/Integer/Byte/Short types \ +:white_check_mark: Double/Float types \ +:white_check_mark: Basic Strings \ +:white_check_mark: Literal Strings \ +:white_check_mark: Char type \ :white_check_mark: Boolean type \ :white_check_mark: Simple Arrays \ :white_check_mark: Comments \ -:white_check_mark: Literal Strings \ :white_check_mark: Inline Tables \ :white_check_mark: Offset Date-Time (to `Instant` of [kotlinx-datetime](https://github.com/Kotlin/kotlinx-datetime)) \ :white_check_mark: Local Date-Time (to `LocalDateTime` of [kotlinx-datetime](https://github.com/Kotlin/kotlinx-datetime)) \ @@ -264,25 +264,26 @@ someBooleanProperty = true gradle-libs-like-property = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } [table1] -# it can be null or nil, but don't forget to mark it with '?' in the code -# keep in mind, that null is prohibited by TOML spec, but it is very important in Kotlin, so we supported it -# see allowNullValues for a more strict enforcement of the TOML spec -property1 = null -property2 = 6 -# check property3 in Table1 below. As it has the default value, it is not required and can be not provided - -[table2] -someNumber = 5 - [table2."akuleshov7.com"] - name = 'this is a "literal" string' - # empty lists are also supported - configurationList = ["a", "b", "c", null] + # null is prohibited by the TOML spec, but allowed in ktoml for nullable types + # so for 'property1' null value is ok. Use: property1 = null + property1 = 100 + property2 = 6 -# such redeclaration of table2 -# is prohibited in toml specification; -# but ktoml is allowing it in non-strict mode: -[table2] -otherNumber = 5.56 +[table2] + someNumber = 5 +[table2."akuleshov7.com"] + name = 'this is a "literal" string' + # empty lists are also supported + configurationList = ["a", "b", "c"] + + # such redeclaration of table2 + # is prohibited in toml specification; + # but ktoml is allowing it in non-strict mode: + [table2] + otherNumber = 5.56 + # use single quotes + charFromString = 'a' + charFromInteger = 123 ``` can be deserialized to `MyClass`: @@ -292,26 +293,38 @@ data class MyClass( val someBooleanProperty: Boolean, val table1: Table1, val table2: Table2, - @SerialName("gradle-libs-like-property") - val kotlinJvm: GradlePlugin + @SerialName("gradle-libs-like-property") + val kotlinJvm: GradlePlugin ) @Serializable data class Table1( - // nullable values, from toml you can pass null/nil/empty value to this kind of a field + // nullable property, from toml input you can pass "null"/"nil"/"empty" value (no quotes needed) to this field val property1: Long?, - // please note, that according to the specification of toml integer values should be represented with Long - val property2: Long, - // no need to pass this value as it has the default value and is NOT REQUIRED - val property3: Long = 5 + // please note, that according to the specification of toml integer values should be represented with Long, + // but we allow to use Int/Short/etc. Just be careful with overflow + val property2: Byte, + // no need to pass this value in the input as it has the default value and so it is NOT REQUIRED + val property3: Short = 5 ) @Serializable data class Table2( val someNumber: Long, @SerialName("akuleshov7.com") - val inlineTable: InlineTable, - val otherNumber: Double + val inlineTable: NestedTable, + val otherNumber: Double, + // Char in a manner of Java/Kotlin is not supported in TOML, because single quotes are used for literal strings. + // However, ktoml supports reading Char from both single-char string and from it's integer code + val charFromString: Char, + val charFromInteger: Char +) + +@Serializable +data class NestedTable( + val name: String, + @SerialName("configurationList") + val overriddenName: List ) @Serializable @@ -319,7 +332,6 @@ data class GradlePlugin(val id: String, val version: Version) @Serializable data class Version(val ref: String) - ``` with the following code: @@ -332,26 +344,29 @@ Translation of the example above to json-terminology: ```json { "someBooleanProperty": true, + + "gradle-libs-like-property": { + "id": "org.jetbrains.kotlin.jvm", + "version": { + "ref": "kotlin" + } + }, + "table1": { - "property1": 5, + "property1": 100, "property2": 5 }, "table2": { "someNumber": 5, + + "otherNumber": 5.56, "akuleshov7.com": { "name": "my name", "configurationList": [ "a", "b", "c" - ], - "otherNumber": 5.56 - } - }, - "gradle-libs-like-property": { - "id": "org.jetbrains.kotlin.jvm", - "version": { - "ref": "kotlin" + ] } } } @@ -364,22 +379,31 @@ The following example from above: ```toml someBooleanProperty = true +# inline tables in gradle 'libs.versions.toml' notation gradle-libs-like-property = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } -# Comments can be added -# More comments can also be added [table1] - property1 = null # At the end of lines too - property2 = 6 +# null is prohibited by the TOML spec, but allowed in ktoml for nullable types +# so for 'property1' null value is ok. Use: property1 = null. +# Null can also be prohibited with 'allowNullValues = false' +property1 = 100 +property2 = 6 [table2] someNumber = 5 - - # Properties always appear before sub-tables, tables aren't redeclared - otherNumber = 5.56 [table2."akuleshov7.com"] name = 'this is a "literal" string' - configurationList = ["a", "b", "c", null] + # empty lists are also supported + configurationList = ["a", "b", "c"] + +# such redeclaration of table2 +# is prohibited in toml specification; +# but ktoml is allowing it in non-strict mode: +[table2] + otherNumber = 5.56 + # use single quotes + charFromString = 'a' + charFromInteger = 123 ``` can be serialized from `MyClass`: @@ -449,8 +473,6 @@ data class Version(val ref: String) with the following code: ```kotlin -Toml.decodeFromString(/* your toml string */) +Toml.encodeToString(/* your encoded object */) ``` - - diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/decoders/TomlAbstractDecoder.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/decoders/TomlAbstractDecoder.kt index 0b19213c..b42a814c 100644 --- a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/decoders/TomlAbstractDecoder.kt +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/decoders/TomlAbstractDecoder.kt @@ -10,6 +10,7 @@ import com.akuleshov7.ktoml.utils.FloatingPointLimitsEnum import com.akuleshov7.ktoml.utils.FloatingPointLimitsEnum.* import com.akuleshov7.ktoml.utils.IntegerLimitsEnum import com.akuleshov7.ktoml.utils.IntegerLimitsEnum.* +import com.akuleshov7.ktoml.utils.convertSpecialCharacters import kotlinx.datetime.Instant import kotlinx.datetime.LocalDate import kotlinx.datetime.LocalDateTime @@ -42,7 +43,7 @@ public abstract class TomlAbstractDecoder : AbstractDecoder() { // converting to Char from a parsed Literal String (with single quotes: '') is TomlLiteralString -> try { - (value.content as String).single() + (value.content as String).convertSpecialCharacters(keyValue.lineNo).single() } catch (ex: NoSuchElementException) { throw IllegalTypeException("Empty value is not allowed for type [Char], " + "please check the value: [${value.content}] or use [String] type for deserialization of " + @@ -58,7 +59,8 @@ public abstract class TomlAbstractDecoder : AbstractDecoder() { // all other toml tree types are not supported else -> throw IllegalTypeException( "Cannot decode the key [${keyValue.key.last()}] with the value [${keyValue.value.content}]" + - " and with the provided type [Char]. Please check the type in your Serializable class or it's nullability", + " and with the provided type [Char]. Please check the type" + + " in your Serializable class or it's nullability", keyValue.lineNo ) } diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/exceptions/TomlDecodingException.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/exceptions/TomlDecodingException.kt index 49092cd6..dd59fba9 100644 --- a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/exceptions/TomlDecodingException.kt +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/exceptions/TomlDecodingException.kt @@ -9,7 +9,13 @@ import kotlinx.serialization.descriptors.elementNames public sealed class TomlDecodingException(message: String) : SerializationException(message) -internal class ParseException(message: String, lineNo: Int) : TomlDecodingException("Line $lineNo: $message") +internal open class ParseException(message: String, lineNo: Int) : TomlDecodingException("Line $lineNo: $message") + +internal class UnknownEscapeSymbolsException(invalid: String, lineNo: Int) : ParseException( + "According to TOML documentation unknown" + + " escape symbols are not allowed. Please check: [\\$invalid]", + lineNo +) internal class InternalDecodingException(message: String) : TomlDecodingException(message) diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/pairs/values/TomlBasicString.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/pairs/values/TomlBasicString.kt index 1dd63472..514038cb 100644 --- a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/pairs/values/TomlBasicString.kt +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/pairs/values/TomlBasicString.kt @@ -4,10 +4,8 @@ import com.akuleshov7.ktoml.TomlOutputConfig import com.akuleshov7.ktoml.exceptions.ParseException import com.akuleshov7.ktoml.exceptions.TomlWritingException import com.akuleshov7.ktoml.parsers.trimQuotes -import com.akuleshov7.ktoml.utils.appendCodePointCompat -import com.akuleshov7.ktoml.utils.controlCharacterRegex -import com.akuleshov7.ktoml.utils.unescapedBackslashRegex -import com.akuleshov7.ktoml.utils.unescapedDoubleQuoteRegex +import com.akuleshov7.ktoml.utils.convertSpecialCharacters +import com.akuleshov7.ktoml.utils.escapeSpecialCharacters import com.akuleshov7.ktoml.writers.TomlEmitter /** @@ -43,12 +41,6 @@ public class TomlBasicString internal constructor( } public companion object { - private const val COMPLEX_UNICODE_LENGTH = 8 - private const val COMPLEX_UNICODE_PREFIX = 'U' - private const val HEX_RADIX = 16 - private const val SIMPLE_UNICODE_LENGTH = 4 - private const val SIMPLE_UNICODE_PREFIX = 'u' - private fun String.verifyAndTrimQuotes(lineNo: Int): Any = when { // ========= basic string ("abc") ======= @@ -80,103 +72,5 @@ public class TomlBasicString internal constructor( } return this } - - private fun String.convertSpecialCharacters(lineNo: Int): String { - val resultString = StringBuilder() - var i = 0 - while (i < length) { - val currentChar = get(i) - var offset = 1 - if (currentChar == '\\' && i != lastIndex) { - // Escaped - val next = get(i + 1) - offset++ - when (next) { - 't' -> resultString.append('\t') - 'b' -> resultString.append('\b') - 'r' -> resultString.append('\r') - 'n' -> resultString.append('\n') - 'f' -> resultString.append('\u000C') - '\\' -> resultString.append('\\') - '\'' -> resultString.append('\'') - '"' -> resultString.append('"') - SIMPLE_UNICODE_PREFIX, COMPLEX_UNICODE_PREFIX -> - offset += resultString.appendEscapedUnicode(this, next, i + 2, lineNo) - else -> throw ParseException( - "According to TOML documentation unknown" + - " escape symbols are not allowed. Please check: [\\$next]", - lineNo - ) - } - } else { - resultString.append(currentChar) - } - i += offset - } - return resultString.toString() - } - - private fun StringBuilder.appendEscapedUnicode( - fullString: String, - marker: Char, - codeStartIndex: Int, - lineNo: Int - ): Int { - val nbUnicodeChars = if (marker == SIMPLE_UNICODE_PREFIX) { - SIMPLE_UNICODE_LENGTH - } else { - COMPLEX_UNICODE_LENGTH - } - if (codeStartIndex + nbUnicodeChars > fullString.length) { - val invalid = fullString.substring(codeStartIndex - 1) - throw ParseException( - "According to TOML documentation unknown" + - " escape symbols are not allowed. Please check: [\\$invalid]", - lineNo - ) - } - val hexCode = fullString.substring(codeStartIndex, codeStartIndex + nbUnicodeChars) - val codePoint = hexCode.toInt(HEX_RADIX) - try { - appendCodePointCompat(codePoint) - } catch (e: IllegalArgumentException) { - throw ParseException( - "According to TOML documentation unknown" + - " escape symbols are not allowed. Please check: [\\$marker$hexCode]", - lineNo - ) - } - return nbUnicodeChars - } - - private fun String.escapeSpecialCharacters(): String { - val withCtrlCharsEscaped = replace(controlCharacterRegex) { match -> - when (val char = match.value.single()) { - '\t' -> "\\t" - '\b' -> "\\b" - '\n' -> "\\n" - '\u000C' -> "\\f" - '\r' -> "\\r" - else -> { - val code = char.code - - val hexDigits = code.toString(HEX_RADIX) - - "\\$SIMPLE_UNICODE_PREFIX${ - hexDigits.padStart(SIMPLE_UNICODE_LENGTH, '0') - }" - } - } - } - - val withQuotesEscaped = withCtrlCharsEscaped.replace(unescapedDoubleQuoteRegex) { match -> - match.value.replace("\"", "\\\"") - } - - return withQuotesEscaped.replace( - unescapedBackslashRegex, - Regex.escapeReplacement("\\\\") - ) - } } } diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/utils/SpecialCharacters.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/utils/SpecialCharacters.kt new file mode 100644 index 00000000..1a785371 --- /dev/null +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/utils/SpecialCharacters.kt @@ -0,0 +1,129 @@ +/** + * This file contains utility methods for correct processing of escaped characters. + * This logic is used for processing of Chars, Literal Strings and basic Strings. + * + * In TOML we need properly process escaped symbols like '\t', '\n', unicode symbols and other. + * For Literal Strings ('') these symbols should be parsed "as is", for basic strings ("") and chars ('') + * they should be decoded to proper characters. + */ + +package com.akuleshov7.ktoml.utils + +import com.akuleshov7.ktoml.exceptions.UnknownEscapeSymbolsException + +internal const val COMPLEX_UNICODE_LENGTH = 8 +internal const val COMPLEX_UNICODE_PREFIX = 'U' +internal const val HEX_RADIX = 16 +internal const val SIMPLE_UNICODE_LENGTH = 4 +internal const val SIMPLE_UNICODE_PREFIX = 'u' + +/** + * Converting special escaped symbols like newlines, tabs and unicode symbols to proper characters for decoding + * + * @param lineNo line number of a string + * @return returning a string with converted escaped special symbols + * @throws ParseException if unknown escaped symbols were used + * @throws UnknownEscapeSymbolsException + */ +public fun String.convertSpecialCharacters(lineNo: Int): String { + val resultString = StringBuilder() + var i = 0 + while (i < length) { + val currentChar = get(i) + var offset = 1 + if (currentChar == '\\' && i != lastIndex) { + // Escaped + val next = get(i + 1) + offset++ + when (next) { + 't' -> resultString.append('\t') + 'b' -> resultString.append('\b') + 'r' -> resultString.append('\r') + 'n' -> resultString.append('\n') + 'f' -> resultString.append('\u000C') + '\\' -> resultString.append('\\') + '\'' -> resultString.append('\'') + '"' -> resultString.append('"') + SIMPLE_UNICODE_PREFIX, COMPLEX_UNICODE_PREFIX -> + offset += resultString.appendEscapedUnicode(this, next, i + 2, lineNo) + + else -> throw UnknownEscapeSymbolsException("\\$next", lineNo) + } + } else { + resultString.append(currentChar) + } + i += offset + } + return resultString.toString() +} + +/** + * Escaping and converting unicode symbols for decoding + * + * @param fullString + * @param marker + * @param codeStartIndex + * @param lineNo line number of a string + * @return position of + * @throws ParseException + * @throws UnknownEscapeSymbolsException + */ +public fun StringBuilder.appendEscapedUnicode( + fullString: String, + marker: Char, + codeStartIndex: Int, + lineNo: Int +): Int { + val nbUnicodeChars = if (marker == SIMPLE_UNICODE_PREFIX) { + SIMPLE_UNICODE_LENGTH + } else { + COMPLEX_UNICODE_LENGTH + } + if (codeStartIndex + nbUnicodeChars > fullString.length) { + val invalid = fullString.substring(codeStartIndex - 1) + throw UnknownEscapeSymbolsException("\\$invalid", lineNo) + } + val hexCode = fullString.substring(codeStartIndex, codeStartIndex + nbUnicodeChars) + val codePoint = hexCode.toInt(HEX_RADIX) + try { + appendCodePointCompat(codePoint) + } catch (e: IllegalArgumentException) { + throw UnknownEscapeSymbolsException("\\$marker$hexCode", lineNo) + } + return nbUnicodeChars +} + +/** + * Escaping special characters for encoding + * + * @return converted string with escaped special symbols + */ +public fun String.escapeSpecialCharacters(): String { + val withCtrlCharsEscaped = replace(controlCharacterRegex) { match -> + when (val char = match.value.single()) { + '\t' -> "\\t" + '\b' -> "\\b" + '\n' -> "\\n" + '\u000C' -> "\\f" + '\r' -> "\\r" + else -> { + val code = char.code + + val hexDigits = code.toString(HEX_RADIX) + + "\\$SIMPLE_UNICODE_PREFIX${ + hexDigits.padStart(SIMPLE_UNICODE_LENGTH, '0') + }" + } + } + } + + val withQuotesEscaped = withCtrlCharsEscaped.replace(unescapedDoubleQuoteRegex) { match -> + match.value.replace("\"", "\\\"") + } + + return withQuotesEscaped.replace( + unescapedBackslashRegex, + Regex.escapeReplacement("\\\\") + ) +} diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/writers/TomlEmitter.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/writers/TomlEmitter.kt index 74de5904..149cd22d 100644 --- a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/writers/TomlEmitter.kt +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/writers/TomlEmitter.kt @@ -222,13 +222,10 @@ public abstract class TomlEmitter(config: TomlOutputConfig) { public fun emitValue(float: Double): TomlEmitter = emit(when { float.isNaN() -> "nan" - float.isInfinite() -> - if (float > 0) "inf" else "-inf" - else -> { - // Whole-number floats are formatted as integers on JS. - float.toString().let { - if ('.' in it) it else "$it.0" - } + float.isInfinite() -> if (float > 0) "inf" else "-inf" + // Whole-number floats are formatted as integers on JS. + else -> float.toString().let { + if ('.' in it) it else "$it.0" } }) diff --git a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/CharDecoderTest.kt b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/CharDecoderTest.kt index 1a4512f1..8de48e70 100644 --- a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/CharDecoderTest.kt +++ b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/CharDecoderTest.kt @@ -4,7 +4,6 @@ import com.akuleshov7.ktoml.Toml import com.akuleshov7.ktoml.exceptions.IllegalTypeException import kotlinx.serialization.Serializable import kotlinx.serialization.decodeFromString -import kotlin.test.Ignore import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFailsWith @@ -39,10 +38,8 @@ class CharDecoderTest { c = '\t' """ - assertFailsWith { val decoded = Toml.decodeFromString(test) assertEquals(decoded, MyClass('\r', '\n', '\t')) - } } @Test @@ -50,13 +47,23 @@ class CharDecoderTest { val test = """ a = '\u0048' - b = '\u0FCA' - c = '\u0002' + b = '\u0065' + c = '\u006C' """ - assertFailsWith { val decoded = Toml.decodeFromString(test) - assertEquals(decoded, MyClass('{', '\n', '\t')) - } + assertEquals(decoded, MyClass('H', 'e', 'l')) + } + + @Test + fun charSeveralUnicodeSymbolsTest() { + val test = + """ + a = '\u0048\u0065' + b = '\u0065\t' + c = '\u006Cdd' + """ + + assertFailsWith { Toml.decodeFromString(test) } } } diff --git a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/IntegersDecoderTest.kt b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/IntegersDecoderTest.kt index 28460d2a..1a48218e 100644 --- a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/IntegersDecoderTest.kt +++ b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/IntegersDecoderTest.kt @@ -64,5 +64,13 @@ class IntegersDecoderTest { l = 5 """.trimMargin() assertFailsWith { Toml.decodeFromString(test) } + + test = """ + s = 0.25 + b = 5 + i = 5 + l = 5 + """.trimMargin() + assertFailsWith { Toml.decodeFromString(test) } } } diff --git a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/ReadMeExampleTest.kt b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/ReadMeExampleTest.kt index 22126f57..a43d55ff 100644 --- a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/ReadMeExampleTest.kt +++ b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/ReadMeExampleTest.kt @@ -20,7 +20,7 @@ class ReadMeExampleTest { @Serializable data class Table1( - // nullable values, from toml you can pass null/nil/empty value to this kind of a field + // nullable property, from toml input you can pass "null"/"nil"/"empty" value (no quotes needed) to this field val property1: Long?, // please note, that according to the specification of toml integer values should be represented with Long, // but we allow to use Int/Short/etc. Just be careful with overflow @@ -64,7 +64,9 @@ class ReadMeExampleTest { gradle-libs-like-property = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } [table1] - property1 = null # null is prohibited by the TOML spec, + # null is prohibited by the TOML spec, but allowed in ktoml for nullable types + # so for 'property1' null value is ok. Use: property1 = null + property1 = 100 property2 = 6 [table2] @@ -72,7 +74,7 @@ class ReadMeExampleTest { [table2."akuleshov7.com"] name = 'this is a "literal" string' # empty lists are also supported - configurationList = ["a", "b", "c", null] + configurationList = ["a", "b", "c"] # such redeclaration of table2 # is prohibited in toml specification; @@ -82,18 +84,17 @@ class ReadMeExampleTest { # use single quotes charFromString = 'a' charFromInteger = 123 - - """.trimMargin() + """ val decoded = Toml.decodeFromString(test) assertEquals( MyClass( someBooleanProperty = true, - table1 = Table1(property1 = null, property2 = 6), + table1 = Table1(property1 = 100, property2 = 6), table2 = Table2( someNumber = 5, - inlineTable = NestedTable(name = "this is a \"literal\" string", overriddenName = listOf("a", "b", "c", null)), + inlineTable = NestedTable(name = "this is a \"literal\" string", overriddenName = listOf("a", "b", "c")), otherNumber = 5.56, charFromString = 'a', charFromInteger = '{' diff --git a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/StringsDecoderTest.kt b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/StringsDecoderTest.kt index 6bd2f9b1..2717fc33 100644 --- a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/StringsDecoderTest.kt +++ b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/StringsDecoderTest.kt @@ -71,5 +71,59 @@ class StringDecoderTest { ), decoded ) + + test = """ + winpath = "\u0048" + winpath2 = "\u0065" + quoted = "\u006C" + regex = "\u006F" + """ + + decoded = Toml.decodeFromString(test) + assertEquals( + Literals( + "H", + "e", + "l", + "o" + ), + decoded + ) + + test = """ + winpath = "\u0048\u0065\u006C\u006F" + winpath2 = "My\u0048\u0065\u006C\u006FWorld" + quoted = "\u0048\u0065\u006C\u006F World" + regex = "My\u0048\u0065\u006CWorld" + """ + + decoded = Toml.decodeFromString(test) + assertEquals( + Literals( + "Helo", + "MyHeloWorld", + "Helo World", + "MyHelWorld" + ), + decoded + ) + + test = """ + winpath = '\u0048\u0065\u006C\u006F' + winpath2 = 'My\u0048\u0065\u006C\u006FWorld' + quoted = '\u0048\u0065\u006C\u006F World' + regex = 'My\u0048\u0065\u006CWorld' + """ + + decoded = Toml.decodeFromString(test) + assertEquals( + Literals( + "\\u0048\\u0065\\u006C\\u006F", + "My\\u0048\\u0065\\u006C\\u006FWorld", + "\\u0048\\u0065\\u006C\\u006F World", + "My\\u0048\\u0065\\u006CWorld" + ), + decoded + ) } } diff --git a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/parsers/StringUtilsTest.kt b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/parsers/StringUtilsTest.kt index 2248fa90..4716f236 100644 --- a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/parsers/StringUtilsTest.kt +++ b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/parsers/StringUtilsTest.kt @@ -4,7 +4,6 @@ import kotlin.test.Test import kotlin.test.assertEquals class StringUtilsTest { - @Test fun testForTakeBeforeComment() { var lineWithoutComment = "test_key = \"test_value\" # \" some comment".takeBeforeComment(false) @@ -47,4 +46,4 @@ class StringUtilsTest { """.trimIndent().trimComment(true) assertEquals("\\abc", comment) } -} \ No newline at end of file +} diff --git a/ktoml-file/src/commonTest/resources/simple_example.toml b/ktoml-file/src/commonTest/resources/simple_example.toml index cd84341a..6b8da607 100644 --- a/ktoml-file/src/commonTest/resources/simple_example.toml +++ b/ktoml-file/src/commonTest/resources/simple_example.toml @@ -3,7 +3,7 @@ title = "TOML \"Example\"" [owner] -name = "Tom Preston-Werner" +name = "Tom Pr\u0065ston-Werner" dob = "1979-05-27T07:32:00-08:00" # First class dates [database]