Skip to content

Commit f9389da

Browse files
committed
Fix the dynamic tag names example. It suffered from some bitrot.
Also added a test for the code.
1 parent 941b52b commit f9389da

File tree

7 files changed

+167
-144
lines changed

7 files changed

+167
-144
lines changed

examples/DYNAMIC_TAG_NAMES.md

Lines changed: 80 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -43,22 +43,10 @@ object CompatContainerSerializer: CommonContainerSerializer() {
4343

4444
[ContainerSerializer](src/main/kotlin/net/devrieze/serialization/examples/dynamictagnames/ContainerSerializer.kt)
4545
```kotlin
46-
/**
47-
* This version of the serializer uses the new delegateFormat method on [XML.XmlInput] and [XML.XmlOutput]
48-
* to inherit configuration and serializerModules.
49-
*/
50-
object ContainerSerializer: CommonContainerSerializer() {
51-
override fun delegateFormat(decoder: Decoder) = (decoder as XML.XmlInput).delegateFormat()
52-
override fun delegateFormat(encoder: Encoder) = (encoder as XML.XmlOutput).delegateFormat()
53-
}
54-
```
55-
56-
[CommonContainerSerializer](src/main/kotlin/net/devrieze/serialization/examples/dynamictagnames/CommonContainerSerializer.kt)
57-
```kotlin
5846
/**
5947
* A common base class that contains the actual code needed to serialize/deserialize the container.
6048
*/
61-
abstract class CommonContainerSerializer: KSerializer<Container> {
49+
class ContainerSerializer : XmlSerializer<Container> {
6250
/** We need to have the serializer for the elements */
6351
private val elementSerializer = serializer<TestElement>()
6452

@@ -69,103 +57,112 @@ abstract class CommonContainerSerializer: KSerializer<Container> {
6957

7058
override fun deserialize(decoder: Decoder): Container {
7159
// XmlInput is designed as an interface to test for to allow custom serializers
72-
if (decoder is XML.XmlInput) { // We treat XML different, using a separate method for clarity
73-
return deserializeDynamic(decoder, decoder.input)
74-
} else { // Simple default decoder implementation that delegates parsing the data to the ListSerializer
75-
val data = decoder.decodeStructure(descriptor) {
76-
decodeSerializableElement(descriptor, 0, ListSerializer(elementSerializer))
77-
}
78-
return Container(data)
60+
val data = decoder.decodeStructure(descriptor) {
61+
decodeSerializableElement(descriptor, 0, ListSerializer(elementSerializer))
7962
}
63+
// Simple default decoder implementation that delegates parsing the data to the ListSerializer
64+
return Container(data)
8065
}
8166

82-
/**
83-
* This function is the meat to deserializing the container with dynamic tag names. Note that
84-
* because we use xml there is no point in going through the (anonymous) list dance. Doing that
85-
* would be an additional complication.
86-
*/
87-
fun deserializeDynamic(decoder: Decoder, reader: XmlReader): Container {
88-
val xml = delegateFormat(decoder) // get the format for deserializing
67+
override fun serialize(encoder: Encoder, value: Container) {
68+
encoder.encodeStructure(descriptor) {
69+
encodeSerializableElement(descriptor, 0, ListSerializer(elementSerializer), value.data)
70+
}
71+
}
72+
73+
override fun deserializeXML(
74+
decoder: Decoder,
75+
input: XmlReader,
76+
previousValue: Container?,
77+
isValueChild: Boolean
78+
): Container {
79+
// This delegate format allows for reusing the settings from the outer format.
80+
val xml = (decoder as XML.XmlInput).delegateFormat()
8981

9082
// We need the descriptor for the element. xmlDescriptor returns a rootDescriptor, so the actual descriptor is
9183
// its (only) child.
9284
val elementXmlDescriptor = xml.xmlDescriptor(elementSerializer).getElementDescriptor(0)
93-
9485
// A list to collect the data
9586
val dataList = mutableListOf<TestElement>()
96-
9787
decoder.decodeStructure(descriptor) {
9888
// Finding the children is actually not left to the serialization framework, but
9989
// done by "hand"
100-
while (reader.next() != EventType.END_ELEMENT) {
101-
when (reader.eventType) {
90+
while (input.next() != EventType.END_ELEMENT) {
91+
when (input.eventType) {
10292
EventType.COMMENT,
10393
EventType.IGNORABLE_WHITESPACE -> {
10494
// Comments and whitespace are just ignored
10595
}
106-
EventType.ENTITY_REF,
107-
EventType.TEXT -> if (reader.text.isNotBlank()) {
108-
// Some parsers can return whitespace as text instead of ignorable whitespace
10996

110-
// Use the handler from the configuration to throw the exception.
111-
xml.config.unknownChildHandler(reader, InputKind.Text, null, emptyList())
97+
EventType.ENTITY_REF,
98+
EventType.TEXT -> {
99+
if (input.text.isNotBlank()) {
100+
// Some parsers can return whitespace as text instead of ignorable whitespace
101+
102+
// Use the handler from the configuration to throw the exception.
103+
@OptIn(ExperimentalXmlUtilApi::class)
104+
xml.config.policy.handleUnknownContentRecovering(
105+
input,
106+
InputKind.Text,
107+
elementXmlDescriptor,
108+
null,
109+
emptyList()
110+
)
111+
}
112112
}
113113
// It's best to still check the name before parsing
114-
EventType.START_ELEMENT -> if(reader.namespaceURI.isEmpty() && reader.localName.startsWith("Test_")) {
115-
// When reading the child tag we use the DynamicTagReader to present normalized XML to the
116-
// deserializer for elements
117-
val filter = DynamicTagReader(reader, elementXmlDescriptor)
118-
119-
// The test element can now be decoded as normal (with the filter applied)
120-
val testElement = xml.decodeFromReader(elementSerializer, filter)
121-
dataList.add(testElement)
122-
} else { // handling unexpected tags
123-
xml.config.unknownChildHandler(reader, InputKind.Element, reader.name, listOf("Test_??"))
114+
EventType.START_ELEMENT -> {
115+
if (input.namespaceURI.isEmpty() && input.localName.startsWith("Test_")) {
116+
// When reading the child tag we use the DynamicTagReader to present normalized XML to the
117+
// deserializer for elements
118+
val filter = DynamicTagReader(input, elementXmlDescriptor)
119+
120+
// The test element can now be decoded as normal (with the filter applied)
121+
val testElement = xml.decodeFromReader(elementSerializer, filter)
122+
dataList.add(testElement)
123+
} else { // handling unexpected tags
124+
@OptIn(ExperimentalXmlUtilApi::class)
125+
xml.config.policy.handleUnknownContentRecovering(
126+
input,
127+
InputKind.Element,
128+
elementXmlDescriptor,
129+
input.name,
130+
(0 until elementXmlDescriptor.elementsCount).map {
131+
val e = elementXmlDescriptor.getElementDescriptor(it)
132+
PolyInfo(e.tagName, it, e)
133+
}
134+
)
135+
}
124136
}
125-
else -> { // other content that shouldn't happen
137+
138+
else -> // other content that shouldn't happen
126139
throw XmlException("Unexpected tag content")
127-
}
128140
}
129141
}
130142
}
131143
return Container(dataList)
132144
}
133145

134-
override fun serialize(encoder: Encoder, value: Container) {
135-
if (encoder is XML.XmlOutput) { // When we are using the xml format use the serializeDynamic method
136-
return serializeDynamic(encoder, encoder.target, value.data)
137-
} else { // Otherwise just manually do the encoding that would have been generated
138-
encoder.encodeStructure(descriptor) {
139-
encodeSerializableElement(descriptor, 0, ListSerializer(elementSerializer), value.data)
140-
}
141-
}
142-
}
143-
144-
/**
145-
* This function provides the actual dynamic serialization
146-
*/
147-
fun serializeDynamic(encoder: Encoder, target: XmlWriter, data: List<TestElement>) {
148-
val xml = delegateFormat(encoder) // get the format for deserializing
146+
override fun serializeXML(encoder: Encoder, output: XmlWriter, value: Container, isValueChild: Boolean) {
147+
val xml =
148+
(encoder as XML.XmlOutput).delegateFormat() // This format keeps the settings from the outer serializer, this allows for
149+
// serializing the children
149150

150151
// We need the descriptor for the element. xmlDescriptor returns a rootDescriptor, so the actual descriptor is
151152
// its (only) child.
152153
val elementXmlDescriptor = xml.xmlDescriptor(elementSerializer).getElementDescriptor(0)
153-
154154
encoder.encodeStructure(descriptor) { // create the structure (will write the tags of Container)
155-
for (element in data) { // write each element
155+
for (element in value.data) { // write each element
156156
// We need a writer that does the renaming from the normal format to the dynamic format
157157
// It is passed the string of the id to add.
158-
val writer = DynamicTagWriter(target, elementXmlDescriptor, element.id.toString())
158+
val writer = DynamicTagWriter(output, elementXmlDescriptor, element.id.toString())
159159

160160
// Normal delegate writing of the element
161161
xml.encodeToWriter(writer, elementSerializer, element)
162162
}
163163
}
164164
}
165165

166-
// These functions abstract away getting the delegate format in improved or compat way.
167-
abstract fun delegateFormat(decoder: Decoder): XML
168-
abstract fun delegateFormat(encoder: Encoder): XML
169166
}
170167
```
171168

@@ -181,13 +178,11 @@ internal class DynamicTagReader(reader: XmlReader, descriptor: XmlDescriptor) :
181178
private var initDepth = reader.depth
182179
private val filterDepth: Int
183180
/**
184-
* We want to be safe so only handle the content at relative depth 0. The way that depth is determined
185-
* means that the depth is the depth after the tag (and end tags are thus one level lower than the tag (and its
186-
* content). We correct for that here.
181+
* We want to be safe so only handle the content at relative depth 0.
187182
*/
188183
get() = when (eventType) {
189-
EventType.END_ELEMENT -> delegate.depth - initDepth + 1
190-
else -> delegate.depth - initDepth
184+
EventType.END_ELEMENT -> delegate.depth - initDepth
185+
else -> delegate.depth - initDepth
191186
}
192187

193188
/**
@@ -226,9 +221,10 @@ internal class DynamicTagReader(reader: XmlReader, descriptor: XmlDescriptor) :
226221
*/
227222
override fun getAttributeNamespace(index: Int): String = when (filterDepth) {
228223
0 -> when (index) {
229-
0 -> idAttrName.namespaceURI
224+
0 -> idAttrName.namespaceURI
230225
else -> super.getAttributeNamespace(index - 1)
231226
}
227+
232228
else -> super.getAttributeNamespace(index)
233229
}
234230

@@ -238,9 +234,10 @@ internal class DynamicTagReader(reader: XmlReader, descriptor: XmlDescriptor) :
238234
*/
239235
override fun getAttributePrefix(index: Int): String = when (filterDepth) {
240236
0 -> when (index) {
241-
0 -> idAttrName.prefix
237+
0 -> idAttrName.prefix
242238
else -> super.getAttributePrefix(index - 1)
243239
}
240+
244241
else -> super.getAttributePrefix(index)
245242
}
246243

@@ -250,9 +247,10 @@ internal class DynamicTagReader(reader: XmlReader, descriptor: XmlDescriptor) :
250247
*/
251248
override fun getAttributeLocalName(index: Int): String = when (filterDepth) {
252249
0 -> when (index) {
253-
0 -> idAttrName.localPart
250+
0 -> idAttrName.localPart
254251
else -> super.getAttributeLocalName(index - 1)
255252
}
253+
256254
else -> super.getAttributeLocalName(index)
257255
}
258256

@@ -262,9 +260,10 @@ internal class DynamicTagReader(reader: XmlReader, descriptor: XmlDescriptor) :
262260
*/
263261
override fun getAttributeValue(index: Int): String = when (filterDepth) {
264262
0 -> when (index) {
265-
0 -> idValue
263+
0 -> idValue
266264
else -> super.getAttributeValue(index - 1)
267265
}
266+
268267
else -> super.getAttributeValue(index)
269268
}
270269

@@ -276,7 +275,8 @@ internal class DynamicTagReader(reader: XmlReader, descriptor: XmlDescriptor) :
276275
filterDepth == 0 &&
277276
(nsUri ?: "") == idAttrName.namespaceURI &&
278277
localName == idAttrName.localPart -> idValue
279-
else -> super.getAttributeValue(nsUri, localName)
278+
279+
else -> super.getAttributeValue(nsUri, localName)
280280
}
281281

282282
/**
@@ -440,4 +440,4 @@ private fun newExample(testElements: List<TestElement>) {
440440
println("Deserialized container:\n $deserializedData")
441441

442442
}
443-
```
443+
```

examples/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,5 @@ val autoModuleName = "net.devrieze.serialexamples"
3636
dependencies {
3737
implementation(projects.serialization)
3838
implementation(projects.serialutil)
39+
testImplementation(projects.testutil)
3940
}

0 commit comments

Comments
 (0)