Skip to content

Instantly share code, notes, and snippets.

@libetl

libetl/XmlToMap.kt

Last active Jun 25, 2019
Embed
What would you like to do?
XmlToMap.kt
package xmltomap
@Suppress("UNCHECKED_CAST")
object XmlToMap {
private val CDATA = Regex("^<!\\[CDATA\\[(.+?)\\]\\]>")
private val OPENING_TAG = Regex("^<([^\\s>/]+)[^>]*>")
private val OPENING_CLOSING_TAG = Regex("^<([^\\s>/]+)[^>]*/>")
private val CLOSING_TAG = Regex("^</([^\\s>]+)>")
private val ATTRS = Regex("([a-zA-Z][a-zA-Z-0-9]*)=\"([^\"]*)\"")
private val UPPERCASE = Regex("[A-Z]+")
fun xmlToMap(body: String): Map<String, Any> {
val root = mutableMapOf<String, Any>()
fun String.toCamelCase(): String =
when (UPPERCASE.matchEntire(this)) {
null -> this.substring(0, 1).toLowerCase() + this.substring(1)
else -> this.toLowerCase()
}
val deque = mutableListOf(root)
val attrSetFunc = { text: String, node: Any ->
ATTRS.findAll(text)
.forEach {
if (node is MutableMap<*, *>) {
val node1 = node as MutableMap<String, Any>
node1[it.groups[1]!!.value.toCamelCase()] = it.groups[2]!!.value
}
}
}
val foundNodeHavingDifferentAttributes =
{ localName: String, attrsNode: MutableMap<String, Any>, values: Any? ->
val areDifferent = { attrsNode1: MutableMap<String, Any>, toBeCompared: MutableMap<String, Any> ->
attrsNode1.size != toBeCompared.size ||
attrsNode1.asSequence().any { it.value != toBeCompared[it.key] }
}
if (values is Map<*, *> && values[localName] == null) false
else when (values) {
is List<*> -> values.any { areDifferent(attrsNode, it as MutableMap<String, Any>) }
is MutableMap<*, *> -> areDifferent(attrsNode, values as MutableMap<String, Any>)
else -> false
}
}
val alreadyArrayTyped = { node: MutableMap<String, Any> ->
node.keys.asSequence().toList().sorted() == listOf("arrayOfType", "children")
}
var currentLocalName = ""
var substring = body
var currentNode = root
while (substring.isNotEmpty()) {
when {
CDATA.containsMatchIn(substring) -> {
val groups = CDATA.find(substring)!!.groups
currentNode["CDATA"] = groups[1]!!.value
substring = substring.substring(groups[0]!!.range.endInclusive + 1)
}
OPENING_CLOSING_TAG.containsMatchIn(substring) -> {
val groups = OPENING_CLOSING_TAG.find(substring)!!.groups
val localName = groups[1]!!.value.toCamelCase()
val attrs = mutableMapOf<String, Any>()
attrSetFunc(groups[0]!!.value, attrs)
currentNode[localName] = attrs
substring = substring.substring(groups[0]!!.range.endInclusive + 1)
}
CLOSING_TAG.containsMatchIn(substring) -> {
val groups = CLOSING_TAG.find(substring)!!.groups
currentNode = deque.removeAt(deque.lastIndex)
substring = substring.substring(groups[0]!!.range.endInclusive + 1)
}
OPENING_TAG.containsMatchIn(substring) -> {
val groups = OPENING_TAG.find(substring)!!.groups
val attrsNode = mutableMapOf<String, Any>()
attrSetFunc(groups[0]!!.value, attrsNode)
val localName = groups[1]!!.value.toCamelCase()
currentLocalName = localName
when {
alreadyArrayTyped(currentNode) -> {
val intermediaryNode = mutableMapOf<String, Any>()
(currentNode["children"] as MutableList<Any>).add(intermediaryNode)
attrsNode.entries.asSequence().forEach { intermediaryNode[it.key] = it.value }
deque.add(currentNode)
currentNode = intermediaryNode
}
foundNodeHavingDifferentAttributes(localName, attrsNode, currentNode) -> {
val cloned = mutableMapOf<String, Any>().apply {
currentNode.entries.asSequence().forEach { this[it.key] = it.value }
}
currentNode.clear()
currentNode["arrayOfType"] = localName
val intermediaryNode = mutableMapOf<String, Any>()
currentNode["children"] =
mutableListOf<Any>(cloned, intermediaryNode)
deque.add(currentNode)
attrsNode.entries.asSequence().forEach { intermediaryNode[it.key] = it.value }
currentNode = intermediaryNode
}
else -> {
attrsNode.entries.asSequence().forEach { currentNode[it.key] = it.value }
deque.add(currentNode)
}
}
val newValue = mutableMapOf<String, Any>()
currentNode[localName] = newValue
currentNode = newValue
substring = substring.substring(groups[0]!!.range.endInclusive + 1)
}
else -> {
val data = substring.substring(0 until substring.indexOf('<'))
val parentNode = deque.last()
if (parentNode[currentLocalName] != null && parentNode[currentLocalName] !is String)
parentNode[currentLocalName] = data
else currentNode["CDATA"] = data
substring = substring.substring(substring.indexOf('<'))
}
}
}
return root
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.