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

org.fernice.flare.selector.Selector.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.selector

import org.fernice.flare.cssparser.*
import org.fernice.flare.panic
import org.fernice.flare.style.QuirksMode
import org.fernice.std.*
import java.io.Writer

data class NamespacePrefix(val prefix: String)

data class NamespaceUrl(val prefix: NamespacePrefix, val url: String)

sealed class Component : ToCss {

    data class Combinator(val combinator: org.fernice.flare.selector.Combinator) : Component()

    data class LocalName(val localName: String, val localNameLower: String) : Component()

    data class ID(val id: String) : Component()
    data class Class(val styleClass: String) : Component()

    data class AttributeInNoNamespaceExists(
        val localName: String,
        val localNameLower: String,
    ) : Component()

    data class AttributeInNoNamespace(
        val localName: String,
        val localNameLower: String,
        val operator: AttributeSelectorOperator,
        val value: String,
        val caseSensitive: Boolean,
        val neverMatches: Boolean,
    ) : Component()

    data class AttributeOther(
        val namespace: NamespaceConstraint,
        val localName: String,
        val localNameLower: String,
        val operation: AttributeSelectorOperation,
        val neverMatches: Boolean,
    ) : Component()

    data object ExplicitNoNamespace : Component()
    data object ExplicitAnyNamespace : Component()
    data class DefaultNamespace(val namespace: NamespaceUrl) : Component()
    data class Namespace(val prefix: NamespacePrefix, val namespace: NamespaceUrl) : Component()

    data object ExplicitUniversalType : Component()

    data class Negation(val selectors: List) : Component()

    data object ParentSelector : Component()

    data object Root : Component()
    data object Empty : Component()
    data object Scope : Component()

    data class Nth(val data: NthData, val selectors: List = emptyList()) : Component()

    data class NonTSPseudoClass(val pseudoClass: org.fernice.flare.selector.NonTSPseudoClass) : Component()
    data class NonTSFPseudoClass(val pseudoClass: org.fernice.flare.selector.NonTSFPseudoClass) : Component()

    data class Part(val names: List) : Component()
    data class Slotted(val selector: Selector) : Component()
    data class Host(val selector: Selector?) : Component()

    data class Where(val selectors: List) : Component()
    data class Is(val selectors: List) : Component()
    data class Has(val selectors: List) : Component()

    data class PseudoElement(val pseudoElement: org.fernice.flare.selector.PseudoElement) : Component()

    data object RelativeSelectorAnchor : Component()

    fun ancestorHash(quirksMode: QuirksMode): Int? {
        return when (this) {
            is DefaultNamespace -> hashString(namespace.url)
            is Namespace -> hashString(namespace.url)
            is LocalName -> if (localName == localNameLower) hashString(localName) else null
            is ID -> if (quirksMode != QuirksMode.Quirks) hashString(id) else null
            is Class -> if (quirksMode != QuirksMode.Quirks) hashString(styleClass) else null
            else -> null
        }
    }

    override fun toCss(writer: Writer) {
        fun Int.toStringWithSign(): String {
            return if (this >= 0) "+$this" else this.toString()
        }

        fun Writer.appendNth(a: Int, b: Int) {
            append(
                when {
                    a == 0 && b == 0 -> "0"

                    a == 1 && b == 0 -> "n"
                    a == -1 && b == 0 -> "-n"
                    b == 0 -> "${a}n"

                    a == 0 -> b.toString()
                    a == 1 -> "n${b.toStringWithSign()}"
                    a == -1 -> "-n${b.toStringWithSign()}"
                    else -> "${a}n${b.toStringWithSign()}"
                }
            )
        }

        fun Writer.appendSelectors(selectors: List) {
            for ((index, selector) in selectors.withIndex()) {
                selector.toCss(this)
                if (index < selectors.lastIndex) append(", ")
            }
        }

        when (this) {
            is Combinator -> this.combinator.toCss(writer)

            is LocalName -> writer.append(localName)

            is ID -> {
                writer.append('#')
                writer.append(this.id)
            }

            is Class -> {
                writer.append(".")
                writer.append(this.styleClass)
            }

            is ExplicitUniversalType -> writer.append('*')

            is ExplicitNoNamespace -> writer.append('|')
            is ExplicitAnyNamespace -> writer.append("*|")
            is DefaultNamespace -> Unit
            is Namespace -> {
                writer.append(this.prefix.prefix)
                writer.append('|')
            }

            is AttributeInNoNamespaceExists -> {
                writer.append('[')
                writer.append(this.localName)
                writer.append(']')
            }

            is AttributeInNoNamespace -> {
                writer.append('[')
                writer.append(this.localName)
                this.operator.toCss(writer)
                writer.append('"')
                writer.append(this.value)
                writer.append('"')
                if (this.caseSensitive) {
                    writer.append(" i")
                }
                writer.append(']')
            }

            is AttributeOther -> TODO("Implement toCss(Writer)")

            is Negation -> {
                writer.append(":not(")
                writer.appendSelectors(selectors)
                writer.append(")")
            }

            is ParentSelector -> writer.append("&")

            is Root -> writer.append(":root")
            is Empty -> writer.append(":empty")
            is Scope -> writer.append(":scope")

            is Nth -> {
                if (!data.isFunction) {
                    when (data.type) {
                        NthType.Child -> writer.append(":first-child")
                        NthType.LastChild -> writer.append(":last-child")
                        NthType.OnlyChild -> writer.append(":only-child")
                        NthType.OfType -> writer.append(":first-of-type")
                        NthType.LastOfType -> writer.append(":last-of-type")
                        NthType.OnlyOfType -> writer.append(":only-of-type")
                    }
                } else {
                    when (data.type) {
                        NthType.Child -> writer.append(":nth-child")
                        NthType.LastChild -> writer.append(":nth-last-child")
                        NthType.OnlyChild -> error("invalid case")
                        NthType.OfType -> writer.append(":nth-of-type")
                        NthType.LastOfType -> writer.append(":nth-last-of-type")
                        NthType.OnlyOfType -> error("invalid case")
                    }
                    writer.append("(")
                    writer.appendNth(data.a, data.b)

                    if (selectors.isNotEmpty()) {
                        writer.append(" of ")
                        writer.appendSelectors(selectors)
                    }

                    writer.append(")")
                }
            }

            is NonTSPseudoClass -> pseudoClass.toCss(writer)
            is NonTSFPseudoClass -> pseudoClass.toCss(writer)

            is Part -> {
                writer.append(":part(")
                writer.append(names.joinToString())
                writer.append(")")
            }

            is Slotted -> {
                writer.append(":slotted(")
                selector.toCss(writer)
                writer.append(")")
            }

            is Host -> {
                writer.append(":host")
                if (selector != null) {
                    writer.append("(")
                    selector.toCss(writer)
                    writer.append(")")
                }
            }

            is Where -> {
                writer.append(":where(")
                writer.appendSelectors(selectors)
                writer.append(")")
            }

            is Is -> {
                writer.append(":is(")
                writer.appendSelectors(selectors)
                writer.append(")")
            }

            is Has -> {
                writer.append(":has(")
                writer.appendSelectors(selectors.map { it.selector })
                writer.append(")")
            }

            is PseudoElement -> this.pseudoElement.toCss(writer)

            is RelativeSelectorAnchor -> {}
        }
    }
}

enum class Combinator : ToCss {

    Child,
    Descendant,
    NextSibling,
    LaterSibling,
    Part,
    SlotAssignment,
    PseudoElement;

    fun isSibling(): Boolean {
        return when (this) {
            Child,
            Descendant,
            -> false

            NextSibling,
            LaterSibling,
            -> true

            Part,
            SlotAssignment,
            PseudoElement,
            -> false
        }
    }

    override fun toCss(writer: Writer) {
        return when (this) {
            Child -> writer.write(" > ")
            Descendant -> writer.write(" ")
            NextSibling -> writer.write(" + ")
            LaterSibling -> writer.write(" ~ ")
            Part,
            SlotAssignment,
            PseudoElement,
            -> {
            }
        }
    }
}

enum class NthType {
    Child,
    LastChild,
    OnlyChild,
    OfType,
    LastOfType,
    OnlyOfType;

    val isOfType: Boolean
        get() = when (this) {
            OfType, LastOfType, OnlyOfType -> true
            Child, LastChild, OnlyChild -> false
        }

    val isOnly: Boolean
        get() = when (this) {
            OnlyChild, OnlyOfType -> true
            Child, LastChild, OfType, LastOfType -> false
        }

    val isFromEnd: Boolean
        get() = when (this) {
            LastChild, LastOfType -> true
            Child, OnlyChild, OnlyOfType, OfType -> false
        }
}

data class NthData(
    val type: NthType,
    val a: Int,
    val b: Int,
    val isFunction: Boolean,
) {

    companion object {
        fun first(ofType: Boolean): NthData {
            val type = when {
                ofType -> NthType.OfType
                else -> NthType.Child
            }
            return NthData(type, a = 0, b = 1, isFunction = false)
        }

        fun last(ofType: Boolean): NthData {
            val type = when {
                ofType -> NthType.LastOfType
                else -> NthType.LastChild
            }
            return NthData(type, a = 0, b = 1, isFunction = false)
        }

        fun only(ofType: Boolean): NthData {
            val type = when {
                ofType -> NthType.OnlyOfType
                else -> NthType.OnlyChild
            }
            return NthData(type, a = 0, b = 1, isFunction = false)
        }
    }
}

enum class PseudoElement : ToCss {

    Before,
    After,
    Selection,
    FirstLetter,
    FirstLine,
    Placeholder,

    Flare_Icon;

    fun acceptsStatePseudoClasses(): Boolean = false

    fun validAfterSlotted(): Boolean = false

    override fun toCss(writer: Writer) {
        val css = when (this) {
            Before -> "::before"
            After -> "::after"
            Selection -> "::selection"
            FirstLetter -> "::first-letter"
            FirstLine -> "::first-line"
            Placeholder -> "::placeholder"

            Flare_Icon -> "::icon"
        }

        writer.write(css)
    }
}

// NonTreeStructural-PseudoClass
enum class NonTSPseudoClass : ToCss {

    Active,
    Checked,
    Autofill,
    Disabled,
    Enabled,
    Defined, // HTML specific
    Focus,
    FocusVisible,
    FocusWithin,
    Hover,
    Target,
    Indeterminate,
    Fullscreen,
    Modal,
    Optional,
    Required,
    Valid,
    Invalid,
    UserValid,
    UserInvalid,
    InRange,
    OutOfRange,
    ReadWrite,
    ReadOnly,
    Default,
    PlaceholderShown,
    Link,
    AnyLink,
    Visited;

    fun isActiveOrHover(): Boolean = when (this) {
        Hover, Active -> true
        else -> false
    }

    fun isUserActionState(): Boolean = when (this) {
        Focus, Hover, Active -> true
        else -> false
    }

    override fun toCss(writer: Writer) {
        writer.append(
            when (this) {
                Active -> ":active"
                Checked -> ":checked"
                Autofill -> ":autofilled"
                Disabled -> ":disabled"
                Enabled -> ":enabled"
                Defined -> ":defined"
                Focus -> ":focus"
                FocusVisible -> ":focus-visible"
                FocusWithin -> ":focus-visible"
                Hover -> ":hover"
                Target -> ":target"
                Indeterminate -> ":indeterminate"
                Fullscreen -> ":fullscreen"
                Modal -> ":modal"
                Optional -> ":optional"
                Required -> ":required"
                Valid -> ":valid"
                Invalid -> ":invalid"
                UserValid -> ":user-valid"
                UserInvalid -> ":user-invalid"
                InRange -> ":in-range"
                OutOfRange -> ":out-of-range"
                ReadWrite -> ":read-write"
                ReadOnly -> ":read-only"
                Default -> ":default"
                PlaceholderShown -> ":placeholder-shown"
                Link -> ":link"
                AnyLink -> ":any-link"
                Visited -> ":visited"
            }
        )
    }
}

// NonTreeStructural-Functional-PseudoClass
sealed class NonTSFPseudoClass : ToCss {

    data class Lang(val language: String) : NonTSFPseudoClass()

    override fun toCss(writer: Writer) {
        writer.append(
            when (this) {
                is Lang -> ":lang($language)"
            }
        )
    }
}

sealed class NamespaceConstraint {

    data object Any : NamespaceConstraint()

    data class Specific(val prefix: NamespacePrefix, val url: NamespaceUrl) : NamespaceConstraint()
}

sealed class AttributeSelectorOperation {

    data object Exists : AttributeSelectorOperation()

    data class WithValue(
        val operator: AttributeSelectorOperator,
        val caseSensitive: Boolean,
        val expectedValue: String,
    ) : AttributeSelectorOperation()
}

sealed class AttributeSelectorOperator : ToCss {

    data object Equal : AttributeSelectorOperator()
    data object Includes : AttributeSelectorOperator()
    data object DashMatch : AttributeSelectorOperator()
    data object Prefix : AttributeSelectorOperator()
    data object Substring : AttributeSelectorOperator()
    data object Suffix : AttributeSelectorOperator()

    override fun toCss(writer: Writer) {
        writer.append(
            when (this) {
                is Equal -> "="
                is Includes -> "~="
                is DashMatch -> "|="
                is Prefix -> "^="
                is Substring -> "*="
                is Suffix -> "$="
            }
        )
    }
}

/**
 * A Selector represents a sequence of compound selectors where each simple selector is separated by a [Combinator].
 * A compound selector consists out of a sequence of simple selectors, represented by [Component]. The Selector is
 * stored in matching order (right-to-left) for the combinators whereas for the compound selectors in parse order
 * (left-to-right).
 */
class Selector(private val header: SpecificityAndFlags, private val components: List) : ToCss {

    /**
     * Returns the specificity of this selector in a 4 Byte compressed format. For further information of the format
     * see [Specificity].
     */
    val specificity: Int
        get() = header.specificity

    val hasParent: Boolean
        get() = header.hasParent

    val pseudoElement: PseudoElement?
        get() {
            if (!header.hasPseudoElement) {
                return null
            }

            for (component in components) {
                if (component is Component.PseudoElement) {
                    return component.pseudoElement
                }
            }

            panic("header.hasPseudoElement() resulted in true, but pseudoElement was not found")
        }

    /**
     * Returns a high-level [SelectorIterator]. Iterates over a single compound selector until it returns [None]. After that
     * [SelectorIter.nextSequence] might return [Some] if the is another compound selector.
     *
     * Selector:
     * ```css
     * div.class > #id:visited > *.class3
     * ```
     * Match Order:
     * ```css
     * *.class3 > #id:visited > div.class
     * ```
     */
    fun iterator(): SelectorIterator {
        return SelectorIterator(components)
    }

    fun rawIteratorMatchOrder(): Iterator {
        return components.iterator()
    }

    /**
     * Returns a raw [Iterator] in parse order. The Iter is not in true parse order meaning the compound selectors are reversed.
     * The sequence of the compound selectors in relation to the combinators are in parse order.
     *
     * Selector:
     * ```
     * div.class > #id:visited > *.class3
     * ```
     * Semi Parse Order:
     * ```
     * [.class] [div] > [:visited] [#id] > [.class3] [*]
     * ```
     */
    fun rawIteratorParseOrder(): Iterator {
        return components.reversed().iterator()
    }

    /**
     * Constructs a raw [Iterator] that represents true parse order. Due to the nature of how the selector is stored internally,
     * this is a very expensive operation compared to the iters.
     *
     * Selector:
     * ```
     * div.class > #id:visited > *.class3
     * ```
     * Parse Order:
     * ```
     * [div] [.class] > [#id] [:visited] > [*] [.class3]
     * ```
     *
     * @see rawIteratorParseOrder for semi parse order
     * @see iterator for high-level iter
     */
    fun rawIteratorTrueParseOrder(): Iterator {
        val iterator = SelectorIterator(components.reversed())

        val selector = mutableListOf()
        val compoundSelector = mutableListOf()

        while (true) {
            while (iterator.hasNext()) {
                val next = iterator.next()
                compoundSelector.add(next)
            }

            selector.addAll(compoundSelector.drain().reversed())

            if (!iterator.hasNextSequence()) break

            selector.add(Component.Combinator(iterator.nextSequence()))
        }

        return selector.iterator()
    }

    fun iteratorSkipRelativeSelectorAnchor(): SelectorIterator {
        debug {
            val iterator = rawIteratorParseOrder()
            assert(iterator.next() is Component.RelativeSelectorAnchor)
            assert(iterator.next() is Component.Combinator)
        }
        return SelectorIterator(
            components.dropLast(2),
        )
    }

    fun combinatorOfRelativeSelectorAnchor(): Combinator {
        debug {
            val iterator = rawIteratorParseOrder()
            assert(iterator.next() is Component.RelativeSelectorAnchor)
            assert(iterator.next() is Component.Combinator)
        }
        return when (val component = components[components.lastIndex - 1]) {
            is Component.Combinator -> component.combinator
            else -> error("not a relative selector")
        }
    }

    fun replaceParent(parent: List): Selector {
        val flags = SelectorFlags.of(header.flags) - SelectorFlags.HAS_PARENT
        val specificity = Specificity.fromInt(header.specificity)

        val parentSpecificity = Specificity.fromInt(selectorListSpecificityAndFlags(parent).specificity)

        fun replaceParentOnSelectorList(
            selectors: List,
            parent: List,
            specificity: Specificity,
            withSpecificity: Boolean,
        ): List {
            var any = false

            val result = selectors.map { selector ->
                if (!selector.hasParent) return@map selector

                any = true
                selector.replaceParent(parent)
            }

            if (any && withSpecificity) {
                specificity += Specificity.fromInt(
                    selectorListSpecificityAndFlags(result).specificity -
                            selectorListSpecificityAndFlags(selectors).specificity
                )
            }

            return result
        }

        fun replaceParentOnRelativeSelectorList(
            relativeSelectors: List,
            parent: List,
            specificity: Specificity,
        ): List {
            var any = false

            val result = relativeSelectors.map { relativeSelector ->
                if (!relativeSelector.selector.hasParent) return@map relativeSelector

                any = true
                RelativeSelector(
                    relativeSelector.selector.replaceParent(parent),
                    relativeSelector.matchHint,
                )
            }

            if (any) {
                specificity += Specificity.fromInt(
                    relativeSelectorListSpecificityAndFlags(result).specificity -
                            relativeSelectorListSpecificityAndFlags(relativeSelectors).specificity
                )
            }

            return result
        }

        fun replaceParentOnSelector(
            selector: Selector,
            parent: List,
            specificity: Specificity,
        ): Selector {
            if (!selector.hasParent) return selector

            val result = selector.replaceParent(parent)
            specificity += Specificity.fromInt(result.specificity - selector.specificity)
            return result
        }

        val components = if (!hasParent) {
            specificity += parentSpecificity

            components + listOf(Component.Combinator(Combinator.Descendant), Component.Is(parent))
        } else {
            components.map { component ->
                when (component) {
                    is Component.LocalName,
                    is Component.ID,
                    is Component.Class,
                    is Component.AttributeInNoNamespace,
                    is Component.AttributeInNoNamespaceExists,
                    is Component.AttributeOther,
                    is Component.ExplicitAnyNamespace,
                    is Component.ExplicitNoNamespace,
                    is Component.ExplicitUniversalType,
                    is Component.DefaultNamespace,
                    is Component.Namespace,
                    is Component.Root,
                    is Component.Empty,
                    is Component.Scope,
                    is Component.NonTSFPseudoClass,
                    is Component.NonTSPseudoClass,
                    is Component.PseudoElement,
                    is Component.Combinator,
                    is Component.Part,
                    is Component.RelativeSelectorAnchor,
                    -> component

                    Component.ParentSelector -> {
                        specificity += parentSpecificity
                        Component.Is(parent)
                    }

                    is Component.Negation -> Component.Negation(
                        replaceParentOnSelectorList(component.selectors, parent, specificity, withSpecificity = true)
                    )

                    is Component.Is -> Component.Is(
                        replaceParentOnSelectorList(component.selectors, parent, specificity, withSpecificity = true)
                    )

                    is Component.Where -> Component.Is(
                        replaceParentOnSelectorList(component.selectors, parent, specificity, withSpecificity = false)
                    )

                    is Component.Has -> Component.Has(
                        replaceParentOnRelativeSelectorList(component.selectors, parent, specificity)
                    )

                    is Component.Host -> when {
                        component.selector != null -> Component.Host(
                            replaceParentOnSelector(component.selector, parent, specificity)
                        )

                        else -> component
                    }

                    is Component.Nth -> when {
                        component.selectors.isNotEmpty() -> Component.Nth(
                            component.data,
                            replaceParentOnSelectorList(component.selectors, parent, specificity, withSpecificity = true),
                        )

                        else -> component
                    }

                    is Component.Slotted -> Component.Slotted(
                        replaceParentOnSelector(component.selector, parent, specificity)
                    )
                }
            }
        }

        return Selector(SpecificityAndFlags(specificity.toInt(), flags.bits), components)
    }

    override fun toCss(writer: Writer) {
        rawIteratorTrueParseOrder().toCssJoining(writer)
    }

    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (other !is Selector) return false

        if (components != other.components) return false

        return true
    }

    override fun hashCode(): Int {
        return components.hashCode()
    }

    override fun toString(): String {
        return "Selector['${toCssString()}' specificity: ${specificity}]"
    }
}

class SelectorIterator(
    private val components: List,
    private var index: Int = 0,
    private var combinator: Combinator? = null,
) : Iterator {

    override fun hasNext(): Boolean {
        if (index < components.size) {
            val component = components[index]
            if (component is Component.Combinator) {
                combinator = component.combinator
            }
        }
        return combinator == null && index < components.size
    }

    override fun next(): Component {
        if (!hasNext()) throw NoSuchElementException("end of sequence")
        return components[index++]
    }

    fun hasNextSequence(): Boolean = combinator != null || hasNext()

    fun nextSequence(): Combinator {
        val next = combinator ?: error("not at end of sequence")
        combinator = null
        index++
        return next
    }

    fun clone(): SelectorIterator {
        return SelectorIterator(
            components,
            index,
            combinator
        )
    }
}

class CombinatorIterator private constructor(
    private val iterator: SelectorIterator,
) : Iterator {

    override fun hasNext(): Boolean = iterator.hasNextSequence()
    override fun next(): Combinator {
        val combinator = iterator.nextSequence()
        drainNonCombinators()
        return combinator
    }

    private fun drainNonCombinators() {
        while (iterator.hasNext()) {
            iterator.next()
        }
    }

    companion object {
        fun from(iterator: SelectorIterator): CombinatorIterator {
            val combinatorIterator = CombinatorIterator(iterator)
            combinatorIterator.drainNonCombinators()
            return combinatorIterator
        }
    }
}

enum class RelativeSelectorMatchHint {
    InChild,
    InSubtree,
    InSibling,
    InSiblingSubtree,
    InNextSibling,
    InNextSiblingSubtree,
}

private class CombinatorComposition(value: UByte) : U8Bitflags(value) {

    override val all: UByte get() = ALL

    companion object {
        const val DESCENDANTS: UByte = 0b0000_0001u
        const val SIBLINGS: UByte = 0b0000_0010u

        private val ALL = DESCENDANTS and SIBLINGS

        fun empty(): CombinatorComposition = CombinatorComposition(0u)
        fun all(): CombinatorComposition = CombinatorComposition(ALL)
        fun of(value: UByte): CombinatorComposition = CombinatorComposition(value and ALL)
    }
}

private fun CombinatorComposition.Companion.forRelativeSelector(selector: Selector): CombinatorComposition {
    val result = empty()
    for (combinator in CombinatorIterator.from(selector.iteratorSkipRelativeSelectorAnchor())) {
        when (combinator) {
            Combinator.Child, Combinator.Descendant -> result.add(DESCENDANTS)
            Combinator.NextSibling, Combinator.LaterSibling -> result.add(SIBLINGS)
            Combinator.Part, Combinator.SlotAssignment, Combinator.PseudoElement -> continue
        }
        if (result.isAll()) break
    }
    return result
}

data class RelativeSelector(
    val selector: Selector,
    val matchHint: RelativeSelectorMatchHint,
) {


    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (other !is RelativeSelector) return false

        if (selector != other.selector) return false

        return true
    }

    override fun hashCode(): Int {
        return selector.hashCode()
    }

    override fun toString(): String {
        return "RelativeSelector['${selector.toCssString()}' specificity: ${selector.specificity}]"
    }

    companion object {
        fun fromSelector(selector: Selector): RelativeSelector {
            val matchHint = when (selector.combinatorOfRelativeSelectorAnchor()) {
                Combinator.Descendant -> RelativeSelectorMatchHint.InSubtree
                Combinator.Child -> {
                    val composition = CombinatorComposition.forRelativeSelector(selector)
                    if (composition.isEmpty() || composition.bits == CombinatorComposition.SIBLINGS) {
                        RelativeSelectorMatchHint.InChild
                    } else {
                        RelativeSelectorMatchHint.InSubtree
                    }
                }

                Combinator.NextSibling -> {
                    val composition = CombinatorComposition.forRelativeSelector(selector)
                    if (composition.isEmpty()) {
                        RelativeSelectorMatchHint.InNextSibling
                    } else if (composition.bits == CombinatorComposition.SIBLINGS) {
                        RelativeSelectorMatchHint.InSibling
                    } else if (composition.bits == CombinatorComposition.DESCENDANTS) {
                        RelativeSelectorMatchHint.InNextSiblingSubtree
                    } else {
                        RelativeSelectorMatchHint.InSiblingSubtree
                    }
                }

                Combinator.LaterSibling -> {
                    val composition = CombinatorComposition.forRelativeSelector(selector)
                    if (composition.isEmpty() || composition.bits == CombinatorComposition.SIBLINGS) {
                        RelativeSelectorMatchHint.InSibling
                    } else {
                        RelativeSelectorMatchHint.InSiblingSubtree
                    }
                }

                Combinator.Part, Combinator.SlotAssignment, Combinator.PseudoElement -> {
                    assert(false) { "unexpected combinator in relative selector" }
                    RelativeSelectorMatchHint.InSubtree
                }
            }

            return RelativeSelector(selector, matchHint)
        }

        fun fromSelectorList(selectorList: SelectorList): List {
            return selectorList.selectors.map { selector -> fromSelector(selector) }
        }
    }
}

class SelectorList(val selectors: List) : Iterable, ToCss {

    override fun iterator(): Iterator = selectors.iterator()

    override fun toCss(writer: Writer) {
        selectors.toCssJoining(writer, separator = ", ")
    }

    override fun toString(): String {
        return "SelectorList[${toCssString()}]"
    }

    companion object {

        fun parse(
            context: SelectorParserContext,
            input: Parser,
            parseRelative: ParseRelative,
        ): Result {
            return parseWithState(
                context,
                input,
                SelectorParsingState.empty(),
                ParseForgiving.No,
                parseRelative,
            )
        }

        fun parseWithState(
            context: SelectorParserContext,
            input: Parser,
            state: SelectorParsingState,
            recovery: ParseForgiving,
            parseRelative: ParseRelative,
        ): Result {
            val selectors = mutableListOf()

            val forgiving = recovery == ParseForgiving.Yes
            while (true) {
                when (val selector = input.parseUntilBefore(Delimiters.Comma) { i -> parseSelector(context, i, state, parseRelative) }) {
                    is Ok -> selectors.add(selector.value)
                    is Err -> if (!forgiving) return selector
                }

                val token = when (val token = input.next()) {
                    is Ok -> token.value
                    is Err -> break
                }

                when (token) {
                    is Token.Comma -> continue
                    else -> error("unreachable")
                }
            }
            return Ok(SelectorList(selectors.resized()))
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy