All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.maryk.yaml.DocumentReader.kt Maven / Gradle / Ivy

package maryk.yaml

import maryk.json.ExceptionWhileReadingJson
import maryk.json.JsonToken
import maryk.json.JsonToken.EndDocument
import maryk.json.JsonToken.StartDocument
import maryk.json.JsonToken.Value
import maryk.json.TokenType
import maryk.json.ValueType
import maryk.lib.exceptions.ParseException
import maryk.yaml.PlainStyleMode.NORMAL

/** Read single or multiple yaml documents until end of stream or "..." */
internal class DocumentReader(
    yamlReader: YamlReaderImpl
) : YamlCharReader(yamlReader),
    IsYamlCharWithIndentsReader {
    private var finishedWithDirectives: Boolean? = null
    private var firstDocumentContentWasFound = false
    private var contentWasFound = false
    private var indentCount: Int = 0

    override fun readUntilToken(extraIndent: Int, tag: TokenType?): JsonToken {
        if (this.lastChar == '\u0000') {
            this.read()
        }

        return when (this.lastChar) {
            '%' -> {
                if (this.finishedWithDirectives == true) {
                    throw InvalidYamlContent("Cannot start another directives block")
                }

                this.finishedWithDirectives = false
                this.read()

                this.directiveReader {
                    this.readUntilToken(0, tag)
                }
            }
            '-' -> {
                try {
                    this.read()
                } catch (e: ExceptionWhileReadingJson) {
                    return plainStringReader("")
                }

                when (this.lastChar) {
                    '-' -> {
                        try {
                            this.read()
                        } catch (e: ExceptionWhileReadingJson) {
                            plainStringReader("-")
                        }
                        when (this.lastChar) {
                            '-' -> {
                                read()
                                return if (this.firstDocumentContentWasFound) {
                                    this.contentWasFound = false
                                    this.indentCount = 0
                                    StartDocument
                                } else {
                                    // First found document open before content
                                    this.finishedWithDirectives = true
                                    this.readUntilToken(0)
                                }
                            }
                            else -> plainStringReader("--")
                        }
                    }
                    ' ', '\n', '\r' -> {
                        checkAlreadyOnIndent()

                        SequenceItemsReader(
                            yamlReader = this.yamlReader,
                            parentReader = this
                        ).let {
                            this.currentReader = it
                            it.readUntilToken(0, tag)
                        }
                    }
                    else -> plainStringReader("-")
                }
            }
            '.' -> {
                try {
                    this.read()
                } catch (e: ExceptionWhileReadingJson) {
                    return plainStringReader("")
                }

                when (this.lastChar) {
                    '.' -> {
                        try {
                            this.read()
                        } catch (e: ExceptionWhileReadingJson) {
                            return plainStringReader(".")
                        }
                        when (this.lastChar) {
                            '.' -> {
                                read()
                                EndDocument
                            }
                            else -> plainStringReader("..")
                        }
                    }
                    else -> plainStringReader(".")
                }
            }
            '#' -> {
                this.commentReader {
                    this.readUntilToken(0, tag)
                }
            }
            '\n' -> {
                read()
                this.readUntilToken(0)
            }
            ' ' -> {
                val indentCount = this.yamlReader.skipEmptyLinesAndCommentsAndCountIndents()

                if (this.indentCount <= 0 && !this.contentWasFound) {
                    this.indentCount = indentCount
                }
                if (indentCount != this.indentCount) {
                    throw InvalidYamlContent("Cannot have a new indent level which is lower than current")
                } else {
                    this.selectReaderAndRead(true, tag, 0) { value, isPlainString, tagg, _ ->
                        createYamlValueToken(value, tagg, isPlainString)
                    }
                }
            }
            else -> {
                if (this.finishedWithDirectives == false) {
                    throw InvalidYamlContent("Directives has to end with an start document --- separator")
                }
                this.checkAlreadyOnIndent()

                return this.selectReaderAndRead(true, tag, 0) { value, isPlainString, tagg, _ ->
                    createYamlValueToken(value, tagg, isPlainString)
                }
            }
        }.also {
            this.firstDocumentContentWasFound = true
            this.contentWasFound = true
        }
    }

    override fun foundMap(tag: TokenType?, startedAtIndent: Int): JsonToken {
        return MapItemsReader(
            this.yamlReader,
            this,
            indentToAdd = startedAtIndent
        ).let {
            this.currentReader = it
            it.readUntilToken(0, tag)
        }
    }

    override fun checkAndCreateFieldName(fieldName: String?, isPlainStringReader: Boolean) =
        throw ParseException("FieldNames are only allowed within maps")

    override fun continueIndentLevel(extraIndent: Int, tag: TokenType?): JsonToken {
        return if (this.indentCount == 0) {
            readUntilToken(extraIndent, tag)
        } else {
            this.selectReaderAndRead(true, tag, extraIndent) { value, isPlainString, tagg, _ ->
                createYamlValueToken(value, tagg, isPlainString)
            }
        }
    }

    override fun endIndentLevel(
        indentCount: Int,
        tag: TokenType?,
        tokenToReturn: (() -> JsonToken)?
    ): JsonToken {
        tokenToReturn?.let {
            this.yamlReader.setUnclaimedIndenting(indentCount)
            return it()
        }
        if (indentCount < this.indentCount && indentCount > 0) {
            throw InvalidYamlContent("Document should not have a lower indent than started")
        }
        return readUntilToken(indentCount, tag)
    }

    override fun indentCount() = this.indentCount

    override fun handleReaderInterrupt(): JsonToken {
        return EndDocument
    }

    private fun plainStringReader(startWith: String): JsonToken {
        checkAlreadyOnIndent()

        return this.plainStringReader(startWith, null, NORMAL, 0) { value, _, _, _ ->
            Value(value, ValueType.String)
        }
    }

    /** Checks if indentation was not reset and thus reading on a lower indent than started */
    private fun checkAlreadyOnIndent() {
        if (this.indentCount > 0 && this.contentWasFound) {
            throw InvalidYamlContent("Document should not have a lower indent than started")
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy