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

com.gitlab.mvysny.konsumexml.AttributeKonsumer.kt Maven / Gradle / Ivy

The newest version!
package com.gitlab.mvysny.konsumexml

import com.gitlab.mvysny.konsumexml.stax.Location
import com.gitlab.mvysny.konsumexml.stax.StaxParser
import javax.xml.XMLConstants
import javax.xml.namespace.QName

/**
 * Accesses attributes of current element.
 */
@KonsumerDsl
public interface AttributeKonsumer {
    /**
     * Returns the current location.
     */
    public val location: Location
    /**
     * Returns the current element name.
     */
    public val elementName: QName

    /**
     * Returns the [QName]s of all available attributes.
     */
    public val names: List

    /**
     * Returns the [QName.getFullName] of all available attributes.
     *
     * Mostly used for debugging purposes, or producing an error message in case of parsing error.
     */
    public val fullNames: List get() = names.map { it.getFullName() }

    /**
     * Returns the normalized attribute value of the attribute with given [name].
     * If the attribute is missing, throws [KonsumerException].
     * @return returns the value of the attribute.
     * @throws IllegalStateException if the current stax event is not a START_ELEMENT or ATTRIBUTE,
     * or if this konsumer has been [finish]d.
     * @throws KonsumerException if the attribute is missing.
     */
    public fun getValue(name: QName): String = getValue(name.localPart, name.namespaceURI)

    /**
     * Returns the normalized attribute value of the attribute with given [name].
     * If the attribute is missing, returns `null`.
     * @return returns the value of the attribute, returns `null` if not found
     * @throws IllegalStateException if the current stax event is not a START_ELEMENT or ATTRIBUTE,
     * or if this konsumer has been [finish]d.
     */
    public fun getValueOrNull(name: QName): String? = getValueOrNull(name.localPart, name.namespaceURI)

    @Deprecated("Use getValueOrNull", replaceWith = ReplaceWith("getValueOrNull"))
    public fun getValueOpt(name: QName): String? = getValueOrNull(name)

    /**
     * Returns the normalized attribute value of the attribute with the [localName] and [namespace].
     * If the attribute is missing, throws [KonsumerException].
     * @return returns the value of the attribute.
     * @throws IllegalStateException if the current stax event is not a START_ELEMENT or ATTRIBUTE,
     * or if this konsumer has been [finish]d.
     * @throws KonsumerException if the attribute is missing.
     */
    public fun getValue(localName: String, namespace: String = XMLConstants.NULL_NS_URI): String {
        val value: String = getValueOrNull(localName, namespace)
                ?: throw KonsumerException(location, elementName, "Required attribute '${QName(namespace, localName)}' is missing. Available attributes: $fullNames")
        return value
    }

    /**
     * Returns the normalized attribute value of the attribute with the [localName] and [namespace].
     * If the attribute is missing, returns `null`.
     * @return returns the value of the attribute, returns `null` if not found
     * @throws IllegalStateException if the current stax event is not a START_ELEMENT or ATTRIBUTE,
     * or if this konsumer has been [finish]ed.
     */
    public fun getValueOrNull(localName: String, namespace: String = XMLConstants.NULL_NS_URI): String?

    @Deprecated("Use getValueOrNull", replaceWith = ReplaceWith("getValueOrNull"))
    public fun getValueOpt(localName: String, namespace: String = XMLConstants.NULL_NS_URI): String? =
            getValueOrNull(localName, namespace)

    /**
     * Finalizes this attribute konsumer and cleans up its memory. Checks for any unconsumed attributes
     * (if so configured).
     *
     * This konsumer should not be used anymore - any attempt to use this konsumer will fail with
     * [IllegalStateException].
     */
    public fun finish()

    /**
     * The same as [getValue].
     */
    public operator fun get(name: QName): String = getValue(name)

    /**
     * The same as [getValue].
     */
    public operator fun get(localName: String): String = getValue(localName)
}

/**
 * Accesses attributes of the current element from given [stax] parser.
 */
public class StaxAttributeKonsumer(public val stax: StaxParser) : AttributeKonsumer {
    override val location: Location
        get() = stax.location
    override val elementName: QName
        get() = stax.elementName
    private var isFinished = false

    override val names: List
        get() {
            checkNotFinalized()
            return stax.attributeNames
        }

    override fun getValueOrNull(localName: String, namespace: String): String? {
        checkNotFinalized()
        return stax.getAttributeValue(namespace, localName)
    }

    private fun checkNotFinalized() {
        check(!isFinished) { "You can only read element attributes when the element has been freshly entered" }
    }

    override fun finish() {
        isFinished = true
    }
}

/**
 * Returns the QNames of all attributes currently available.
 *
 * This method is only valid on a START_ELEMENT or ATTRIBUTE.
 */
public val StaxParser.attributeNames: List get() =
        (0 until attributeCount).map { getAttributeName(it) }

/**
 * Makes sure that all attributes are properly consumed. If not, fails in
 * [finish] with a proper error message.
 */
public class AttributeKonsumerWatchdog(public val delegate: AttributeKonsumer, public val stax: StaxParser) : AttributeKonsumer {
    /**
     * Remembers all names of all attributes that were requested via one of the [getValue] methods. In [finish] we'll
     * verify that all attributes has been requested and there are no unrequested (=not present in schema) attributes.
     *
     * Implementation detail: most elements do not have any attributes. Use lazy to defer construction of mutable set unless
     * absolutely necessary.
     */
    private val consumedAttributes: MutableSet by lazy(LazyThreadSafetyMode.NONE) { mutableSetOf() }
    private var isFinished = false

    override val location: Location
        get() = stax.location
    override val elementName: QName
        get() = stax.elementName

    override val names: List
        get() = delegate.names

    override fun getValue(name: QName): String {
        onRequested(name)
        return delegate.getValue(name)
    }

    override fun getValue(localName: String, namespace: String): String {
        val qname = QName(namespace, localName)
        onRequested(qname)
        return delegate.getValue(qname)
    }

    private val QName.isIgnored: Boolean get() = namespaceURI == XMLConstants.XML_NS_URI || namespaceURI == XMLConstants.XMLNS_ATTRIBUTE_NS_URI

    private fun onRequested(name: QName) {
        if (!name.isIgnored) {
            consumedAttributes.add(name)
        }
    }

    override fun getValueOrNull(name: QName): String? {
        onRequested(name)
        return delegate.getValueOrNull(name)
    }

    override fun getValueOrNull(localName: String, namespace: String): String? {
        val qname = QName(namespace, localName)
        onRequested(qname)
        return delegate.getValueOrNull(qname)
    }

    override fun finish() {
        if (isFinished) return
        (0 until stax.attributeCount).forEach { attributeIndex ->
            val qname: QName = stax.getAttributeName(attributeIndex)
            if (!qname.isIgnored && !consumedAttributes.contains(qname)) {
                throw KonsumerException(location, elementName, "The attribute $qname has not been consumed")
            }
        }
        delegate.finish()
        isFinished = true
    }
}

/**
 * Used for root [Konsumer] which is supposed to consume the root element.
 */
public class NullElementKonsumer(public val stax: StaxParser) : AttributeKonsumer {
    override val location: Location
        get() = stax.location
    override val elementName: QName
        get() = QName("", "")
    override val names: List
        get() = listOf()

    override fun getValueOrNull(localName: String, namespace: String): String? = throw IllegalStateException("Not in element")
    override fun finish() {}
}

/**
 * Retrieves a value of attribute with given [localName], passing it through [converter].
 * If the conversion throws, the exception is wrapped in [KonsumerException]
 * with exact location and rethrown.
 */
public fun  AttributeKonsumer.getValue(
        localName: String,
        namespace: String = XMLConstants.NULL_NS_URI,
        converter: (String) -> T
): T {
    val value: String = getValue(localName, namespace)
    return try {
        converter(value)
    } catch (e: Exception) {
        throw KonsumerException(location, elementName, "Failed to convert the value '$value' of attribute '$localName': '${e.message}'", e)
    }
}

/**
 * Retrieves a value of attribute with given [localName], passing it through [converter]. If the conversion throws, the exception is wrapped in [KonsumerException]
 * with exact location and rethrown.
 * @throws KonsumerException if the conversion fails.
 */
public fun  AttributeKonsumer.getValueOrNull(localName: String, namespace: String = XMLConstants.NULL_NS_URI, converter: (String) -> T): T? {
    val value: String = getValueOrNull(localName, namespace) ?: return null
    return try {
        converter(value)
    } catch (e: Exception) {
        throw KonsumerException(location, elementName, "Failed to convert the value '$value' of attribute '$localName': '${e.message}'", e)
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy