Home

Awesome

XmlUtil

Build Status GitHub license

XmlUtil is a set of packages that supports multiplatform XML in Kotlin.

Introduction

This project is a cross-platform XML serialization (wrapping) library compatible with kotlinx.serialization. It supports all platforms although native is at beta quality.

Based upon the core xml library, the serialization module supports automatic object serialization based upon Kotlin's standard serialization library and plugin.

Help wanted: Any help with extending this project is welcome. Help is especially needed for the following aspects:

Notes

Please note that the JVM target will not work on Android due to different serialization libraries. It is possible to consume the multiplatform targets on single-target Kotlin although there may be issues with older Gradle versions not finding the correct version. As a workaround for single-platform Android projects, try adding the following code to your Gradle build file:

kotlin {
    target {
        attributes {
            if (KotlinPlatformType.attribute !in this) {
                attribute(KotlinPlatformType.attribute, KotlinPlatformType.androidJvm)
            }
        }
    }
}

KotlinPlatformType.setupAttributesMatchingStrategy(dependencies.attributesSchema)

This code tells Gradle that are targeting Android when it resolves multi-platform libraries. In other cases you can use the different platform types.

Versioning scheme

This library is based upon the unstable kotlinx.serialization library. While every effort is made to limit incompatible changes, this cannot be guaranteed even in "minor" versions when the changes are due to bugs. These changes should mostly be limited to the serialization part of the library.

How to use

The library is designed as a multiplatform Kotlin module, but platform-specific versions can also be used were appropriate.

Add repository

The project's Maven access is hosted on OSS Sonatype (and available from Maven Central).

Releases can be added from maven central

They are also available directly from Sonatype by adding the following to your Gradle build file:

repositories {
	maven {
		url  "https://s01.oss.sonatype.org/content/repositories/releases/"
	}
}

Snapshots are available from:

repositories {
	maven {
		url  "https://s01.oss.sonatype.org/content/repositories/snapshots/"
	}
}

Core

It should be noted that the JVM and Android packages are no longer part of the multiplatform publication (they are combined into a jvmCommon) package. The JVM and Android packages provide the native implementations and depend on (publishing) the jvmCommon package.

multiplatform (will default to multiplatform implementation for JVM/Android)

   implementation("io.github.pdvrieze.xmlutil:core:0.90.3")

Optional JVM – uses the stax API not available on Android

   implementation("io.github.pdvrieze.xmlutil:core-jvm:0.90.3")

Optional Android – Uses the android streaming library

   implementation("io.github.pdvrieze.xmlutil:core-android:0.90.3")

JS – Wraps DOM

   implementation("io.github.pdvrieze.xmlutil:core-js:0.90.3")

Native

Has platform independent implementations of xml parsing/serialization (based upon the Android implementation) and DOM (a simple implementation that mirrors the Java API)

Serialization

multiplatform

   implementation("io.github.pdvrieze.xmlutil:serialization:0.90.3")

JVM

   implementation("io.github.pdvrieze.xmlutil:serialization-jvm:0.90.3")

Android

   implementation("io.github.pdvrieze.xmlutil:serialization-android:0.90.3")

js

   implementation("io.github.pdvrieze.xmlutil:serialization-js:0.90.3")

-Ktor- (Deprecated)

Deprecated

This library is no longer supported. Instead use official Ktor xml serialization support. It is mostly equal to this version.

Serialization help

Hello world

To serialize a very simple type you have the following:

@Serializable
data class HelloWorld(val user: String)

println(XML.encodeToString(HelloWorld("You!")))

To deserialize you would do:

@Serializable
data class HelloWorld(val user: String)

XML.decodeFromString(HelloWorld.serializer(), "<HelloWorld user='You!' />")

Please look at the examples and the documentation for further features that can influence: the tag names/namespaces used, the actual structure used (how lists and polymorphic types are handled), etc.

Examples

You should be able to find examples in the Examples module

Format

The entrypoint to the library is the XML format. There is a default, but often a child is better. Custom formats are created through:

val format = XML(mySerialModule) {  
    // configuration options
    autoPolymorphism = true 
}

The following options are available when using the XML format builder:

OptionDescription
repairNamespacesShould namespaces automatically be repaired. This option will be passed on to the XmlWriter
xmlDeclModeThe mode to use for emitting XML declarations (<?xml ...?>). Replaces omitXmlDecl for more finegrained control
indentStringThe indentation to use. Must be a combination of XML whitespace or comments (this is checked). This is passed to the XmlWriter
-autoPolymorphic-Deprecated Shorcut to policy.autoPolymorphic
isInlineCollapsedIf true(default) the content of an inline type is used directly, with the name of the inline type.
xmlVersionWhich xml version will be written/declared (default XML 1.1)
isCollectingNSAttributes(Attempt to) collect all needed namespace declarations and emit them on the root tag, this does have a performance overhead
defaultToGenericParserUse the generic parser, rather than the platform specific one.
policyThis is a class that can be used to define a custom policy that informs how the kotlin structure is translated to XML. It drives most complex configuration
defaultPolicy {}Builder that allows configuring the default policy. This policy is stable, it doesn't change across versions.
recommended_0_86_3 {}Builder that sets the policy to the recommended defaults per version 0.86.3, this is stable and includes: autopolymorphic, inlineCollapsed, indent=4, p.pedantic, p.typeDiscriminatorName=xsi:type, encodeDefault=ANNOTATED, throwOnRepeatedElement, isStrictAttributeNames
recommended_0_90_2 {}Builder that sets the policy to the recommended defaults per version 0.90.2, this is stable and extends from 0.86.3. It uses xml 1.1, strict booleans, and minimal xml declaration
fast_0_90_2 {}Builder that sets the policy to the recommended for fast parsing. This reduces error checks, but otherwise has the same semantics as 0.90.2.
recommended {}Builder that sets the policy to the currently recommended defaults (the 0.90.2 configuration)
-indent-Deprecated for reading: The indentation level (in spaces) to use. This is backed by indentString. Reading is "invalid" for indentString values that are not purely string sequences. Writing it will set indentation as the specified amount of spaces.
-omitXmlDecl-Deprecated (use xmlDeclMode). Should the generated XML contain an XML declaration or not. This is passed to the XmlWriter
-unknownChildHandler-Deprecated into policy A function that is called when an unknown child is found. By default an exception is thrown but the function can silently ignore it as well.

The properties that have been moved into the policy can still be set in the builder, but are no longer able to be read through the config object.

The following options are available as part of the default policy builder. Note that the policy is designed to allow configuration through code, but the default policy has significant configuration options available.

OptionDescription
pedanticFail on output type specifications that are incompatible with the data, rather than silently correcting this
autoPolymorphicWhen not specifying a custom policy this determines whether polymorphism is handled without wrappers. This replaces XmlPolyChildren, but changes serialization where that annotation is not applied. This option will become the default in the future although XmlPolyChildren will retain precedence (when present)
encodeDefaultDetermine whether in which cases default values should be encoded.
unknownChildHandlerA function that is called when an unknown child is found. By default an exception is thrown but the function can silently ignore it as well.
typeDiscriminatorNameThis property determines the type discriminator attribute used. It is always recognised, but not serialized in transparent polymorphic (autoPolymorphic) mode. If this is null, a wrapper tag with type attribute is used instead of a discriminator.
throwOnRepeatedElementRather than silently allowing a repeated element (not part of a list), throw an exception if the element occurs multiple times.
verifyElementOrderWhile element order (when specified using @XmlBefore and @XmlAfter) is always used for serialization, this flag allows checking this order on inputs.
isStrictAttributeNamesEnables stricter, standard compliant attribute name mapping in respect to default/null namespaces. Mainly relevant to decoding.

Algorithms

XML and Kotlin data types are not perfectly alligned. As such there are some algorithms that aim to automatically make a "best attempt" at structuring the XML document. Most of this is implemented in the default XmlSerializationPolicy implementation, but this can be customized/replaced with a policy that results in a different structure. The policy includes the mapping from types/attributes to tag and attribute names.

Storage type

In the default policy, the way a field is stored is automatically determined to be one of: Element, Attribute, Text or Mixed. Mixed is a special type that allows for mixing of text and element content and requires some special treatment.:

Tag/attribute name

The way the name is determined is configured/implemented through the configured policy. The documentation below is for the default policy. This is designed to allow customization by users.

Based upon the storage type, the effective name for an attribute is determined as follows:

The effective name for a regular tag is determined as follows for normal serializers:

The effective name for a polymorphic child is determined as follows:

The implementation if serialization in the Kotlin compiler does not allow distinguishing between the automatic name and a @SerialName annotation. The default implementation supposes that if there is a '.' character in the name, this is a java type name and it strips the package out. (This also when it could be an attribute).

If you need to support names with dots in your format, either use the @XmlSerialName annotation, or use a different policy.

Annotations

AnnotationPropertyDescription
@XmlSerialNameSpecify more detailed name information than can be provided by kotlinx.serialization.SerialName. In particular, it is not reliably possible to distinguish between @SerialName and the type name. We also need to specify namespace and prefix information.
value: StringThe local part of the name
namespace: StringThe namespace to use
val prefix: StringThe prefix to use
@XmlPolyChildrenMostly legacy annotation that allows specifying valid child tags for polymorphic resolution.
value: Array<String>Each string specifies a child according to the following format: childSerialName[=[prefix:]localName]. The childSerialName is the name value of the descriptor. By default that would be the class name, but @SerialName will change that. If the name is prefixed with a . the package name of the container will be prefixed. Prefix is the namespace prefix to use (the namespace will be looked up based upon this). Localname allows to specify the local name of the tag.
@XmlChildrenNameUsed in lists. This causes the children to be serialized as separate tags in an outer tag. The outer tag name is determined regularly.
@XmlElementForce a property to be either serialized as tag or attribute.
value: Booleantrue to indicate serialization as tag, false to indicate serialization as attribute. Note that not all values can be serialized as attribute
@XmlValueForce a property to be element content. Note that only one field can be element content and tags would not be expected. When applied on CompactFragment this is treated specially.
@XmlDefaultOlder versions of the framework do not support default values. This annotation allows a default value to be specified. The default value will not be written out if matched.
value: StringThe default value used if no value is specified. The value is parsed as if there was textual substitution of this value into the serialized XML.
@XmlBeforeAnnotation to support serialization order.
value: Array<String>All the children that should be serialized after this one (uses the @SerialName value or field name)
@XmlAfterAnnotation to support serialization order.
value: Array<String>All the children that should be serialized before this one (uses the @SerialName value or field name)
@XmlCDataForce serialization as CData.
@XmlMixedWhen specified on a polymorphic list it will store mixed content (like html) where text is done as Strings.
@XmlOtherAttributesCan be specified on a Map<QName, String> to store unsupported attributes.

Special types

These types have contextual support by default (without needed user intervention), but the serializer can also be specified explicitly by the user. They get special treatment to support their features.

QName

By default (configurable by the policy) QName is handled by special logic that stores QNames in a prefix:localName manner ensuring the prefix is valid in the tag. Many XML standards use this approach for string attributes.

CompactFragment

The CompactFragment class is a special class (with supporting serializer) that will be able to capture the tag soup content of an element. Instead of using regular serialization its custom serializer will (in the case of xml serialization) directly read all the child content of the tag and store it as string content. It will also make a best effort attempt at retaining all namespace declarations necessary to understand this tag soup.

Alternatively the serialutil subproject contains the nl.adaptivity.serialutil.MixedContent type that allows for typesafe serialization/deserialization of mixed content with the proviso that the serialModule must use Any as the baseclass for the content.

Modules

core

Container for the core library (versions)

core.common

All code shared between JavaScript and Java (either jvm or android)

core.common-nonshared

All code that is common, but not shared between Jvm and Android platforms

core.android

Code specific to the Android platform (Pulls in core.java as API dependency). This is a regular jar rather than an AAR as the only specific thing to Android is the XML library

core.java

Implementation of the shared code for Java based platforms (both Android and JVM)

core.js

JavaScript based implementation

core.jvm

Code unique to the JVM platform (Pulls in core.java as API dependency)

Serialization

The kotlinx.serialization plugin to allow serialization to XML

Serialization.java

The java version of the serialization plugin. Please note that it does not pull in the platform specific library. The core library is dependent on the actual platform used (JVM or Android). This library only pulls in the shared Java code.

Serialization.jvm

The JVM version merely uses the jvm platform xml library but the serialization is

Serialization.android

Serialization.js

The JavaScript version of the serialization plugin.

Serialization.test-android

An android test project to test serialization on Android.