Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

The KtXmlReader incorrectly handles namespace and prefixes in the case where the prefix is empty #99

Closed
dannyvalentesonos opened this issue Sep 16, 2022 · 1 comment

Comments

@dannyvalentesonos
Copy link

The KtXmlReader incorrectly handles namespace and prefixes in the case where the prefix is empty. This very simple example shows the problem.

@Serializable
@XmlSerialName("ElementA", "namespace/a", "a")
data class A(
    val value:String,

    val b: B
)

@Serializable
@XmlSerialName("ElementB", "namespace/b", "")
data class B(
    val value:String,

    val c: C
)

@Serializable
@XmlSerialName("ElementC", "", "")
data class C(
    val value:String
)

fun test() {
    val xml = XML {
        xmlDeclMode = XmlDeclMode.None
        autoPolymorphic = true
        indentString = "  "
    }

    val data = A(
        "a",
        B(
            "b",
            C(
                "c"
            )
        )
    )

    val dataString = xml.encodeToString(data)
    Log.e("test", "Encoding:\n${dataString.prependIndent("    ")}\n")

    val obj = xml.decodeFromString(A.serializer(), dataString)
    Log.e("test", "Decoding: $obj")
}

When running this, dataString looks like this after encoding:

<a:ElementA xmlns:a="namespace/a" value="a">
  <ElementB xmlns:b="namespace/b" value="b">
    <ElementC value="c" />
  </ElementB>
</a:ElementA>

And when decoding, it throws this exception:

Namespace namespace/b does not match expected ""

If we change class B to look like this:

@Serializable
@XmlSerialName("ElementB", "namespace/b", "b")
data class B(
    val value:String,

    val c: C
)

then the encoded string now looks like this:

<a:ElementA xmlns:a="namespace/a" value="a">
  <b:ElementB xmlns:b="namespace/b" value="b">
    <ElementC value="c" />
  </b:ElementB>
</a:ElementA>

and decoding works properly.

The issue is in NamespaceHolders's getNamespaceUri() function:

    fun getNamespaceUri(prefix: CharSequence): String? {
        return when (val prefixStr = prefix.toString()) {
            XML_NS_PREFIX -> return XML_NS_URI
            XMLNS_ATTRIBUTE -> return XMLNS_ATTRIBUTE_NS_URI

            else -> ((totalNamespaceCount - 1) downTo 0)
                .firstOrNull { getPrefix(it) == prefixStr }
                ?.let { getNamespace(it) } ?: if (prefixStr.isEmpty()) NULL_NS_URI else null
        }
    }

It doesn't take into account if the prefix is an empty string. If it's an empty string, it should not be getting the namespace from a parent with an empty prefix, as that will always fail the requirement of having the closing tag with the same namespace, as the prefix is empty, and it will be empty on the closing tag too.

If an element has no prefix, then it will end up getting a random namespace from a parent if any parent doesn't have a prefix either. For this reason, I imagine this can't be desired behavior. Also, if we keep class B with a prefix, the issue is that if the xml string you are trying to decode doesn't provide a prefix on the ElementB tag, it still fails, and we don't have control over the xml string that could come from third parties, which is my case.

If it is strongly felt that this really is the desired behavior, then the requirement on the ending tag should be that its prefix is the same as its starting element's prefix, and not the namespace.

@pdvrieze
Copy link
Owner

There is actually an encoding error in the first example. The second element needs xmlns=... and the third element needs xmlns="" (although it is not clear that this is valid, the null namespace is only for compatibility)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants