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

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

There is a newer version: 1.2
Show 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

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

    fun getValue(name: QName): String = getValue(name.localPart, name.namespaceURI)
    fun getValueOpt(name: QName): String? = getValueOpt(name.localPart, name.namespaceURI)
    /**
     * 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, returns `null` if not found
     * @throws IllegalStateException if this is not a START_ELEMENT or ATTRIBUTE
     */
    fun getValue(localName: String, namespace: String = XMLConstants.NULL_NS_URI): String =
            getValueOpt(localName, namespace)
                    ?: throw KonsumerException(location, elementName, "Required attribute '${QName(namespace, localName)}' is missing")

    /**
     * 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 this is not a START_ELEMENT or ATTRIBUTE
     */
    fun getValueOpt(localName: String, namespace: String = XMLConstants.NULL_NS_URI): String?

    /**
     * Finalizes this attribute konsumer, cleans up its memory and checks for any unconsumed elements.
     * This konsumer should not be used anymore - any attempt to use this konsumer will fail with
     * [IllegalStateException].
     */
    fun finalize()

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

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

class StaxAttributeKonsumer(val stax: StaxParser) : AttributeKonsumer {
    override val location: Location
        get() = stax.location
    override val elementName: QName
        get() = stax.elementName
    private var isFinalized = false
    override fun getValueOpt(localName: String, namespace: String): String? {
        check(!isFinalized) { "You can only read element attributes when the element has been freshly entered" }
        return stax.getAttributeValue(namespace, localName)
    }

    override fun finalize() {
        isFinalized = true
    }
}

class AttributeKonsumerWatchdog(val delegate: AttributeKonsumer, val stax: StaxParser) : AttributeKonsumer {
    /**
     * Remembers all names of all attributes that were requested via one of the [getValue] methods. In [finalize] 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 requestedAttributes by lazy(LazyThreadSafetyMode.NONE) { mutableSetOf() }
    private var hasRequestedAttributes = false
    private var isFinalized = false

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

    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 get() = namespaceURI == XMLConstants.XML_NS_URI || namespaceURI == XMLConstants.XMLNS_ATTRIBUTE_NS_URI

    private fun onRequested(name: QName) {
        if (!name.isIgnored) {
            requestedAttributes.add(name)
            hasRequestedAttributes = true
        }
    }

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

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

    override fun finalize() {
        if (isFinalized) return
        (0 until stax.attributeCount).forEach { attributeIndex ->
            val qname = stax.getAttributeName(attributeIndex)
            if (!qname.isIgnored) {
                if (!hasRequestedAttributes || !requestedAttributes.contains(qname)) {
                    throw KonsumerException(location, elementName, "The attribute $qname has not been consumed")
                }
            }
        }
        delegate.finalize()
        isFinalized = true
    }
}

class NullElementKonsumer(val stax: StaxParser) : AttributeKonsumer {
    override val location: Location
        get() = stax.location
    override val elementName: QName
        get() = QName("", "")

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

/**
 * 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.
 */
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.
 */
fun  AttributeKonsumer.getValueOpt(localName: String, namespace: String = XMLConstants.NULL_NS_URI, converter: (String) -> T): T? {
    val value: String = getValueOpt(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