Skip to content

Commit

Permalink
Hotfix for parsing of equals sign (there could be more than one equal…
Browse files Browse the repository at this point in the history
… sign in the string)
  • Loading branch information
orchestr7 committed Oct 13, 2021
1 parent d46a44b commit ce92267
Showing 5 changed files with 42 additions and 20 deletions.
Original file line number Diff line number Diff line change
@@ -27,9 +27,17 @@ public inline class TomlParser(private val ktomlConf: KtomlConf) {
return parseStringsToTomlTree(tomlString.split("\n"))
}

/**
* Parsing the list of strings to the TOML intermediate representation (TOML- abstract syntax tree).
*
* @param ktomlLines list with toml strings (line by line)
* @return the root node of the resulted toml tree
* @throws InternalAstException - if toml node does not inherit TomlNode class
*/
public fun parseStringsToTomlTree(ktomlLines: List<String>): TomlFile {
var currentParent: TomlNode = TomlFile()
val tomlFileHead = currentParent as TomlFile
// need to trim empty lines BEFORE the start of processing
val mutableKtomlLines = ktomlLines.toMutableList().trimEmptyLines()

mutableKtomlLines.forEachIndexed { index, line ->
@@ -39,17 +47,15 @@ public inline class TomlParser(private val ktomlConf: KtomlConf) {
val tableSection = TomlTable(line, lineNo)
// if the table is the last line in toml, than it has no children and we need to
// add at least fake node as a child
if (index == mutableKtomlLines.size - 1) {
if (index == mutableKtomlLines.lastIndex) {
tableSection.appendChild(TomlStubEmptyNode(lineNo))
}

val newParent = tomlFileHead.insertTableToTree(tableSection)
// covering the case when table contains no key-value pairs or no tables (after our insertion)
// covering the case when processed table contains no key-value pairs or no tables (after our insertion)
// adding fake nodes to a previous table (it has no children because we have found another table right after)
if (currentParent.hasNoChildren()) {
currentParent.appendChild(TomlStubEmptyNode(currentParent.lineNo))
}
currentParent = newParent
currentParent = tomlFileHead.insertTableToTree(tableSection)
} else {
val keyValue = line.parseTomlKeyValue(lineNo, ktomlConf)
if (keyValue !is TomlNode) {
Original file line number Diff line number Diff line change
@@ -65,24 +65,32 @@ public fun String.splitKeyValue(lineNo: Int, ktomlConf: KtomlConf): Pair<String,
this.lastIndexOf("\"\"\"")
).filterNot { it == -1 }.maxOrNull() ?: 0

// finding the index of a commented part of the string
// search starts goes from the closingQuoteIndex to the end of the string
val firstHash = (closingQuoteIndex until this.length).filter { this[it] == '#' }.minOrNull() ?: this.length

val keyValue = this.substring(0, firstHash)
.split("=")
.map { it.trim() }
// searching for an equals sign that should be placed main part of the string (not in the comment)
val firstEqualsSign = this.substring(0, firstHash).indexOfFirst { it == '=' }

if (keyValue.size != 2) {
// equals sign not found in the string
if (firstEqualsSign == -1) {
throw TomlParsingException(
"Incorrect format of Key-Value pair." +
"Incorrect format of Key-Value pair (missing equals sign)." +
" Should be <key = value>, but was: $this." +
" If you wanted to define table - use brackets []",
lineNo
)
}

val keyStr = keyValue.getKeyValuePart("key", 0, this, ktomlConf, lineNo)
val valueStr = keyValue.getKeyValuePart("value", 1, this, ktomlConf, lineNo)
return Pair(keyStr, valueStr)
// aaaa = bbbb # comment -> aaaa
val key = this.substring(0, firstEqualsSign).trim()
// aaaa = bbbb # comment -> bbbb
val value = this.substring(firstEqualsSign + 1, firstHash).trim()

return Pair(
key.checkNotEmpty("key", this, ktomlConf, lineNo),
value.checkNotEmpty("value", this, ktomlConf, lineNo)
)
}

/**
@@ -113,17 +121,16 @@ public fun String.parseValue(lineNo: Int): TomlValue = when (this) {
/**
* method to get proper value from content to get key or value
*/
private fun List<String>.getKeyValuePart(
private fun String.checkNotEmpty(
log: String,
index: Int,
content: String,
ktomlConf: KtomlConf,
lineNo: Int
): String =
this[index].trim().also {
this.also {
// key should never be empty, but the value can be empty (and treated as null)
// see the discussion: https://github.com/toml-lang/toml/issues/30
if ((!ktomlConf.emptyValuesAllowed || index == 0) && it.isBlank()) {
if ((!ktomlConf.emptyValuesAllowed || log == "key") && it.isBlank()) {
throw TomlParsingException(
"Incorrect format of Key-Value pair. It has empty $log: $content",
lineNo
Original file line number Diff line number Diff line change
@@ -27,7 +27,7 @@ public class TomlBasicString(content: String, lineNo: Int) : TomlValue(lineNo) {
} else {
throw TomlParsingException(
"According to the TOML specification string values (even Enums)" +
" should be wrapped with quotes (\"\"): <$content>", lineNo
" should be wrapped with quotes (\"\"), but the following value was not: <$content>", lineNo
)
}

Original file line number Diff line number Diff line change
@@ -5,11 +5,13 @@ import com.akuleshov7.ktoml.Toml
import kotlinx.serialization.decodeFromString
import com.akuleshov7.ktoml.exceptions.InvalidEnumValueException
import com.akuleshov7.ktoml.exceptions.MissingRequiredFieldException
import com.akuleshov7.ktoml.exceptions.TomlParsingException
import com.akuleshov7.ktoml.exceptions.UnknownNameDecodingException
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFails
import kotlin.test.assertFailsWith

@ExperimentalSerializationApi
@@ -75,6 +77,12 @@ class GeneralDecoderTest {
assertEquals(SimpleTomlCase(Table1(5, 6)), Toml.decodeFromString(test))
}

@Test
fun testForSimpleTomlCaseWithIssueInParsing() {
val test = "[table1]\n b = 6 = 7 \n a = 5 "
assertFailsWith<TomlParsingException> { Toml.decodeFromString(test) }
}

@Test
fun testForTwoTomlTablesCase() {
val test = ("[table1]\n" +
Original file line number Diff line number Diff line change
@@ -67,16 +67,17 @@ class ValueParserTest {
assertEquals("hello\t\\\\world", test.value.content)

// regression test related to comments with an equals symbol after it
val pairTest =
var pairTest =
"lineCaptureGroup = 1 # index `warningTextHasLine = false`\n".splitKeyValue(0, ktomlConf = KtomlConf())
assertEquals(1L, TomlKeyValueSimple(pairTest, 0).value.content)

pairTest = "lineCaptureGroup = \"1 = 2\" # index = `warningTextHasLine = false`\n".splitKeyValue(0, ktomlConf = KtomlConf())
assertEquals("1 = 2", TomlKeyValueSimple(pairTest, 0).value.content)
}


@Test
fun parsingIssueValue() {
assertFailsWith<TomlParsingException> { " a = b = c".splitKeyValue(0, ktomlConf = KtomlConf()) }
assertFailsWith<TomlParsingException> { " = false".splitKeyValue(0, ktomlConf = KtomlConf()) }
assertFailsWith<TomlParsingException> { " just false".splitKeyValue(0, ktomlConf = KtomlConf()) }
assertFailsWith<TomlParsingException> { TomlKeyValueSimple(Pair("a", "\"\\hello tworld\""), 0) }

0 comments on commit ce92267

Please sign in to comment.