XmlUtil

repository·master·Indexed 19 days ago

https://github.com/pdvrieze/xmlutil

A multiplatform Kotlin library for XML serialization and parsing designed for the kotlinx.serialization ecosystem. It provides a platform-independent core with optional platform-specific implementations for JVM (STAX), Android (XmlPullParser), and JS (DOM). The library includes a serialization module for automatic object mapping, various format presets (recommended, compact, fast), and integration with kotlinx.io.

Tokens
18.5K
Snippets
26
Records
50
Agent score
68%

What's inside xmlutil

  1. Overview of XmlUtil

    master
    XmlUtil is a multiplatform Kotlin library for XML serialization and wrapping. It is designed to be compatible with kotlinx.serialization, allowing for automatic object serialization using Kotlin's standard serialization plugin. It supports all Kotlin platforms, though Native support is currently at beta quality.
  2. Integrate xmlutil.core with kotlinx.io

    master
    The nl.adaptivity.xmlutil.core.kxio package provides core generic functions designed to integrate the xmlutil.core library with kotlinx.io. Use this module when you need to perform XML operations using kotlinx.io source or sink abstractions.
  3. Use XML serialization with kotlinx.serialization

    master
    The serialization module of xmlutil provides support for XML serialization by leveraging the kotlinx.serialization framework. This allows you to define your data structures using standard @Serializable annotations and convert them to and from XML formats.
  4. Understand the XmlUtil module structure

    master

    XmlUtil is organized into several modules depending on your platform and serialization needs:

    ModuleDescription
    coreThe core library containing platform-independent XML functionality.
    core-ioProvides kotlinx.io bindings for the core XML library.
    core-jdkProvides access to JDK-specific types (STaX) and the platform parser. Use this only if you require integration with JDK STaX.
    serializationThe kotlinx.serialization format implementation for XML.
    serialization-ioShortcut bindings providing direct access to kotlinx.io based streams (wraps core-io).
    serialutilAn auxiliary library providing utility functions for serialization (not XML-specific).
    core-androidDeprecated. Android-specific parsing; generic implementations are now preferred.
  5. Use the xmlserializable module for type-based serialization

    master

    The xmlserializable module provides mechanisms for type-based serialization and factory-based deserialization. This module is primarily intended for legacy purposes and allows for automatic serialization/deserialization based on interfaces.

    Note: This module is distinct from kotlinx.serialization and is not related to it.

  6. Explore available utility packages

    master

    The library is organized into several packages to facilitate different tasks:

    • nl.adaptivity.xmlutil: The core package for XML pull parsing access (via XmlStreaming).
    • nl.adaptivity.js.util: Contains extension functions designed to simplify working with DOM nodes.
    • nl.adaptivity.xmlutil.util: Contains various utility types intended to make the library more convenient to use.
  7. Create a DynamicTagWriter to produce dynamic XML tags

    master

    A DynamicTagWriter is a custom XmlDelegatingWriter used during serialization to transform static tag names into dynamic ones (e.g., converting <TestElement> with id=1 into <Test_1>).

    Key Responsibilities

    • Tag Renaming: Overrides startTag and endTag to write the dynamic string (e.g., "Test_" + idValue) instead of the standard tag name.
    • Attribute Filtering: Can be used to suppress specific attributes that are being moved from the element body into the tag name itself (e.g., ignoring the id attribute during the attribute() call so it doesn't appear twice).
    internal class DynamicTagWriter(private val writer: XmlWriter, descriptor: XmlDescriptor, private val idValue: String) :
        XmlDelegatingWriter(writer) {
        
        override fun startTag(namespace: String?, localName: String, prefix: String?) {
            when (filterDepth) {
                0 -> super.startTag("", "Test_$idValue", "")
                else -> super.startTag(namespace, localName, prefix)
            }
        }
    
        override fun endTag(namespace: String?, localName: String, prefix: String?) {
            when (filterDepth) {
                1 -> super.endTag("", "Test_$idValue", "")
                else -> super.endTag(namespace, localName, prefix)
            }
        }
    }
  8. Implement SOAP Envelopes using @XmlSerialName

    master

    To implement a SOAP-compliant structure, use the @XmlSerialName annotation to define the namespace and the preferred prefix for the Envelope and Body elements.

    In the provided pattern, the Envelope class acts as a generic wrapper. It uses a private Body class to ensure the SOAP standard requirement of a single wrapping element is met. The Envelope provides a public constructor that accepts the payload directly, hiding the internal Body implementation.

    Key annotations:

    • @XmlSerialName(name, namespace, prefix): Used on the Envelope class to set the SOAP envelope namespace (e.g., http://schemas.xmlsoap.org/soap/envelope/) and prefix (e.g., S).
    • @Polymorphic: Used within the Body class to allow the content of the SOAP message to vary.
    @Serializable
    @XmlSerialName("Envelope", "http://schemas.xmlsoap.org/soap/envelope/", "S")
    class Envelope<BODYTYPE> private constructor(
        private val body: Body<BODYTYPE>
    ) {
        constructor(data: BODYTYPE) : this(Body(data))
        val data: BODYTYPE get() = body.data
    
        @Serializable
        private data class Body<BODYTYPE>(@Polymorphic val data: BODYTYPE)
    }
  9. Customize XML serialization with XmlSerializationPolicy

    master

    The XmlSerializationPolicy defines how Kotlin/Java types are mapped to XML structures (tags, attributes, text, or mixed content). While the default policy provides a 'best attempt' at structuring XML, you can implement or replace it to control:

    • Storage Type: How a field is represented (Element, Attribute, Text, or Mixed).
    • Naming: How tag and attribute names are derived from annotations or type names.
    • Polymorphism: How polymorphic types are handled (e.g., using transparent polymorphism where the tag name determines the type).

    Commonly used customization points in the policy include effectiveOutputKind, effectiveName, and polymorphicDiscriminatorName.

  10. Handle Namespaces and Prefixes for Message Payloads

    master

    When defining the actual message content (the payload inside the SOAP Body), you can control namespaces at different levels:

    1. Payload Namespace: Use @XmlSerialName on your result class (e.g., GeResult) to define its specific namespace and prefix (e.g., ns2).
    2. Empty Namespaces for Primitives: If a property (like a code field) should reside in the empty/default namespace rather than inheriting the parent's namespace, use @XmlSerialName with empty strings for the namespace and prefix, and pair it with @XmlElement(true) to ensure it is serialized as an element.
    3. Data Payloads: For nested data objects, you can specify their names and namespaces using @XmlSerialName to ensure they match the expected XML schema.
    @Serializable
    @XmlSerialName("Ge", "http://www.gxtlink.com/webservice/", "ns2")
    data class GeResult<out T>(
        @XmlSerialName("code", "", "")
        @XmlElement(true)
        val code: Int,
        val data: T
    )
    
    @Serializable
    @XmlSerialName("data", "", "")
    data class GeResultData(
        @XmlElement(true)
        val project: String,
        @XmlElement(true)
        val unit: String
    )
  11. Create a DynamicTagReader to normalize dynamic XML tags

    master

    A DynamicTagReader is a custom XmlDelegatingReader used during deserialization to transform dynamic tag names (like Test_123) into a static, structured format that the serialization framework understands.

    Key Responsibilities

    • Tag Normalization: Overrides localName, namespaceURI, and prefix at the target depth to return the expected static name from the XmlDescriptor instead of the dynamic name found in the XML.
    • Attribute Injection: Synthetically injects attributes (like an id attribute) into the stream. For example, it can intercept the dynamic part of the tag name (the 123 in Test_123) and present it as a standard attribute value.
    • Depth Awareness: Uses a filterDepth calculation to ensure transformations only apply to the specific level of the XML hierarchy being handled, preventing accidental corruption of parent or child elements.
    internal class DynamicTagReader(reader: XmlReader, descriptor: XmlDescriptor) : XmlDelegatingReader(reader) {
        // ... implementation details for normalizing localName, namespaceURI, and injecting attributes
        override val localName: String
            get() = when (filterDepth) {
                0 -> elementName.localPart
                else -> super.localName
            }
    }