Skip to content

Commit f7e3b5f

Browse files
committed
Fix #291, handling of entity references in Node/DocumentFragments
1 parent 2adae8d commit f7e3b5f

File tree

7 files changed

+186
-48
lines changed

7 files changed

+186
-48
lines changed

Changelog.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ Changes:
1919
Fixes:
2020
- Fix trimming of strings inside a mixed context where there is an
2121
`@XmlIgnoreSpace(true)` annotation.
22+
- Fix handling of entity references in handling generic value holders
23+
(node, compactFragment). This addresses bug #291 with two separate,
24+
but equivalent errors.
2225

2326
# 0.91.1
2427
*(May 15, 2025)

core/base/src/commonMain/kotlin/nl/adaptivity/xmlutil/DomWriter.kt

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,21 @@
11
/*
2-
* Copyright (c) 2024.
2+
* Copyright (c) 2024-2025.
33
*
44
* This file is part of xmlutil.
55
*
6-
* This file is licenced to you under the Apache License, Version 2.0 (the
7-
* "License"); you may not use this file except in compliance
8-
* with the License. You should have received a copy of the license with the source distribution.
9-
* Alternatively, you may obtain a copy of the License at
6+
* This file is licenced to you under the Apache License, Version 2.0
7+
* (the "License"); you may not use this file except in compliance
8+
* with the License. You should have received a copy of the license
9+
* with the source distribution. Alternatively, you may obtain a copy
10+
* of the License at
1011
*
1112
* http://www.apache.org/licenses/LICENSE-2.0
1213
*
13-
* Unless required by applicable law or agreed to in writing,
14-
* software distributed under the License is distributed on an
15-
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16-
* KIND, either express or implied. See the License for the
17-
* specific language governing permissions and limitations
18-
* under the License.
14+
* Unless required by applicable law or agreed to in writing, software
15+
* distributed under the License is distributed on an "AS IS" BASIS,
16+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
17+
* implied. See the License for the specific language governing
18+
* permissions and limitations under the License.
1919
*/
2020

2121
@file:Suppress("DEPRECATION")
@@ -237,7 +237,16 @@ public class DomWriter @Deprecated("Don't use directly. Instead create an instan
237237

238238
override fun entityRef(text: String) {
239239
lastTagDepth = TAG_DEPTH_NOT_TAG
240-
throw UnsupportedOperationException("Creating entity references is not supported (or incorrect) in most browsers")
240+
when (text) { // handle the built in entities as regular text (which will escape where needed)
241+
"amp" -> text("&")
242+
"quot" -> text("\"")
243+
"lt" -> text("<")
244+
"gt" -> text(">")
245+
"apos" -> text("'")
246+
247+
else ->
248+
throw UnsupportedOperationException("Creating entity references is not supported (or incorrect) in most browsers: $text")
249+
}
241250
}
242251

243252
override fun processingInstruction(text: String) {

core/base/src/commonMain/kotlin/nl/adaptivity/xmlutil/XmlReaderNS.kt

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,21 @@
11
/*
2-
* Copyright (c) 2024.
2+
* Copyright (c) 2024-2025.
33
*
44
* This file is part of xmlutil.
55
*
6-
* This file is licenced to you under the Apache License, Version 2.0 (the
7-
* "License"); you may not use this file except in compliance
8-
* with the License. You should have received a copy of the license with the source distribution.
9-
* Alternatively, you may obtain a copy of the License at
6+
* This file is licenced to you under the Apache License, Version 2.0
7+
* (the "License"); you may not use this file except in compliance
8+
* with the License. You should have received a copy of the license
9+
* with the source distribution. Alternatively, you may obtain a copy
10+
* of the License at
1011
*
1112
* http://www.apache.org/licenses/LICENSE-2.0
1213
*
13-
* Unless required by applicable law or agreed to in writing,
14-
* software distributed under the License is distributed on an
15-
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16-
* KIND, either express or implied. See the License for the
17-
* specific language governing permissions and limitations
18-
* under the License.
14+
* Unless required by applicable law or agreed to in writing, software
15+
* distributed under the License is distributed on an "AS IS" BASIS,
16+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
17+
* implied. See the License for the specific language governing
18+
* permissions and limitations under the License.
1919
*/
2020

2121

@@ -83,12 +83,13 @@ public fun XmlReader.siblingsToFragment(): CompactFragment {
8383
EventType.IGNORABLE_WHITESPACE ->
8484
if (text.isNotEmpty()) appendable.append(text.xmlEncode())
8585

86+
EventType.ENTITY_REF,
8687
EventType.TEXT,
8788
EventType.CDSECT ->
8889
appendable.append(text.xmlEncode())
8990

9091
else -> {
91-
} // ignore
92+
} // ignore, note this also removes any xml comments
9293
}
9394
type = if (hasNext()) next() else break
9495
}

core/base/src/commonMain/kotlin/nl/adaptivity/xmlutil/XmlWriter.kt

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,21 @@
11
/*
2-
* Copyright (c) 2024.
2+
* Copyright (c) 2024-2025.
33
*
44
* This file is part of xmlutil.
55
*
6-
* This file is licenced to you under the Apache License, Version 2.0 (the
7-
* "License"); you may not use this file except in compliance
8-
* with the License. You should have received a copy of the license with the source distribution.
9-
* Alternatively, you may obtain a copy of the License at
6+
* This file is licenced to you under the Apache License, Version 2.0
7+
* (the "License"); you may not use this file except in compliance
8+
* with the License. You should have received a copy of the license
9+
* with the source distribution. Alternatively, you may obtain a copy
10+
* of the License at
1011
*
1112
* http://www.apache.org/licenses/LICENSE-2.0
1213
*
13-
* Unless required by applicable law or agreed to in writing,
14-
* software distributed under the License is distributed on an
15-
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16-
* KIND, either express or implied. See the License for the
17-
* specific language governing permissions and limitations
18-
* under the License.
14+
* Unless required by applicable law or agreed to in writing, software
15+
* distributed under the License is distributed on an "AS IS" BASIS,
16+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
17+
* implied. See the License for the specific language governing
18+
* permissions and limitations under the License.
1919
*/
2020
@file:JvmMultifileClass
2121
@file:JvmName("XmlWriterUtil")
@@ -326,7 +326,7 @@ public fun XmlWriter.writeCurrentEvent(reader: XmlReader) {
326326

327327
EventType.END_DOCUMENT -> endDocument()
328328

329-
EventType.ENTITY_REF -> entityRef(reader.text)
329+
EventType.ENTITY_REF -> entityRef(reader.localName)
330330

331331
EventType.IGNORABLE_WHITESPACE -> ignorableWhitespace(reader.text)
332332

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,21 @@
11
/*
2-
* Copyright (c) 2024.
2+
* Copyright (c) 2024-2025.
33
*
44
* This file is part of xmlutil.
55
*
6-
* This file is licenced to you under the Apache License, Version 2.0 (the
7-
* "License"); you may not use this file except in compliance
8-
* with the License. You should have received a copy of the license with the source distribution.
9-
* Alternatively, you may obtain a copy of the License at
6+
* This file is licenced to you under the Apache License, Version 2.0
7+
* (the "License"); you may not use this file except in compliance
8+
* with the License. You should have received a copy of the license
9+
* with the source distribution. Alternatively, you may obtain a copy
10+
* of the License at
1011
*
1112
* http://www.apache.org/licenses/LICENSE-2.0
1213
*
13-
* Unless required by applicable law or agreed to in writing,
14-
* software distributed under the License is distributed on an
15-
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16-
* KIND, either express or implied. See the License for the
17-
* specific language governing permissions and limitations
18-
* under the License.
14+
* Unless required by applicable law or agreed to in writing, software
15+
* distributed under the License is distributed on an "AS IS" BASIS,
16+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
17+
* implied. See the License for the specific language governing
18+
* permissions and limitations under the License.
1919
*/
2020

2121
package nl.adaptivity.xmlutil.core.impl.dom
@@ -34,4 +34,7 @@ internal open class TextImpl(delegate: Text) : CharacterDataImpl<Text>(delegate)
3434
override fun replaceWholeText(content: String): IText =
3535
delegate.replaceWholeText(content).wrap()
3636

37+
override fun toString(): String {
38+
return delegate.toString()
39+
}
3740
}

serialization/src/commonMain/kotlin/nl/adaptivity/xmlutil/serialization/XML.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -787,8 +787,11 @@ public class XML(
787787

788788
public companion object : StringFormat {
789789

790+
@Suppress("DEPRECATION")
790791
public val defaultInstance: XML = XML {
791-
policy = DefaultXmlSerializationPolicy(try { defaultSharedFormatCache() } catch(e: Error) { FormatCache.Dummy }) {}
792+
policy = DefaultXmlSerializationPolicy(
793+
runCatching { defaultSharedFormatCache() }.getOrElse { FormatCache.Dummy }
794+
) {}
792795
}
793796
override val serializersModule: SerializersModule
794797
get() = defaultInstance.serializersModule
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
/*
2+
* Copyright (c) 2025.
3+
*
4+
* This file is part of xmlutil.
5+
*
6+
* This file is licenced to you under the Apache License, Version 2.0
7+
* (the "License"); you may not use this file except in compliance
8+
* with the License. You should have received a copy of the license
9+
* with the source distribution. Alternatively, you may obtain a copy
10+
* of the License at
11+
*
12+
* http://www.apache.org/licenses/LICENSE-2.0
13+
*
14+
* Unless required by applicable law or agreed to in writing, software
15+
* distributed under the License is distributed on an "AS IS" BASIS,
16+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
17+
* implied. See the License for the specific language governing
18+
* permissions and limitations under the License.
19+
*/
20+
21+
package nl.adaptivity.xml.serialization.regressions
22+
23+
import io.github.pdvrieze.xmlutil.testutil.assertXmlEquals
24+
import kotlinx.serialization.SerialName
25+
import kotlinx.serialization.Serializable
26+
import kotlinx.serialization.decodeFromString
27+
import kotlinx.serialization.encodeToString
28+
import nl.adaptivity.xmlutil.dom2.Node
29+
import nl.adaptivity.xmlutil.dom2.Text
30+
import nl.adaptivity.xmlutil.dom2.data
31+
import nl.adaptivity.xmlutil.serialization.XML
32+
import nl.adaptivity.xmlutil.serialization.XmlValue
33+
import nl.adaptivity.xmlutil.util.CompactFragment
34+
import nl.adaptivity.xmlutil.xmlStreaming
35+
import kotlin.test.Test
36+
import kotlin.test.assertEquals
37+
import kotlin.test.assertIs
38+
39+
class EntityInNode291 {
40+
val expectedXml = "<Tag>&amp;Content</Tag>"
41+
42+
val xml = XML {
43+
recommended_0_90_2()
44+
defaultToGenericParser = true
45+
}
46+
47+
@SerialName("Tag")
48+
@Serializable
49+
data class Tag(@XmlValue val content: String)
50+
51+
@SerialName("Tag")
52+
@Serializable
53+
data class NodeTag(@XmlValue val content: List<Node>)
54+
55+
@SerialName("Tag")
56+
@Serializable
57+
data class CFTag(@XmlValue val content: CompactFragment)
58+
59+
@SerialName("Tag")
60+
@Serializable
61+
data class CFListTag(@XmlValue val content: List<CompactFragment>)
62+
63+
@Test
64+
fun assertSerializeTag() {
65+
val data = Tag("&Content")
66+
assertXmlEquals(expectedXml, xml.encodeToString(data))
67+
}
68+
69+
@Test
70+
fun assertDeserializeTag() {
71+
val expected = Tag("&Content")
72+
assertEquals(expected, xml.decodeFromString(expectedXml))
73+
}
74+
75+
@Test
76+
fun assertSerializeNodeTag() {
77+
val document = xmlStreaming.genericDomImplementation.createDocument()
78+
val data = NodeTag(listOf(document.createTextNode("&Content")))
79+
assertXmlEquals(expectedXml, xml.encodeToString(data))
80+
}
81+
82+
@Test
83+
fun assertDeserializeNodeTag() {
84+
val actual = xml.decodeFromString<NodeTag>(expectedXml)
85+
assertEquals(2, actual.content.size, "Unexpected content: ${actual.content.joinToString()}")
86+
val textNode1 = assertIs<Text>(actual.content[0])
87+
val textNode2 = assertIs<Text>(actual.content[1])
88+
assertEquals("&", textNode1.data)
89+
assertEquals("Content", textNode2.data)
90+
}
91+
92+
@Test
93+
fun assertSerializeCFTag() {
94+
val data = CFTag(CompactFragment("&amp;Content"))
95+
assertXmlEquals(expectedXml, xml.encodeToString(data))
96+
}
97+
98+
@Test
99+
fun assertDeserializeCFTag() {
100+
val actual = xml.decodeFromString<CFTag>(expectedXml)
101+
assertEquals("&amp;Content", actual.content.contentString)
102+
}
103+
104+
@Test
105+
fun assertSerializeCFListTag() {
106+
val data = CFListTag(listOf(CompactFragment("&amp;Content")))
107+
assertXmlEquals(expectedXml, xml.encodeToString(data))
108+
}
109+
110+
@Test
111+
fun assertDeserializeCFListTag() {
112+
val actual = xml.decodeFromString<CFListTag>(expectedXml)
113+
assertEquals(2, actual.content.size, "Unexpected content: ${actual.content.joinToString()}")
114+
val textNode1 = actual.content[0]
115+
val textNode2 = actual.content[1]
116+
assertEquals("&", textNode1.contentString)
117+
assertEquals("Content", textNode2.contentString)
118+
}
119+
}

0 commit comments

Comments
 (0)