com.gitlab.mvysny.konsumexml.AttributeKonsumer.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of konsume-xml Show documentation
Show all versions of konsume-xml Show documentation
Konsume-XML: A simple functional XML parser with no annotations
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)
}
}