commonMain.nl.adaptivity.xmlutil.serialization.XML.kt Maven / Gradle / Ivy
/*
* Copyright (c) 2023.
*
* This file is part of xmlutil.
*
* This file is licenced to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You should have received a copy of the license with the source distribution.
* Alternatively, you may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
@file:Suppress("KDocUnresolvedReference")
package nl.adaptivity.xmlutil.serialization
import kotlinx.serialization.*
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.descriptors.capturedKClass
import kotlinx.serialization.encoding.CompositeDecoder
import kotlinx.serialization.modules.EmptySerializersModule
import kotlinx.serialization.modules.SerializersModule
import kotlinx.serialization.modules.plus
import nl.adaptivity.xmlutil.*
import nl.adaptivity.xmlutil.core.impl.multiplatform.Language
import nl.adaptivity.xmlutil.core.impl.multiplatform.MpJvmDefaultWithCompatibility
import nl.adaptivity.xmlutil.core.impl.multiplatform.StringWriter
import nl.adaptivity.xmlutil.core.impl.multiplatform.use
import nl.adaptivity.xmlutil.serialization.XML.Companion.encodeToWriter
import nl.adaptivity.xmlutil.serialization.XmlSerializationPolicy.DeclaredNameInfo
import nl.adaptivity.xmlutil.serialization.impl.*
import nl.adaptivity.xmlutil.serialization.structure.*
import nl.adaptivity.xmlutil.util.CompactFragment
import kotlin.jvm.JvmOverloads
import kotlin.reflect.KClass
@ExperimentalXmlUtilApi
private val defaultXmlModule = getPlatformDefaultModule() + SerializersModule {
contextual(QName::class, QNameSerializer)
}
@Suppress("MemberVisibilityCanBePrivate")
/**
* Class that provides access to XML parsing and serialization for the Kotlin Serialization system. In most cases the
* companion functions would be used. Creating an object explicitly however allows for the serialization to be configured.
*
* **Note** that at this point not all configuration options will work on all platforms.
*
* The serialization can be configured with various annotations: [XmlSerialName], [XmlChildrenName], [XmlPolyChildren],
* [XmlDefault], [XmlElement] [XmlValue]. These control the way the content is actually serialized. Those tags that support
* being set on types (rather than properties) prefer values set on properties (the property can override settings on the
* type).
*
* Serialization normally prefers to store values as attributes. This can be overridden by the [XmlElement] annotation
* to force a tag child. Similarly [XmlValue] can be used for the child to be marked as textual content of the parent tag
* - only one such child is allowed.
*
* Naming of tags and attributes follows some special rules. In particular attributes will be named based on their use
* where tags are named based on their type. Both can be overridden by specifying [XmlSerialName] on the property.
*
* When names are not specified on types, their class name is used, but the package is normally omitted if it matches the
* package name of the class that represents the parent. Attributes get their use site name which is either the property
* name or the name modified through [SerialName]
*
* @property serializersModule The serialization context used to resolve serializers etc.
* @property config The configuration of the various options that may apply.
*/
@OptIn(ExperimentalSerializationApi::class, ExperimentalXmlUtilApi::class)
public class XML(
public val config: XmlConfig,
serializersModule: SerializersModule = EmptySerializersModule()
) : StringFormat {
override val serializersModule: SerializersModule = serializersModule + defaultXmlModule
private val codecConfig: XmlCodecConfig = object : XmlCodecConfig {
override val serializersModule: SerializersModule
get() = [email protected]
override val config: XmlConfig
get() = [email protected]
}
@Suppress("DEPRECATION")
public constructor(
serializersModule: SerializersModule = EmptySerializersModule(),
configure: XmlConfig.Builder.() -> Unit = {}
) : this(XmlConfig.Builder().apply(configure), serializersModule)
@Suppress("DEPRECATION")
public fun copy(
serializersModule: SerializersModule = this.serializersModule,
configure: XmlConfig.Builder.() -> Unit,
): XML = XML(XmlConfig.Builder(config).apply(configure), serializersModule)
override fun encodeToString(serializer: SerializationStrategy, value: T): String {
return encodeToString(serializer, value, null)
}
/**
* Transform into a string. This function is expected to be called indirectly.
*
* @param value The actual object
* @param serializer The serializer/saver to use to write
* @param prefix The prefix (if any) to use for the namespace
*/
public fun encodeToString(serializer: SerializationStrategy, value: T, prefix: String?): String {
val stringWriter = StringWriter()
val xw = when {
config.defaultToGenericParser -> xmlStreaming.newGenericWriter(stringWriter, config.repairNamespaces, config.xmlDeclMode)
else -> xmlStreaming.newWriter(stringWriter, config.repairNamespaces, config.xmlDeclMode)
}
xw.use { xmlWriter ->
encodeToWriter(xmlWriter, serializer, value, prefix)
}
return stringWriter.toString()
}
/**
* Transform into a string. This function is expected to be called indirectly.
*
* @param value The actual object
* @param serializer The serializer/saver to use to write
* @param rootName The QName to use for the root tag
*/
public fun encodeToString(serializer: SerializationStrategy, value: T, rootName: QName): String {
val stringWriter = StringWriter()
val xw = when {
config.defaultToGenericParser -> xmlStreaming.newGenericWriter(stringWriter, config.repairNamespaces, config.xmlDeclMode)
else -> xmlStreaming.newWriter(stringWriter, config.repairNamespaces, config.xmlDeclMode)
}
xw.use { xmlWriter ->
encodeToWriter(xmlWriter, serializer, value, rootName)
}
return stringWriter.toString()
}
/**
* Write the object to the given writer
*
* @param value The actual object
* @param target The [XmlWriter] to append the object to
* @param prefix The prefix (if any) to use for the namespace
*/
public inline fun encodeToWriter(target: XmlWriter, value: T, prefix: String?) {
encodeToWriter(target, serializer(), value, prefix)
}
/**
* Transform onto an existing xml writer.
*
* @param target The [XmlWriter] to append the object to
* @param value The actual object
* @param serializer The serializer/saver to use to write
* @param prefix The prefix (if any) to use for the namespace
*/
public fun encodeToWriter(
target: XmlWriter,
serializer: SerializationStrategy,
value: T,
prefix: String? = null
) {
target.indentString = config.indentString
if (prefix != null) {
val root = XmlRootDescriptor(codecConfig, serializer.descriptor)
val serialQName = root.getElementDescriptor(0).tagName.copy(prefix = prefix)
encodeToWriter(target, serializer, value, serialQName)
} else {
encodeToWriter(target, serializer, value, rootName = null)
}
}
/**
* Transform onto an existing xml writer.
*
* @param target The [XmlWriter] to append the object to
* @param serializer The serializer/saver to use to write
* @param rootName The QName to use for the root tag
* @param value The actual object
*/
public fun encodeToWriter(
target: XmlWriter,
serializer: SerializationStrategy,
value: T,
rootName: QName?,
) {
target.indentString = config.indentString
if (target.depth == 0) {
when (config.xmlDeclMode) {
XmlDeclMode.Minimal -> {
target.startDocument(config.xmlVersion.versionString)
}
XmlDeclMode.Charset -> {
// TODO support non-utf8 encoding
target.startDocument(config.xmlVersion.versionString, encoding = "UTF-8")
}
XmlDeclMode.None,
XmlDeclMode.Auto -> Unit // no implementation needed
}
}
val safeSerialName = serializer.descriptor.run { capturedKClass?.maybeSerialName ?: serialName }
val declNameInfo = DeclaredNameInfo(safeSerialName, (serializer as? XmlSerialDescriptor)?.serialQName, false)
val policyDerivedName =
config.policy.serialTypeNameToQName(declNameInfo, DEFAULT_NAMESPACE)
val rootNameInfo = rootNameInfo(serializer.descriptor, rootName, policyDerivedName)
val root = XmlRootDescriptor(codecConfig, serializer.descriptor, rootNameInfo)
val xmlDescriptor = root.getElementDescriptor(0)
val xmlEncoderBase = XmlEncoderBase(serializersModule, config, target)
val encoder = when {
config.isCollectingNSAttributes -> {
val collectedNamespaces = collectNamespaces(xmlDescriptor, xmlEncoderBase, serializer, value)
val prefixMap = collectedNamespaces.associate { it.namespaceURI to it.prefix }
val newConfig = XmlConfig(XmlConfig.Builder(config).apply {
policy = PrefixWrappingPolicy(policy ?: policyBuilder().build(), prefixMap)
})
val remappedEncoderBase = XmlEncoderBase(serializersModule, newConfig, target)
val newRootName = rootNameInfo.remapPrefix(prefixMap)
val newRoot = XmlRootDescriptor(remappedEncoderBase, serializer.descriptor, newRootName)
val newDescriptor = newRoot.getElementDescriptor(0)
remappedEncoderBase.NSAttrXmlEncoder(
newDescriptor,
collectedNamespaces,
-1
)
}
else -> xmlEncoderBase.XmlEncoder(xmlDescriptor, -1)
}
encoder.encodeSerializableValue(serializer, value)
}
private fun collectNamespaces(
xmlDescriptor: XmlDescriptor,
xmlEncoderBase: XmlEncoderBase,
serializer: SerializationStrategy,
value: T
): List {
val prefixToNamespaceMap = HashMap()
val namespaceToPrefixMap = HashMap()
val pendingNamespaces = HashSet()
val seenDescriptors = HashSet()
var hasSeenDynamicQname = false
fun collect(prefix: String, namespaceUri: String) {
if (namespaceUri !in namespaceToPrefixMap) {
if (prefix in prefixToNamespaceMap) { // prefix with different usage
// For the default namespace, always force this to be the empty prefix (remap
// all other namespaces)
if (namespaceUri.isEmpty()) {
prefixToNamespaceMap[""]?.let { oldDefaultNamespace ->
pendingNamespaces.add(oldDefaultNamespace)
namespaceToPrefixMap.remove(oldDefaultNamespace)
}
prefixToNamespaceMap[""] = ""
namespaceToPrefixMap[""] = ""
} else {
pendingNamespaces.add(namespaceUri)
}
} else { // Prefix has not been seen before
if (namespaceUri in pendingNamespaces) { // If it matches a pending namespace use that
pendingNamespaces.remove(namespaceUri)
}
prefixToNamespaceMap[prefix] = namespaceUri
namespaceToPrefixMap[namespaceUri] = prefix
}
}
}
fun collect(descriptor: XmlDescriptor) {
val prefix = descriptor.tagName.prefix
val namespaceUri = descriptor.tagName.namespaceURI
/* Don't register attributes without prefix in the default namespace (that doesn't
* require namespace declarations). #135
*/
if (descriptor.effectiveOutputKind != OutputKind.Attribute || namespaceUri.isNotEmpty() || prefix.isNotEmpty()) {
collect(prefix, namespaceUri)
}
val childrenToCollect = mutableListOf()
if (descriptor is XmlPolymorphicDescriptor) {
childrenToCollect.addAll(descriptor.polyInfo.values)
}
for (elementIndex in 0 until descriptor.elementsCount) {
childrenToCollect.add(descriptor.getElementDescriptor(elementIndex))
}
for (childDescriptor in childrenToCollect) {
// Only check if we haven't seen a dynamic name yet.
if (!hasSeenDynamicQname && childDescriptor.overriddenSerializer.let { it is XmlSerializationStrategy<*> }) {
hasSeenDynamicQname = true
return
}
if (childDescriptor !in seenDescriptors) {
seenDescriptors.add(childDescriptor)
collect(childDescriptor)
}
}
}
val polyCollector = ChildCollector(null)
xmlEncoderBase.serializersModule.dumpTo(polyCollector)
collect(xmlDescriptor)
for (childSerializer in polyCollector.children) {
collect(xmlDescriptor(childSerializer))
}
if (hasSeenDynamicQname) {
// Collect all namespaces by actually generating the full document.
val collector = NamespaceCollectingXmlWriter(prefixToNamespaceMap, namespaceToPrefixMap, pendingNamespaces)
val base = XmlEncoderBase(xmlEncoderBase.serializersModule, xmlEncoderBase.config, collector)
base.XmlEncoder(xmlDescriptor, -1).encodeSerializableValue(serializer, value)
}
var nsIdx = 1
for (namespaceUri in pendingNamespaces) {
while ("ns$nsIdx" in prefixToNamespaceMap) {
nsIdx += 1
}
val prefix = "ns$nsIdx"
prefixToNamespaceMap[prefix] = namespaceUri
namespaceToPrefixMap[namespaceUri] = prefix
}
return prefixToNamespaceMap.asSequence()
.filterNot { (prefix, ns) -> prefix.isEmpty() && ns.isEmpty() } // skip empy namespace
.map { XmlEvent.NamespaceImpl(it.key, it.value) }
.sortedBy { it.prefix }
.toList()
}
/**
* Decode the given string value using the deserializer. It is equivalent to
* `decodeFromReader(deserializer, XmlStreaming.newReader(string))`.
* @param deserializer The deserializer to use.
* @param string The string input
*/
override fun decodeFromString(deserializer: DeserializationStrategy, @Language("XML", "", "") string: String): T {
val xr = when {
config.defaultToGenericParser -> xmlStreaming.newGenericReader(string)
else -> xmlStreaming.newReader(string)
}
return decodeFromReader(deserializer, xr)
}
/**
* Decode the given string value using the deserializer. It is equivalent to
* `decodeFromReader(deserializer, XmlStreaming.newReader(string))`.
* @param deserializer The deserializer to use.
* @param rootName The QName to use for the root tag
* @param string The string input
*/
public fun decodeFromString(
deserializer: DeserializationStrategy,
@Language("XML") string: String,
rootName: QName?
): T {
val xr = when {
config.defaultToGenericParser -> xmlStreaming.newGenericReader(string)
else -> xmlStreaming.newReader(string)
}
return decodeFromReader(deserializer, xr, rootName)
}
/**
* Parse an object of the type [T] out of the reader. This function is intended mostly to be used indirectly where
* though the reified function.
*
* @param reader An [XmlReader] that contains the XML from which to read the object
* @param rootName The QName to use for the root tag
* @param T The type to use to read the object
*/
public inline fun decodeFromReader(reader: XmlReader, rootName: QName? = null): T =
decodeFromReader(serializer(), reader, rootName)
/**
* Function that determines the "proper" use name requirement for a root tag.
* @param descriptor The descriptor of the root tag
* @param rootName The explicitly given name requirement
* @param localName The qname from the reader or (for writer derived from the serial name - avoiding captured types)
*/
private fun rootNameInfo(descriptor: SerialDescriptor, rootName: QName?, localName: QName): DeclaredNameInfo {
if (rootName != null) {
return DeclaredNameInfo(localName.localPart, rootName, false)
}
val tmpRoot =
XmlRootDescriptor(codecConfig, descriptor, DeclaredNameInfo(localName.localPart))
val realName = tmpRoot.typeDescriptor.typeQname ?: localName
return DeclaredNameInfo(realName.localPart, realName, false)
}
/**
* Parse an object of the type [T] out of the reader. This function is intended mostly to be used indirectly where
* though the reified function.
*
* @param reader An [XmlReader] that contains the XML from which to read the object
* @param rootName The QName to use for the root tag
* @param deserializer The loader to use to read the object
*/
@JvmOverloads
public fun decodeFromReader(
deserializer: DeserializationStrategy,
reader: XmlReader,
rootName: QName? = null
): T {
// We skip all ignorable content here. To get started while supporting direct content we need to put the parser
// in the correct state of having just read the startTag (that would normally be read by the code that determines
// what to parse (before calling readSerializableValue on the value)
reader.skipPreamble()
val xmlDecoderBase = XmlDecoderBase(serializersModule, config, reader)
val rootNameInfo = rootNameInfo(deserializer.descriptor, rootName, reader.name)
val rootDescriptor = XmlRootDescriptor(xmlDecoderBase, deserializer.descriptor, rootNameInfo)
val elementDescriptor = rootDescriptor.getElementDescriptor(0)
val polyInfo: PolyInfo? = if (elementDescriptor is XmlPolymorphicDescriptor) {
val tagName = reader.name
val info = elementDescriptor.polyInfo.values.singleOrNull {
tagName.isEquivalent(it.tagName)
}
info?.let { PolyInfo(tagName, 0, it) }
} else {
// only check names when not having polymorphic root
val serialName = rootDescriptor.getElementDescriptor(0).tagName
if (!serialName.isEquivalent(reader.name)) {
throw XmlException("Local name \"${reader.name}\" for root tag does not match expected name \"$serialName\"")
}
null
}
val decoder = xmlDecoderBase.XmlDecoder(elementDescriptor, polyInfo)
return decoder.decodeSerializableValue(deserializer)
}
@JvmOverloads
public fun xmlDescriptor(serializer: SerializationStrategy<*>, rootName: QName? = null): XmlDescriptor {
return xmlDescriptor(serializer.descriptor, rootName)
}
@JvmOverloads
public fun xmlDescriptor(deserializer: DeserializationStrategy<*>, rootName: QName? = null): XmlDescriptor {
return xmlDescriptor(deserializer.descriptor, rootName)
}
@JvmOverloads
public fun xmlDescriptor(deserializer: KSerializer<*>, rootName: QName? = null): XmlDescriptor {
return xmlDescriptor(deserializer.descriptor, rootName)
}
private fun xmlDescriptor(serialDescriptor: SerialDescriptor, rootName: QName? = null): XmlRootDescriptor {
val nameInfo = DeclaredNameInfo(rootName?.localPart ?: serialDescriptor.serialName, rootName, false)
return XmlRootDescriptor(codecConfig, serialDescriptor, nameInfo)
}
@Deprecated("Use config directly", ReplaceWith("config.repairNamespaces"), DeprecationLevel.HIDDEN)
public val repairNamespaces: Boolean
get() = config.repairNamespaces
@Suppress("DEPRECATION")
@Deprecated("Use config directly", ReplaceWith("config.omitXmlDecl"), DeprecationLevel.HIDDEN)
public val omitXmlDecl: Boolean
get() = config.omitXmlDecl
@Suppress("DEPRECATION")
@Deprecated(
"Use config directly, consider using indentString",
ReplaceWith("config.indent"),
DeprecationLevel.HIDDEN
)
public val indent: Int
get() = config.indent
@Suppress("DEPRECATION")
@Deprecated("Use the new configuration system", level = DeprecationLevel.HIDDEN)
public constructor(
repairNamespaces: Boolean = true,
omitXmlDecl: Boolean = true,
indent: Int = 0,
serializersModule: SerializersModule = EmptySerializersModule()
) : this(XmlConfig(repairNamespaces, omitXmlDecl, indent), serializersModule)
@Deprecated(
"This version of the constructor has limits in future compatibility. Use the version that takes a configuration lambda",
)
@ExperimentalXmlUtilApi
public constructor(config: XmlConfig.Builder, serializersModule: SerializersModule = EmptySerializersModule()) :
this(XmlConfig(config), serializersModule)
@Suppress("DeprecatedCallableAddReplaceWith")
@Deprecated(
"This version of the copy function has limits in future compatibility. Use the version that takes a configuration lambda",
level = DeprecationLevel.ERROR
)
@ExperimentalXmlUtilApi
public fun copy(
config: XmlConfig = this.config,
serializersModule: SerializersModule = this.serializersModule
): XML = XML(config, serializersModule)
/**
* Transform the object into an XML String. This is a shortcut for the non-reified version that takes a
* KClass parameter
*/
@Deprecated(
"Use encodeToString", ReplaceWith(
"encodeToString(obj, prefix)",
"nl.adaptivity.xmlutil.serialization.XML.Companion.encodeToString"
),
level = DeprecationLevel.ERROR
)
@Suppress("unused")
public inline fun stringify(obj: T, prefix: String? = null): String =
encodeToString(obj, prefix)
/**
* Transform into a string. This function is expected to be called indirectly.
*
* @param obj The actual object
* @param saver The serializer/saver to use to write
* @param prefix The prefix (if any) to use for the namespace
*/
@Deprecated(
"Fit within the serialization library, so reorder arguments",
ReplaceWith("stringify(saver, obj, prefix)"),
level = DeprecationLevel.ERROR
)
public fun stringify(obj: T, saver: SerializationStrategy, prefix: String? = null): String =
encodeToString(saver, obj, prefix)
@Deprecated("Use encodeToString", ReplaceWith("encodeToString(serializer, value)"), DeprecationLevel.ERROR)
public fun stringify(serializer: SerializationStrategy, value: T): String =
encodeToString(serializer, value)
/**
* Transform into a string. This function is expected to be called indirectly.
*
* @param obj The actual object
* @param serializer The serializer/saver to use to write
* @param prefix The prefix (if any) to use for the namespace
*/
@Deprecated("Use encodeToString", ReplaceWith("encodeToString(serializer, obj, prefix)"), DeprecationLevel.ERROR)
public fun stringify(serializer: SerializationStrategy, obj: T, prefix: String?): String =
encodeToString(serializer, obj, prefix)
/**
* Transform onto an existing xml writer.
*
* @param target The [XmlWriter] to append the object to
* @param value The actual object
* @param serializer The serializer/saver to use to write
* @param prefix The prefix (if any) to use for the namespace
*/
@Deprecated(
"Renamed to encodeToWriter",
ReplaceWith("encodeToWriter(target, serializer, value, prefix)"),
DeprecationLevel.ERROR
)
public fun toXml(
target: XmlWriter,
serializer: SerializationStrategy,
value: T,
prefix: String? = null
) {
encodeToWriter(target, serializer, value, prefix)
}
/**
* Write the object to the given writer
*
* @param obj The actual object
* @param target The [XmlWriter] to append the object to
* @param prefix The prefix (if any) to use for the namespace
*/
@Deprecated(
"Use new naming scheme: encodeToWriter",
ReplaceWith("encodeToWriter(target, obj, prefix)"),
DeprecationLevel.ERROR
)
public inline fun toXml(target: XmlWriter, obj: T, prefix: String? = null) {
encodeToWriter(target, obj, prefix)
}
@Deprecated(
"Replaced by version with consistent parameter order",
ReplaceWith("toXml(target, obj, prefix)"),
DeprecationLevel.ERROR
)
public inline fun toXml(obj: T, target: XmlWriter, prefix: String? = null) {
encodeToWriter(target, obj, prefix)
}
/**
* Parse an object of the type [T] out of the reader
*/
@Suppress("unused")
@Deprecated("Renamed to decodeFromReader", ReplaceWith("decodeFromReader(reader)"), DeprecationLevel.ERROR)
public inline fun parse(reader: XmlReader): T = decodeFromReader(reader)
/**
* Parse an object of the type [T] out of the reader. This function is intended mostly to be used indirectly where
* though the reified function.
*
* @param reader An [XmlReader] that contains the XML from which to read the object
* @param deserializer The loader to use to read the object
*/
@Deprecated(
"Renamed to decodeFromReader",
ReplaceWith("decodeFromReader(deserializer, reader)"),
DeprecationLevel.ERROR
)
public fun parse(deserializer: DeserializationStrategy, reader: XmlReader): T {
return decodeFromReader(deserializer, reader)
}
@Deprecated("Use new function name", ReplaceWith("decodeFromString(deserializer, string)"), DeprecationLevel.ERROR)
public fun parse(deserializer: DeserializationStrategy, string: String): T {
return decodeFromString(deserializer, string)
}
/**
* Transform into a string. This function is expected to be called indirectly.
*
* @param kClass The type of the object being serialized
* @param obj The actual object
* @param prefix The prefix (if any) to use for the namespace
*/
@Deprecated("Reflection is no longer supported", level = DeprecationLevel.HIDDEN)
@Suppress("UNUSED_PARAMETER")
public fun stringify(kClass: KClass, obj: T, prefix: String? = null): String {
throw UnsupportedOperationException("Not supported by serialization library ")
}
/**
* Transform into a string. This function is expected to be called indirectly.
*
* @param kClass The type of the object being serialized
* @param obj The actual object
* @param target The [XmlWriter] to append the object to
* @param saver The serializer/saver to use to write
* @param prefix The prefix (if any) to use for the namespace
*/
@Deprecated("Reflection is no longer supported", level = DeprecationLevel.HIDDEN)
@Suppress("UNUSED_PARAMETER")
public fun toXml(target: XmlWriter, kClass: KClass, obj: T, prefix: String? = null) {
throw UnsupportedOperationException("Reflection no longer works")
}
@Deprecated("Reflection is no longer supported", level = DeprecationLevel.HIDDEN)
@Suppress("UNUSED_PARAMETER")
public fun parse(kClass: KClass, reader: XmlReader): T {
throw UnsupportedOperationException("Reflection for serialization is no longer supported")
}
/**
* Parse an object of the type [T] out of the string. It merely creates an xml reader and forwards the request.
* This function is intended mostly to be used indirectly where
* though the reified function. The loader defaults to the loader for [kClass]
*
* @param kClass The actual class object to parse the object from.
* @param string The string that contains the XML from which to read the object
* @param loader The loader to use to read the object
*/
@Deprecated("Reflection is no longer supported", level = DeprecationLevel.HIDDEN)
@Suppress("UNUSED_PARAMETER")
public fun parse(kClass: KClass, string: String): T {
throw UnsupportedOperationException("Reflection for serialization is no longer supported")
}
public companion object : StringFormat {
public val defaultInstance: XML = XML {}
override val serializersModule: SerializersModule
get() = defaultInstance.serializersModule
public fun xmlDescriptor(serializer: SerializationStrategy<*>): XmlDescriptor {
return defaultInstance.xmlDescriptor(serializer)
}
public fun xmlDescriptor(deserializer: DeserializationStrategy<*>): XmlDescriptor {
return defaultInstance.xmlDescriptor(deserializer)
}
public fun xmlDescriptor(serializer: KSerializer<*>): XmlDescriptor {
return defaultInstance.xmlDescriptor(serializer)
}
/**
* Transform the object into an XML string. This requires the object to be serializable by the kotlin
* serialization library (either it has a built-in serializer or it is [kotlinx.serialization.Serializable].
* @param value The object to transform
* @param serializer The serializer to user
*/
override fun encodeToString(
serializer: SerializationStrategy,
value: T
): String = defaultInstance.encodeToString(serializer, value)
/**
* Transform the object into an XML string. This requires the object to be serializable by the kotlin
* serialization library (either it has a built-in serializer or it is [kotlinx.serialization.Serializable].
* @param value The object to transform
* @param serializer The serializer to user
* @param prefix The namespace prefix to use
*/
public fun encodeToString(serializer: SerializationStrategy, value: T, prefix: String): String =
defaultInstance.encodeToString(serializer, value, prefix)
/**
* Transform the object into an XML string. This requires the object to be serializable by the kotlin
* serialization library (either it has a built-in serializer or it is [kotlinx.serialization.Serializable].
* @param value The object to transform
* @param serializer The serializer to user
* @param rootName The QName to use for the root tag
*/
public fun encodeToString(serializer: SerializationStrategy, value: T, rootName: QName): String =
defaultInstance.encodeToString(serializer, value, rootName)
/**
* Transform the object into an XML string. This requires the object to be serializable by the kotlin
* serialization library (either it has a built-in serializer or it is [kotlinx.serialization.Serializable].
* @param obj The object to transform
* @param prefix The namespace prefix to use
*/
public inline fun encodeToString(obj: T, prefix: String? = null): String =
encodeToString(serializer(), obj, prefix ?: "")
/**
* Transform the object into an XML string. This requires the object to be serializable by the kotlin
* serialization library (either it has a built-in serializer or it is [kotlinx.serialization.Serializable].
* @param obj The object to transform
* @param rootName The QName to use for the root tag
*/
public inline fun encodeToString(obj: T, rootName: QName): String =
encodeToString(serializer(), obj, rootName)
/**
* Write the object to the given writer
*
* @param target The [XmlWriter] to append the object to
* @param value The actual object
* @param prefix The prefix (if any) to use for the namespace
*/
public inline fun encodeToWriter(target: XmlWriter, value: T, prefix: String? = null) {
defaultInstance.encodeToWriter(target, serializer(), value, prefix)
}
/**
* Write the object to the given writer
*
* @param target The [XmlWriter] to append the object to
* @param value The actual object
* @param rootName The QName to use for the root tag
*/
public inline fun encodeToWriter(target: XmlWriter, value: T, rootName: QName) {
defaultInstance.encodeToWriter(target, serializer(), value, rootName)
}
/**
* Write the object to the given writer
*
* @param target The [XmlWriter] to append the object to
* @param serializer The serializer to use
* @param value The actual object
* @param prefix The prefix (if any) to use for the namespace
*/
public fun encodeToWriter(
target: XmlWriter,
serializer: SerializationStrategy,
value: T,
prefix: String? = null
) {
defaultInstance.encodeToWriter(target, serializer, value, prefix)
}
/**
* Write the object to the given writer
*
* @param target The [XmlWriter] to append the object to
* @param serializer The serializer to use
* @param value The actual object
* @param rootName The QName to use for the root tag
*/
public fun encodeToWriter(
target: XmlWriter,
serializer: SerializationStrategy,
value: T,
rootName: QName
) {
defaultInstance.encodeToWriter(target, serializer, value, rootName)
}
/**
* Parse an object of the type [T] out of the reader
* @param str The source of the XML events
* @param rootName The QName to use for the root tag
*/
@JvmOverloads
public inline fun decodeFromString(str: String, rootName: QName? = null): T =
decodeFromString(serializer(), str, rootName)
/**
* Parse an object of the type [T] out of the reader
* @param deserializer The loader to use
* @param string The source of the XML events
*/
override fun decodeFromString(deserializer: DeserializationStrategy, string: String): T {
return defaultInstance.decodeFromString(deserializer, string)
}
/**
* Parse an object of the type [T] out of the reader
* @param deserializer The loader to use
* @param rootName The QName to use for the root tag
* @param string The source of the XML events
*/
public fun decodeFromString(deserializer: DeserializationStrategy, string: String, rootName: QName?): T {
return defaultInstance.decodeFromString(deserializer, string, rootName)
}
/**
* Parse an object of the type [T] out of the reader
* @param reader The source of the XML events
* @param rootName The QName to use for the root tag
*/
@JvmOverloads
public inline fun decodeFromReader(reader: XmlReader, rootName: QName? = null): T =
defaultInstance.decodeFromReader(reader, rootName)
/**
* Parse an object of the type [T] out of the reader
* @param deserializer The loader to use (rather than the default)
* @param rootName The QName to use for the root tag
* @param reader The source of the XML events
*/
@JvmOverloads
public fun decodeFromReader(
deserializer: DeserializationStrategy,
reader: XmlReader,
rootName: QName? = null
): T = defaultInstance.decodeFromReader(deserializer, reader, rootName)
@Deprecated("Use encodeToString", ReplaceWith("encodeToString(serializer, value)"), DeprecationLevel.ERROR)
public fun stringify(serializer: SerializationStrategy, value: T): String {
return encodeToString(serializer, value)
}
/**
* Transform the object into an XML string. This requires the object to be serializable by the kotlin
* serialization library (either it has a built-in serializer or it is [kotlinx.serialization.Serializable].
* @param obj The object to transform
* @param serializer The serializer to user
* @param prefix The namespace prefix to use
*/
@Deprecated(
"Use encodeToString",
ReplaceWith(
"encodeToString(serializer, obj, prefix)",
"nl.adaptivity.xmlutil.serialization.XML.Companion.encodeToString"
),
DeprecationLevel.ERROR
)
public fun stringify(serializer: SerializationStrategy, obj: T, prefix: String): String =
encodeToString(serializer, obj, prefix)
/**
* Transform the object into an XML string. This requires the object to be serializable by the kotlin
* serialization library (either it has a built-in serializer or it is [kotlinx.serialization.Serializable].
* @param obj The object to transform
* @param prefix The namespace prefix to use
*/
@Deprecated(
"Use encodeToString",
ReplaceWith(
"encodeToString(obj, prefix ?: \"\")",
"nl.adaptivity.xmlutil.serialization.XML.Companion.encodeToString"
),
DeprecationLevel.ERROR
)
public inline fun stringify(obj: T, prefix: String? = null): String =
encodeToString(obj, prefix ?: "")
/**
* Write the object to the given writer
*
* @param obj The actual object
* @param dest The [XmlWriter] to append the object to
* @param prefix The prefix (if any) to use for the namespace
*/
@Suppress("unused")
@Deprecated(
"Renamed to encodeToWriter",
ReplaceWith(
"encodeToWriter(dest, obj, prefix)",
"nl.adaptivity.xmlutil.serialization.XML.Companion.encodeToWriter"
),
DeprecationLevel.ERROR
)
public inline fun toXml(dest: XmlWriter, obj: T, prefix: String? = null) {
encodeToWriter(dest, obj, prefix)
}
/**
* Write the object to the given writer
*
* @param target The [XmlWriter] to append the object to
* @param serializer The serializer to use
* @param value The actual object
* @param prefix The prefix (if any) to use for the namespace
*/
@Deprecated(
"Renamed to encodeToWriter",
ReplaceWith(
"encodeToWriter(target, serializer, value, prefix)",
"nl.adaptivity.xmlutil.serialization.XML.Companion.encodeToWriter"
),
DeprecationLevel.ERROR
)
public fun toXml(
target: XmlWriter,
serializer: SerializationStrategy,
value: T,
prefix: String? = null
) {
encodeToWriter(target, serializer, value, prefix)
}
/**
* Parse an object of the type [T] out of the reader
* @param str The source of the XML events
*/
@Deprecated(
"Use decodeFromString",
ReplaceWith("decodeFromString(str)", "nl.adaptivity.xmlutil.serialization.XML.Companion.decodeFromString"),
DeprecationLevel.ERROR
)
public inline fun parse(str: String): T = decodeFromString(str)
/**
* Parse an object of the type [T] out of the reader
* @param string The source of the XML events
* @param deserializer The loader to use
*/
@Suppress("unused")
@Deprecated("Use new name", ReplaceWith("decodeFromString(deserializer, string)"), DeprecationLevel.ERROR)
public fun parse(
deserializer: DeserializationStrategy,
string: String
): T = decodeFromString(deserializer, string)
@Deprecated(
"Replaced by version with consistent parameter order",
ReplaceWith("parse(reader, kClass, loader)"),
DeprecationLevel.HIDDEN
)
public fun parse(
@Suppress("UNUSED_PARAMETER") kClass: KClass, reader: XmlReader, loader: DeserializationStrategy
): T = decodeFromReader(loader, reader)
/**
* Parse an object of the type [T] out of the reader
* @param reader The source of the XML events
* @param loader The loader to use (rather than the default)
*/
@Suppress("UNUSED_PARAMETER")
@Deprecated(
"Use the version that doesn't take a KClass",
ReplaceWith("parse(reader, loader)", "nl.adaptivity.xmlutil.serialization.XML.Companion.parse"),
DeprecationLevel.HIDDEN
)
public fun parse(reader: XmlReader, kClass: KClass, loader: DeserializationStrategy): T =
decodeFromReader(loader, reader)
/**
* Parse an object of the type [T] out of the reader
* @param reader The source of the XML events
*/
@Deprecated(
"Renamed to decodeFromReader", ReplaceWith(
"decodeFromReader(reader)",
"nl.adaptivity.xmlutil.serialization.XML.Companion.decodeFromReader"
),
DeprecationLevel.HIDDEN
)
public inline fun parse(reader: XmlReader): T = decodeFromReader(reader)
/**
* Parse an object of the type [T] out of the reader
* @param reader The source of the XML events
* @param loader The loader to use (rather than the default)
*/
@Suppress("unused")
@Deprecated(
"Renamed to decodeFromReader", ReplaceWith(
"decodeFromReader(reader, loader)",
"nl.adaptivity.xmlutil.serialization.XML.Companion.decodeFromReader"
),
DeprecationLevel.HIDDEN
)
public fun parse(reader: XmlReader, loader: DeserializationStrategy): T =
decodeFromReader(loader, reader)
}
@MpJvmDefaultWithCompatibility
public interface XmlCodecConfig {
/**
* The currently active serialization context
*/
public val serializersModule: SerializersModule
/**
* The configuration used for serialization
*/
public val config: XmlConfig
/**
* A delegate method to get access to a format with the same configuration
*/
public fun delegateFormat(): XML = XML(config, serializersModule)
}
/**
* An interface that allows custom serializers to special case being serialized to XML and retrieve the underlying
* [XmlWriter]. This is used for example by [CompactFragment] to make the fragment transparent when serializing to
* XML.
*/
@MpJvmDefaultWithCompatibility
public interface XmlOutput : XmlCodecConfig {
/**
* The name for the current tag
*/
public val serialName: QName
/**
* Ensure that the prefix of the [qName] is recorded (and the prefix added). This will not
* add the actual name anywhere, just ensures the namespace attribute if needed
*
* @param qName The name to try to ensure is valid
* @return The [QName] to use. This may have a different prefix if the prefix for the parameter would be
* conflicting.
*/
public fun ensureNamespace(qName: QName): QName = ensureNamespace(qName, false)
/**
* Ensure that the prefix of the [qName] is recorded (and the prefix added). This will not
* add the actual name anywhere, just ensures the namespace attribute if needed
*
* @param qName The name to try to ensure is valid
* @param isAttr Ensure handling attribute default namespaces correctly
* @return The [QName] to use. This may have a different prefix if the prefix for the parameter would be
* conflicting.
*/
public fun ensureNamespace(qName: QName, isAttr: Boolean): QName
/**
* The XmlWriter used. Can be used directly by serializers
*/
public val target: XmlWriter
@WillBePrivate
@Deprecated("Not used will always return null", ReplaceWith("null"), DeprecationLevel.HIDDEN)
public val currentTypeName: Nothing?
get() = null
}
/**
* An interface that allows custom serializers to special case being serialized to XML and retrieve the underlying
* [XmlReader]. This is used for example by [CompactFragment] to read arbitrary XML from the stream and store it inside
* the buffer (without attempting to use the serializer/decoder for it.
*/
@MpJvmDefaultWithCompatibility
public interface XmlInput : XmlCodecConfig {
/**
* The reader used. Can be used directly by serializers
*/
public val input: XmlReader
public fun getNamespaceURI(prefix: String): String? = input.namespaceContext.getNamespaceURI(prefix)
}
/**
* Class to support recovery in parsing.
* @property elementIndex The index of the child element that is the data that is parsed
* @property value The value for the particular property
* @property unParsed It is also possible to just provide a property index. In this case the value
* of this property should be `true` and the value of the [value] property is ignored (should
* be null)
*/
@ExperimentalXmlUtilApi
public data class ParsedData(public val elementIndex: Int, public val value: T, val unParsed: Boolean = false)
}
public fun XmlSerialName.toQName(serialName: String, parentNamespace: Namespace?): QName = when {
namespace == UNSET_ANNOTATION_VALUE -> when (value) {
UNSET_ANNOTATION_VALUE -> parentNamespace?.let { QName(it.namespaceURI, serialName) } ?: QName(serialName)
else -> parentNamespace?.let { QName(it.namespaceURI, value) } ?: QName(value)
}
value == UNSET_ANNOTATION_VALUE -> when (prefix) {
UNSET_ANNOTATION_VALUE -> QName(namespace, serialName)
else -> QName(serialName, namespace, prefix)
}
prefix == UNSET_ANNOTATION_VALUE -> QName(namespace, value)
else -> QName(namespace, value, prefix)
}
public fun XmlChildrenName.toQName(): QName = when {
namespace == UNSET_ANNOTATION_VALUE -> QName(value)
prefix == UNSET_ANNOTATION_VALUE -> QName(namespace, value)
else -> QName(namespace, value, prefix)
}
internal inline fun Iterable<*>.firstOrNull(): T? {
for (e in this) {
if (e is T) return e
}
return null
}
@OptIn(ExperimentalSerializationApi::class)
internal fun SerialDescriptor.getValueChild(): Int {
for (i in 0 until elementsCount) {
if (getElementAnnotations(i).any { it is XmlValue }) return i
}
return CompositeDecoder.UNKNOWN_NAME
}
internal fun XmlDescriptor.getValueChild(): Int {
return (this as? XmlCompositeDescriptor)?.valueChild ?: -1
}
internal fun XmlDescriptor.getAttrMap(): Int {
return (this as? XmlCompositeDescriptor)?.attrMapChild ?: -1
}
/** Straightforward copy function */
internal fun QName.copy(
namespaceURI: String = this.namespaceURI,
localPart: String = this.localPart,
prefix: String = this.prefix
) =
QName(namespaceURI, localPart, prefix)
/** Shortcircuit copy function that creates a new version (if needed) with the new prefix only */
internal fun QName.copy(prefix: String = this.prefix) = when (prefix) {
this.prefix -> this
else -> QName(namespaceURI, localPart, prefix)
}
/**
* Extension function for writing an object as XML.
*
* @param out The writer to use for writing the XML
* @param serializer The serializer to use. Often `T.Companion.serializer()`
*/
@Suppress("DeprecatedCallableAddReplaceWith")
@Deprecated("Use the XML object that allows configuration", level = DeprecationLevel.ERROR)
public fun T.writeAsXml(out: XmlWriter, serializer: SerializationStrategy) {
XML.defaultInstance.encodeToWriter(out, serializer, this)
}
/**
* Extension function that allows any (serializable) object to be written to an XmlWriter.
*/
@Deprecated(
"Use the XML object that allows configuration",
ReplaceWith("XML.toXml(out, this)"),
level = DeprecationLevel.ERROR
)
public inline fun T.writeAsXML(out: XmlWriter) {
encodeToWriter(out, this)
}
@RequiresOptIn("This function will become private in the future", RequiresOptIn.Level.WARNING)
public annotation class WillBePrivate
© 2015 - 2024 Weber Informatics LLC | Privacy Policy