com.gitlab.mvysny.konsumexml.stax.StaxParser.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
The newest version!
package com.gitlab.mvysny.konsumexml.stax
import com.gitlab.mvysny.konsumexml.existsClass
import java.io.Closeable
import java.io.InputStream
import java.lang.RuntimeException
import javax.xml.namespace.QName
import javax.xml.stream.XMLStreamException
public enum class StaxEventType {
StartElement,
EndElement,
ProcessingInstruction,
Characters,
Comment,
Space,
StartDocument,
EndDocument,
EntityReference,
Attribute,
DTD,
CData,
Namespace,
NotationDeclaration,
EntityDeclaration
}
/**
* A low-level STAX Parser API, wrapping the actual STAX XML parser. Currently there are two implementors:
*
* * [JavaxXmlStreamStaxParser] using javax.xml.stream API which is present on
* regular Java but not on Android
* * [OrgXmlpullStaxParser] using the org.xmlpull API which is present on Android.
*
* [close] closes the underlying parser and releases its resources, but does not
* need to close the underlying input stream.
*/
public interface StaxParser : Closeable {
/**
* Returns the current event type. Only valid after [next] has been
* called.
*/
public val eventType: StaxEventType
/**
* Return the current location of the processor.
* If the Location is unknown the processor should return
* an implementation of Location that returns -1 for the
* location and null for the publicId and systemId.
* The location information is only valid after [next] has been
* called.
*/
public val location: Location
/**
* Returns a QName for the current START_ELEMENT or END_ELEMENT event
* @return the QName for the current START_ELEMENT or END_ELEMENT event
* @throws IllegalStateException if this is not a START_ELEMENT or
* END_ELEMENT
*/
public val elementName: QName
/**
* Returns the qname of the attribute at the provided index
* @param index the position of the attribute, zero-based, must be 0..[attributeCount]-1
* @return the QName of the attribute
* @throws IllegalStateException if this is not a START_ELEMENT or ATTRIBUTE
* @throws IllegalArgumentException if index is out-of-bounds.
*/
public fun getAttributeName(index: Int): QName
/**
* Returns the count of attributes on this START_ELEMENT,
* this method is only valid on a START_ELEMENT or ATTRIBUTE. This
* count excludes namespace definitions. Attribute indices are
* zero-based.
* @return returns the number of attributes
* @throws IllegalStateException if this is not a START_ELEMENT or ATTRIBUTE
*/
public val attributeCount: Int
/**
* Returns the normalized attribute value of the
* attribute with the namespace and localName
* @param namespaceURI the namespace of the attribute
* @param localName the local name of the attribute, cannot be null
* @return returns the value of the attribute , returns null if not found
* @throws IllegalStateException if this is not a START_ELEMENT or ATTRIBUTE
*/
public fun getAttributeValue(namespaceURI: String, localName: String): String?
/**
* Returns the current value of the parse event as a string,
* this returns the string value of a CHARACTERS event,
* returns the value of a COMMENT, the replacement value
* for an ENTITY_REFERENCE, the string value of a CDATA section,
* the string value for a SPACE event,
* or the String value of the internal subset of the DTD.
* If an ENTITY_REFERENCE has been resolved, any character data
* will be reported as CHARACTERS events.
* @return the current text or null
* @throws java.lang.IllegalStateException if this state is not
* a valid text state.
*/
public val text: String?
/**
* Get next parsing event - a processor may return all contiguous
* character data in a single chunk, or it may split it into several chunks.
* If the property javax.xml.stream.isCoalescing is set to true
* element content must be coalesced and only one CHARACTERS event
* must be returned for contiguous element content or
* CDATA Sections.
*
* By default entity references must be
* expanded and reported transparently to the application.
* An exception will be thrown if an entity reference cannot be expanded.
* If element content is empty (i.e. content is "") then no CHARACTERS event will be reported.
*
* Given the following XML:
* ```xml
* content textHello]]>other content
* ```
* The behavior of calling next() when being on foo will be:
* 1. the comment (COMMENT)
* 2. then the characters section (CHARACTERS)
* 3. then the CDATA section (another CHARACTERS)
* 4. then the next characters section (another CHARACTERS)
* 5. then the END_ELEMENT
*
*
* **NOTE:** empty element (such as ` `) will be reported
* with two separate events: START_ELEMENT, END_ELEMENT - This preserves
* parsing equivalency of empty element to ` `.
*
* This method will throw an IllegalStateException if it is called after [hasNext] returns false.
*
* @throws NoSuchElementException if this is called when hasNext() returns false
* @throws XMLStreamException if there is an error processing the underlying XML source
*/
public fun next()
/**
* 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
*/
public fun hasNext(): Boolean
}
/**
* A location in the XML file being parsed.
* @property lineNumber Return the line number where the current event ends,
* returns -1 if none is available. 1-based (first row has the number of 1).
* @property columnNumber Return the column number where the current event ends,
* returns -1 if none is available. 1-based (first column has the number of 1).
* @property characterOffset Return the byte or character offset into the input source this location
* is pointing to. If the input source is a file or a byte stream then
* this is the byte offset into that stream, but if the input source is
* a character media then the offset is the character offset.
* Returns -1 if there is no offset available.
* @property publicId Returns the public ID of the XML or null if not available.
* @property systemId the system ID, or null if not available
*/
public data class Location(val lineNumber: Int, val columnNumber: Int, val characterOffset: Int, val publicId: String?, val systemId: String?) {
override fun toString(): String = "line $lineNumber column $columnNumber at $publicId $systemId"
}
/**
* Produces XML parser for given XML `stream`. The `systemId` optionally refers to the XML -
* it's either a URL or an absolute path in a local filesystem.
*/
public typealias XmlParserFactory = (stream: InputStream, systemId: String?) -> StaxParser
/**
* Produces [StaxParser]s. Automatically chooses the proper implementation depending on the platform we're running on.
* See [isJavaxXmlStreamAvailable] and [isOrgXmlpullAvailable] for more details.
*/
public object StaxParserFactory {
/**
* Checks whether the `javax.xml.stream` API is available. It is not available on Androids.
*/
public val isJavaxXmlStreamAvailable: Boolean =
existsClass("javax.xml.stream.XMLStreamReader") &&
// also test for javax.xml.stream.FactoryFinder; should fix https://gitlab.com/mvysny/konsume-xml/-/issues/17
existsClass("javax.xml.stream.FactoryFinder")
/**
* Checks whether the `org.xmlpull` API is available. It is typically available on Androids 8+ and also on JDK
* when you include either `org.ogce:xpp3:1.1.6`, `kxml2` or similar libraries.
*/
public val isOrgXmlpullAvailable: Boolean = existsClass("org.xmlpull.v1.XmlPullParser")
/**
* The default [StaxParser] factory. It tries to use `javax.xml.stream` since that one is also able to expand
* entity references; if it isn't available then `org.xmlpull` is used.
*/
public val defaultFactory: XmlParserFactory = { stream, systemId ->
when {
isJavaxXmlStreamAvailable -> JavaxXmlStreamStaxParser.create(stream, systemId)
isOrgXmlpullAvailable -> OrgXmlpullStaxParser.create(stream, systemId)
else -> throw RuntimeException("No StaX library is available")
}
}
/**
* The factory producing [StaxParser]. By default set to [defaultFactory].
*
* Change this to use any [StaxParser] implementation you need, or to provide different parser configuration etc.
*/
public var factory: XmlParserFactory = defaultFactory
/**
* Calls [factory] to create new [StaxParser]. The parser will read given [stream]; if you know the origin (absolute
* name of the file, http source url), pass it as [systemId].
*
* Closing the parser will not close the stream, you need to close the stream separately.
*/
public fun create(stream: InputStream, systemId: String? = null): StaxParser = factory(stream, systemId)
}