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