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

org.fernice.flare.style.properties.DeclarationBlock.kt Maven / Gradle / Ivy

/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */
package org.fernice.flare.style.properties

import org.fernice.flare.cssparser.*
import org.fernice.flare.selector.SelectorList
import org.fernice.flare.style.ContextualError
import org.fernice.flare.style.Importance
import org.fernice.flare.style.ParserContext
import org.fernice.flare.style.StyleParseErrorKind
import org.fernice.std.*
import java.util.*

class PropertyDeclarationBlock {

    private val declarations: MutableList = mutableListOf()
    private val importances: BitSet = BitSet()

    fun expand(declarations: List, importance: Importance) {
        val index = this.declarations.size

        if (importance == Importance.Important) {
            this.importances.set(index, index + declarations.size)
        } else {
            this.importances.clear(index, index + declarations.size)
        }

        this.declarations.addAll(declarations)
    }

    fun hasImportant(): Boolean {
        return !importances.isEmpty
    }

    fun asSequence(): Sequence {
        return declarations.asSequence()
    }

    fun asSequence(importance: Importance, reversed: Boolean = false): Sequence {
        return if (reversed) {
            declarations
                .asReversedSequence()
                .filterIndexed { index, _ -> importances.toImportance(index) == importance }
        } else {
            declarations
                .asSequence()
                .filterIndexed { index, _ -> importances.toImportance(index) == importance }
        }
    }

    val size: Int get() = declarations.size

    override fun toString(): String = "PropertyDeclarationBlock[${declarations.size} declarations]"

    companion object {

        fun parsePropertyDeclarationList(
            context: ParserContext,
            input: Parser,
            selectors: List,
        ): PropertyDeclarationBlock {
            val state = DeclarationParserState()

            val parser = PropertyDeclarationParser(context, state)
            val iter = RuleBodyParser(input, parser)

            while (true) {
                when (val result = iter.next() ?: break) {
                    is Ok -> {}
                    is Err -> state.didError(context, result.value.error, result.value.slice)
                }
            }
            state.flushErrors(context, selectors)

            return state.takeDeclarations()
        }
    }
}

private fun BitSet.toImportance(index: Int): Importance = if (get(index)) Importance.Important else Importance.Normal

class PropertyDeclarationParser(
    private val context: ParserContext,
    private val state: DeclarationParserState,
) : RuleBodyItemParser {

    override fun parseValue(name: String, input: Parser): Result {
        return state.parseValue(context, name, input)
    }

    override fun shouldParseDeclarations(): Boolean = true
    override fun shouldParseQualifiedRule(): Boolean = false
}

class DeclarationParserState {
    private var declarationBlock = PropertyDeclarationBlock()
    private val declarations = mutableListOf()
    private var lastParsedPropertyId: PropertyId? = null
    private val errors = mutableListOf()

    fun parseValue(
        context: ParserContext,
        name: String,
        input: Parser,
    ): Result {
        val id = when (val id = PropertyId.parse(name)) {
            is Ok -> id.value
            is Err -> return Err(input.newError(StyleParseErrorKind.UnknownProperty(name)))
        }

        lastParsedPropertyId = id

        input.parseUntilBefore(Delimiters.Bang) { nestedInput ->
            PropertyDeclaration.parseInto(declarations, id, context, nestedInput)
        }.propagate { return it }

        val importance = when (input.tryParse(::parseImportant)) {
            is Ok -> Importance.Important
            is Err -> Importance.Normal
        }

        input.expectExhausted().propagate { return it }

        declarationBlock.expand(declarations.drain(), importance)

        lastParsedPropertyId = null

        return Ok()
    }

    fun didError(context: ParserContext, error: ParseError, slice: String) {
        if (!context.isErrorReportingEnabled()) return

        errors.add(PropertyParseError(error, slice, lastParsedPropertyId))
    }

    fun flushErrors(context: ParserContext, selectors: List) {
        if (errors.isEmpty()) return

        for ((error, slice, propertyId) in errors) {
            context.reportError(selectors, error, slice, propertyId)
        }
    }

    @Suppress("NAME_SHADOWING")
    private fun ParserContext.reportError(selectors: List, error: ParseError, slice: String, propertyId: PropertyId?) {
        var error = error

        if (propertyId != null) {
            val name = when (propertyId) {
                is PropertyId.Longhand -> propertyId.id.name
                is PropertyId.Shorthand -> propertyId.id.name
                is PropertyId.Custom -> propertyId.name.value
            }
            error = when (error.kind) {
                is StyleParseErrorKind.UnknownProperty -> error
                else -> ParseError(
                    StyleParseErrorKind.InvalidPropertyValue(name, error),
                    error.location,
                )
            }
        }

        reportError(
            error.location,
            ContextualError.UnsupportedPropertyDeclaration(slice, error, selectors),
        )
    }

    fun hasDeclarations(): Boolean {
        return declarationBlock.size > 0
    }

    fun takeDeclarations(): PropertyDeclarationBlock {
        val declarations = declarationBlock
        declarationBlock = PropertyDeclarationBlock()
        return declarations
    }

    private data class PropertyParseError(
        val error: ParseError,
        val slice: String,
        val propertyId: PropertyId?,
    )
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy