diff --git a/Changelog.md b/Changelog.md index 88f492292..8969b24a6 100644 --- a/Changelog.md +++ b/Changelog.md @@ -2,6 +2,8 @@ Features: Fixes: +- When attributes have an `@XmlSerialName` annotation with a default namespace + value, then this will result in a non-qualified attribute. # 0.86.1 *(July 5, 2023)
* diff --git a/serialization/api/android/serialization.api b/serialization/api/android/serialization.api index a0cfb4248..9620c24e7 100644 --- a/serialization/api/android/serialization.api +++ b/serialization/api/android/serialization.api @@ -569,15 +569,17 @@ public final class nl/adaptivity/xmlutil/serialization/XmlSerializationPolicy$Co } public final class nl/adaptivity/xmlutil/serialization/XmlSerializationPolicy$DeclaredNameInfo { - public fun (Ljava/lang/String;Ljavax/xml/namespace/QName;)V + public fun (Ljava/lang/String;Ljavax/xml/namespace/QName;Z)V public final fun component1 ()Ljava/lang/String; public final fun component2 ()Ljavax/xml/namespace/QName; - public final fun copy (Ljava/lang/String;Ljavax/xml/namespace/QName;)Lnl/adaptivity/xmlutil/serialization/XmlSerializationPolicy$DeclaredNameInfo; - public static synthetic fun copy$default (Lnl/adaptivity/xmlutil/serialization/XmlSerializationPolicy$DeclaredNameInfo;Ljava/lang/String;Ljavax/xml/namespace/QName;ILjava/lang/Object;)Lnl/adaptivity/xmlutil/serialization/XmlSerializationPolicy$DeclaredNameInfo; + public final fun component3 ()Z + public final fun copy (Ljava/lang/String;Ljavax/xml/namespace/QName;Z)Lnl/adaptivity/xmlutil/serialization/XmlSerializationPolicy$DeclaredNameInfo; + public static synthetic fun copy$default (Lnl/adaptivity/xmlutil/serialization/XmlSerializationPolicy$DeclaredNameInfo;Ljava/lang/String;Ljavax/xml/namespace/QName;ZILjava/lang/Object;)Lnl/adaptivity/xmlutil/serialization/XmlSerializationPolicy$DeclaredNameInfo; public fun equals (Ljava/lang/Object;)Z public final fun getAnnotatedName ()Ljavax/xml/namespace/QName; public final fun getSerialName ()Ljava/lang/String; public fun hashCode ()I + public final fun isDefaultNamespace ()Z public fun toString ()Ljava/lang/String; } diff --git a/serialization/api/jvm/serialization.api b/serialization/api/jvm/serialization.api index a0cfb4248..9620c24e7 100644 --- a/serialization/api/jvm/serialization.api +++ b/serialization/api/jvm/serialization.api @@ -569,15 +569,17 @@ public final class nl/adaptivity/xmlutil/serialization/XmlSerializationPolicy$Co } public final class nl/adaptivity/xmlutil/serialization/XmlSerializationPolicy$DeclaredNameInfo { - public fun (Ljava/lang/String;Ljavax/xml/namespace/QName;)V + public fun (Ljava/lang/String;Ljavax/xml/namespace/QName;Z)V public final fun component1 ()Ljava/lang/String; public final fun component2 ()Ljavax/xml/namespace/QName; - public final fun copy (Ljava/lang/String;Ljavax/xml/namespace/QName;)Lnl/adaptivity/xmlutil/serialization/XmlSerializationPolicy$DeclaredNameInfo; - public static synthetic fun copy$default (Lnl/adaptivity/xmlutil/serialization/XmlSerializationPolicy$DeclaredNameInfo;Ljava/lang/String;Ljavax/xml/namespace/QName;ILjava/lang/Object;)Lnl/adaptivity/xmlutil/serialization/XmlSerializationPolicy$DeclaredNameInfo; + public final fun component3 ()Z + public final fun copy (Ljava/lang/String;Ljavax/xml/namespace/QName;Z)Lnl/adaptivity/xmlutil/serialization/XmlSerializationPolicy$DeclaredNameInfo; + public static synthetic fun copy$default (Lnl/adaptivity/xmlutil/serialization/XmlSerializationPolicy$DeclaredNameInfo;Ljava/lang/String;Ljavax/xml/namespace/QName;ZILjava/lang/Object;)Lnl/adaptivity/xmlutil/serialization/XmlSerializationPolicy$DeclaredNameInfo; public fun equals (Ljava/lang/Object;)Z public final fun getAnnotatedName ()Ljavax/xml/namespace/QName; public final fun getSerialName ()Ljava/lang/String; public fun hashCode ()I + public final fun isDefaultNamespace ()Z public fun toString ()Ljava/lang/String; } diff --git a/serialization/src/commonMain/kotlin/nl/adaptivity/xmlutil/serialization/XML.kt b/serialization/src/commonMain/kotlin/nl/adaptivity/xmlutil/serialization/XML.kt index 367ba62b4..cc952f18b 100644 --- a/serialization/src/commonMain/kotlin/nl/adaptivity/xmlutil/serialization/XML.kt +++ b/serialization/src/commonMain/kotlin/nl/adaptivity/xmlutil/serialization/XML.kt @@ -33,6 +33,7 @@ import nl.adaptivity.xmlutil.core.impl.multiplatform.Language 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.impl.ChildCollector import nl.adaptivity.xmlutil.serialization.impl.NamespaceCollectingXmlWriter @@ -162,7 +163,7 @@ public class XML constructor( target.indentString = config.indentString if (prefix != null) { - val root = XmlRootDescriptor(config, serializersModule, serializer.descriptor, null) + val root = XmlRootDescriptor(config, serializersModule, serializer.descriptor) val serialQName = root.getElementDescriptor(0).tagName.copy(prefix = prefix) @@ -206,7 +207,7 @@ public class XML constructor( } } - val root = XmlRootDescriptor(config, serializersModule, serializer.descriptor, rootName) + val root = XmlRootDescriptor(config, serializersModule, serializer.descriptor, rootName, false) val xmlDescriptor = root.getElementDescriptor(0) @@ -220,7 +221,7 @@ public class XML constructor( }) val remappedEncoderBase = XmlEncoderBase(serializersModule, newConfig, target) val newRootName = rootName?.remapPrefix(prefixMap) - val newRoot = XmlRootDescriptor(newConfig, serializersModule, serializer.descriptor, newRootName) + val newRoot = XmlRootDescriptor(newConfig, serializersModule, serializer.descriptor, newRootName, false) val newDescriptor = newRoot.getElementDescriptor(0) @@ -398,7 +399,7 @@ public class XML constructor( reader.skipPreamble() val xmlDecoderBase = XmlDecoderBase(serializersModule, config, reader) - val rootDescriptor = XmlRootDescriptor(config, serializersModule, deserializer.descriptor, serialName) + val rootDescriptor = XmlRootDescriptor(config, serializersModule, deserializer.descriptor, serialName, false) val elementDescriptor = rootDescriptor.getElementDescriptor(0) val polyInfo = (elementDescriptor as? XmlPolymorphicDescriptor)?.run { @@ -434,11 +435,11 @@ public class XML constructor( ?: serialDescriptor.annotations.firstOrNull() ?.toQName(serialDescriptor.serialName, null) ?: config.policy.serialTypeNameToQName( - XmlSerializationPolicy.DeclaredNameInfo(serialDescriptor.serialName, null), + DeclaredNameInfo(serialDescriptor.serialName), XmlEvent.NamespaceImpl(XMLConstants.DEFAULT_NS_PREFIX, XMLConstants.NULL_NS_URI) ) - return XmlRootDescriptor(config, serializersModule, serialDescriptor, serialName) + return XmlRootDescriptor(config, serializersModule, serialDescriptor, serialName, false) } @Deprecated("Use config directly", ReplaceWith("config.repairNamespaces"), DeprecationLevel.HIDDEN) diff --git a/serialization/src/commonMain/kotlin/nl/adaptivity/xmlutil/serialization/XmlSerializationPolicy.kt b/serialization/src/commonMain/kotlin/nl/adaptivity/xmlutil/serialization/XmlSerializationPolicy.kt index d99caf61c..81ca11f83 100644 --- a/serialization/src/commonMain/kotlin/nl/adaptivity/xmlutil/serialization/XmlSerializationPolicy.kt +++ b/serialization/src/commonMain/kotlin/nl/adaptivity/xmlutil/serialization/XmlSerializationPolicy.kt @@ -97,8 +97,15 @@ public interface XmlSerializationPolicy { public data class DeclaredNameInfo( val serialName: String, - val annotatedName: QName? - ) + val annotatedName: QName?, + val isDefaultNamespace: Boolean/* = false*/ + ) { + internal constructor(serialName: String) : this(serialName, null, false) + + init { + check(!(isDefaultNamespace && annotatedName == null)) { "Default namespace requires there to be an annotated name" } + } + } public data class ActualNameInfo( val serialName: String, @@ -190,13 +197,13 @@ public interface XmlSerializationPolicy { /** Determine the name of map keys for a given map type */ public fun mapKeyName(serializerParent: SafeParentInfo): DeclaredNameInfo = - DeclaredNameInfo("key", null) // minimal default for implementations. + DeclaredNameInfo("key") // minimal default for implementations. /** * Determine the name of the values for a given map type */ public fun mapValueName(serializerParent: SafeParentInfo, isListEluded: Boolean): DeclaredNameInfo = - DeclaredNameInfo("value", null) // minimal default for implementations. + DeclaredNameInfo("value") // minimal default for implementations. /** * Determine the name to use for the map element (only used when a map entry is wrapped) @@ -222,7 +229,8 @@ public interface XmlSerializationPolicy { * first element. */ @ExperimentalXmlUtilApi - public fun attributeListDelimiters(serializerParent: SafeParentInfo, tagParent: SafeParentInfo): Array = arrayOf(" ", "\n", "\t", "\r") + public fun attributeListDelimiters(serializerParent: SafeParentInfo, tagParent: SafeParentInfo): Array = + arrayOf(" ", "\n", "\t", "\r") public enum class XmlEncodeDefault { ALWAYS, ANNOTATED, NEVER @@ -236,13 +244,18 @@ public interface XmlSerializationPolicy { * correct child). */ @ExperimentalXmlUtilApi - public fun recoverNullNamespaceUse(inputKind: InputKind, descriptor: XmlDescriptor, name: QName?): List>? { + public fun recoverNullNamespaceUse( + inputKind: InputKind, + descriptor: XmlDescriptor, + name: QName? + ): List>? { if (name != null) { if (name.namespaceURI == "") { for (idx in 0 until descriptor.elementsCount) { val candidate = descriptor.getElementDescriptor(idx) if (inputKind.mapsTo(candidate.effectiveOutputKind) && - candidate.tagName.localPart == name.getLocalPart()) { + candidate.tagName.localPart == name.getLocalPart() + ) { return listOf(XML.ParsedData(idx, Unit, true)) } } @@ -250,7 +263,8 @@ public interface XmlSerializationPolicy { for (idx in 0 until descriptor.elementsCount) { val candidate = descriptor.getElementDescriptor(idx) if (inputKind.mapsTo(candidate.effectiveOutputKind) && - candidate.tagName.isEquivalent(QName(name.localPart))) { + candidate.tagName.isEquivalent(QName(name.localPart)) + ) { return listOf(XML.ParsedData(idx, Unit, true)) } } @@ -495,9 +509,14 @@ public open class DefaultXmlSerializationPolicy val parentSerialKind = tagParent.descriptor?.serialKind return when { + outputKind == OutputKind.Attribute -> when { + !useName.isDefaultNamespace -> useName.annotatedName!! // invariant enforced by type + useName.annotatedName != null -> QName(useName.annotatedName.getLocalPart()) // use unqualified attribute name + else -> QName(useName.serialName) + } // Use non-prefix attributes by default + useName.annotatedName != null -> useName.annotatedName - outputKind == OutputKind.Attribute -> QName(useName.serialName) // Use non-prefix attributes by default serialKind is PrimitiveKind || serialKind == StructureKind.MAP || @@ -652,12 +671,13 @@ public open class DefaultXmlSerializationPolicy } override fun mapKeyName(serializerParent: SafeParentInfo): DeclaredNameInfo { - return DeclaredNameInfo("key", null) + return DeclaredNameInfo("key") } override fun mapValueName(serializerParent: SafeParentInfo, isListEluded: Boolean): DeclaredNameInfo { - val childrenName = serializerParent.elementUseAnnotations.firstOrNull()?.toQName() - return DeclaredNameInfo("value", childrenName) + val childAnnotation = serializerParent.elementUseAnnotations.firstOrNull() + val childrenName = childAnnotation?.toQName() + return DeclaredNameInfo("value", childrenName, childAnnotation?.namespace == UNSET_ANNOTATION_VALUE) } override fun mapEntryName(serializerParent: SafeParentInfo, isListEluded: Boolean): QName { diff --git a/serialization/src/commonMain/kotlin/nl/adaptivity/xmlutil/serialization/structure/XmlDescriptor.kt b/serialization/src/commonMain/kotlin/nl/adaptivity/xmlutil/serialization/structure/XmlDescriptor.kt index cedd90a34..4e4671c60 100644 --- a/serialization/src/commonMain/kotlin/nl/adaptivity/xmlutil/serialization/structure/XmlDescriptor.kt +++ b/serialization/src/commonMain/kotlin/nl/adaptivity/xmlutil/serialization/structure/XmlDescriptor.kt @@ -310,7 +310,15 @@ public class XmlRootDescriptor internal constructor( serializersModule: SerializersModule, descriptor: SerialDescriptor, tagName: QName?, -) : XmlDescriptor(config.policy, DetachedParent(descriptor, tagName, true, outputKind = null)) { + isDefaultNamespace: Boolean, +) : XmlDescriptor(config.policy, DetachedParent(descriptor, tagName, true, outputKind = null, isDefaultNamespace = isDefaultNamespace)) { + + internal constructor( +// TODO get rid of coded, put policy in its place + config: XmlConfig, + serializersModule: SerializersModule, + descriptor: SerialDescriptor, + ) : this (config, serializersModule, descriptor, null, false) private val element: XmlDescriptor by lazy { from(config, serializersModule, tagParent, canBeAttribute = false) @@ -536,9 +544,10 @@ public class XmlInlineDescriptor internal constructor( // This is needed as this descriptor is not complete yet and would use this element's // unset name for the namespace. val serialName = typeDescriptor.serialDescriptor.getElementName(0) - val qName = typeDescriptor.serialDescriptor.getElementAnnotations(0).firstOrNull() - ?.toQName(serialName, tagParent.namespace) - val childUseNameInfo = DeclaredNameInfo(serialName, qName) + val annotation = typeDescriptor.serialDescriptor.getElementAnnotations(0).firstOrNull() + val qName = annotation?.toQName(serialName, tagParent.namespace) + val childUseNameInfo = + DeclaredNameInfo(serialName, qName, annotation?.namespace == UNSET_ANNOTATION_VALUE) when { childUseNameInfo.annotatedName != null -> childUseNameInfo @@ -864,7 +873,7 @@ public class XmlPolymorphicDescriptor internal constructor( for (polyChild in xmlPolyChildren.value) { val childInfo = polyTagName(baseName, polyChild, baseClass, serializersModule) - val childSerializerParent = DetachedParent(childInfo.descriptor, childInfo.tagName, false) + val childSerializerParent = DetachedParent(childInfo.descriptor, childInfo.tagName, false, isDefaultNamespace = false) map[childInfo.describedName] = from(config, serializersModule, childSerializerParent, tagParent, canBeAttribute = false) @@ -876,7 +885,7 @@ public class XmlPolymorphicDescriptor internal constructor( val d = serialDescriptor.getElementDescriptor(1) for (i in 0 until d.elementsCount) { val childDesc = d.getElementDescriptor(i) - val childSerializerParent = DetachedParent(childDesc, qName, false) + val childSerializerParent = DetachedParent(childDesc, qName, false, isDefaultNamespace = false) map[childDesc.serialName] = from(config, serializersModule, childSerializerParent, tagParent, canBeAttribute = false) @@ -890,7 +899,7 @@ public class XmlPolymorphicDescriptor internal constructor( for (childDesc in childDescriptors) { - val childSerializerParent = DetachedParent(childDesc, qName, false, outputKind) + val childSerializerParent = DetachedParent(childDesc, qName, false, outputKind, isDefaultNamespace = false) map[childDesc.serialName] = from(config, serializersModule, childSerializerParent, tagParent, canBeAttribute = false) @@ -980,8 +989,9 @@ public class XmlPolymorphicDescriptor internal constructor( @ExperimentalSerializationApi internal fun SerialDescriptor.getElementNameInfo(index: Int, parentNamespace: Namespace?): DeclaredNameInfo { val serialName = getElementName(index) - val qName = getElementAnnotations(index).firstOrNull()?.toQName(serialName, parentNamespace) - return DeclaredNameInfo(serialName, qName) + val annotation = getElementAnnotations(index).firstOrNull() + val qName = annotation?.toQName(serialName, parentNamespace) + return DeclaredNameInfo(serialName, qName, annotation?.namespace == UNSET_ANNOTATION_VALUE) } @ExperimentalSerializationApi @@ -990,8 +1000,9 @@ internal fun SerialDescriptor.getNameInfo(parentNamespace: Namespace?): Declared isNullable && serialName.endsWith('?') -> serialName.dropLast(1) else -> capturedKClass?.serialName ?: serialName } - val qName = annotations.firstOrNull()?.toQName(realSerialName, parentNamespace) - return DeclaredNameInfo(realSerialName, qName) + val annotation = annotations.firstOrNull() + val qName = annotation?.toQName(realSerialName, parentNamespace) + return DeclaredNameInfo(realSerialName, qName, annotation?.namespace == UNSET_ANNOTATION_VALUE) } public sealed class XmlListLikeDescriptor( @@ -1111,7 +1122,7 @@ public class XmlListDescriptor internal constructor( tagParent.elementUseAnnotations.firstOrNull() != null && config.policy.isTransparentPolymorphic( - DetachedParent(serialDescriptor.getElementDescriptor(0), null, false), + DetachedParent(serialDescriptor.getElementDescriptor(0), false), tagParent ) -> OutputKind.Mixed @@ -1132,10 +1143,10 @@ public class XmlListDescriptor internal constructor( } private val childDescriptor: XmlDescriptor by lazy { - val childrenName = tagParent.elementUseAnnotations.firstOrNull()?.toQName() + val childrenNameAnnotation = tagParent.elementUseAnnotations.firstOrNull() val useNameInfo = when { - childrenName != null -> DeclaredNameInfo(childrenName.localPart, childrenName) + childrenNameAnnotation != null -> DeclaredNameInfo(childrenNameAnnotation.value, childrenNameAnnotation.toQName(), childrenNameAnnotation?.namespace == UNSET_ANNOTATION_VALUE) !isListEluded -> null // if we have a list, don't repeat the outer name (at least allow the policy to decide) @@ -1286,10 +1297,23 @@ private class DetachedParent( serialDescriptor: SerialDescriptor, useName: QName?, isDocumentRoot: Boolean, - outputKind: OutputKind? = null + outputKind: OutputKind? = null, + isDefaultNamespace: Boolean, + ) : this( + serialDescriptor, + DeclaredNameInfo(serialDescriptor.run { capturedKClass?.serialName ?: serialName }, useName, isDefaultNamespace), + isDocumentRoot, + outputKind + ) + + @OptIn(ExperimentalSerializationApi::class) + constructor( + serialDescriptor: SerialDescriptor, + isDocumentRoot: Boolean, + outputKind: OutputKind? = null, ) : this( serialDescriptor, - DeclaredNameInfo(serialDescriptor.run { capturedKClass?.serialName ?: serialName }, useName), + DeclaredNameInfo(serialDescriptor.run { capturedKClass?.serialName ?: serialName }, null, false), isDocumentRoot, outputKind ) @@ -1409,7 +1433,7 @@ public class ParentInfo( @OptIn(ExperimentalSerializationApi::class) override val elementUseNameInfo: DeclaredNameInfo = useNameInfo ?: when (index) { - -1 -> DeclaredNameInfo(descriptor.serialDescriptor.serialName, null) + -1 -> DeclaredNameInfo(descriptor.serialDescriptor.serialName) else -> descriptor.serialDescriptor.getElementNameInfo(index, descriptor.tagName.toNamespace()) } diff --git a/serialization/src/commonTest/kotlin/nl/adaptivity/xml/serialization/MapTest.kt b/serialization/src/commonTest/kotlin/nl/adaptivity/xml/serialization/MapTest.kt index a0c655330..b007af0e3 100644 --- a/serialization/src/commonTest/kotlin/nl/adaptivity/xml/serialization/MapTest.kt +++ b/serialization/src/commonTest/kotlin/nl/adaptivity/xml/serialization/MapTest.kt @@ -136,7 +136,7 @@ class MapTest : PlatformTestBase( val xml = baseXmlFormat.copy { policy = object : DefaultXmlSerializationPolicy(policy) { override fun mapKeyName(serializerParent: SafeParentInfo): XmlSerializationPolicy.DeclaredNameInfo { - return XmlSerializationPolicy.DeclaredNameInfo("name", null) + return XmlSerializationPolicy.DeclaredNameInfo("name") } } }