com.gitlab.mvysny.konsumexml.StaxReader.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.StaxEventType
import com.gitlab.mvysny.konsumexml.stax.StaxParser
import java.io.Closeable
import java.io.InputStream
import javax.xml.stream.XMLStreamException
import javax.xml.stream.XMLStreamReader
import javax.xml.stream.events.XMLEvent
/**
* Wraps [stax] and provides a way of iterating of the XML events, with the following properties:
*
* These events are always ignored and are skipped over silently:
* * [XMLEvent.ENTITY_DECLARATION]
* * [XMLEvent.SPACE]
* * [XMLEvent.COMMENT]
* * [XMLEvent.DTD]
* * [XMLEvent.START_DOCUMENT]
* * [XMLEvent.PROCESSING_INSTRUCTION]
*
* These events cause konsumer to fail and throw [KonsumerException]:
* * [XMLEvent.ENTITY_REFERENCE] since we expect all entities to be expanded.
* * [XMLEvent.NAMESPACE] can't really happen in well-formed XML
* * [XMLEvent.ATTRIBUTE] can't really happen in well-formed XML; attributes are reported as part of [XMLEvent.START_ELEMENT]
* * [XMLEvent.NOTATION_DECLARATION]
*
* This reader will therefore only return events of type:
* * [XMLEvent.CDATA], [XMLEvent.CHARACTERS]
* * [XMLEvent.END_DOCUMENT]
* * [XMLEvent.START_ELEMENT]
* * [XMLEvent.END_ELEMENT]
*
* It is also possible to "push back" current event, so that [hasNext] returns true and [next] returns the current event, unless
* the current event is [XMLEvent.END_DOCUMENT], in that case [hasNext] will return false.
*
* All methods throw [KonsumerException] on any unexpected contents (such as unexpected attribute nodes),
* [javax.xml.stream.XMLStreamException] on any I/O errors and XML parsing errors.
*
* Not thread-safe.
*
* Closing this reader will close both [stax] parser and [inputStreamToClose].
*
* The client is expected to read additional information about the event from [stax], e.g. [StaxParser.text].
*/
class StaxReader(val stax: StaxParser, private val inputStreamToClose: InputStream? = null) : Closeable, Iterator {
private var pushBack = false
private fun skipIgnoredEvents() {
while(true) {
when (stax.eventType) {
StaxEventType.EntityDeclaration, StaxEventType.Space, StaxEventType.Comment, StaxEventType.DTD,
StaxEventType.StartDocument, StaxEventType.ProcessingInstruction -> {
// ignore
stax.next()
}
StaxEventType.Attribute -> throw KonsumerException(stax.location, null, "unexpected ATTRIBUTE")
StaxEventType.EntityReference -> throw KonsumerException(stax.location, null, "Expected entities to be expanded")
StaxEventType.Namespace -> throw KonsumerException(stax.location, null, "unexpected NAMESPACE")
StaxEventType.NotationDeclaration -> throw KonsumerException(stax.location, null, "unexpected NOTATION_DECLARATION")
else -> return
}
}
}
/**
* Pushes back current event, so that [hasNext] returns true and [next] returns the current event, unless
* the current event is [XMLEvent.END_DOCUMENT], in that case this function does nothing.
*
* It's not possible to push back multiple events since it's not possible to push back [stax] itself and the client
* reads current stuff from [stax].
*/
fun pushBack() {
check(!pushBack) { "cannot push back more than 1 event" }
if (stax.hasNext()) {
pushBack = true
}
}
/**
* Returns true if there are more parsing events and false
* if there are no more events. This method will return
* false if the current state of the XMLStreamReader is
* END_DOCUMENT
* @return true if there are more events, false otherwise
* @throws XMLStreamException if there is a fatal error detecting the next state
*/
override fun hasNext(): Boolean = pushBack || stax.hasNext()
/**
* See [XMLStreamReader.next] for details. Only returns the following events: [XMLEvent.CDATA], [XMLEvent.CHARACTERS],
* [XMLEvent.END_DOCUMENT], [XMLEvent.START_ELEMENT], [XMLEvent.END_ELEMENT]
*/
override fun next(): StaxEventType {
if (!pushBack) {
stax.next()
} else {
pushBack = false
}
skipIgnoredEvents()
return stax.eventType
}
override fun close() {
stax.close()
inputStreamToClose?.close()
}
}