All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.underscore.Xml.kt Maven / Gradle / Ivy

There is a newer version: 1.9
Show newest version
/*
 * The MIT License (MIT)
 *
 * Copyright 2015-2024 Valentyn Kolesnikov
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package com.github.underscore

import com.github.underscore.Base32.Companion.decode
import com.github.underscore.Base32.Companion.encode
import com.github.underscore.Base32.DecodingException
import org.w3c.dom.Node
import org.xml.sax.EntityResolver
import org.xml.sax.InputSource
import org.xml.sax.SAXException
import org.xml.sax.helpers.DefaultHandler
import java.io.IOException
import java.io.StringReader
import java.math.BigDecimal
import java.math.BigInteger
import java.nio.charset.StandardCharsets
import java.util.*
import java.util.function.BiFunction
import java.util.function.Function
import javax.xml.XMLConstants
import javax.xml.parsers.DocumentBuilderFactory
import javax.xml.parsers.ParserConfigurationException
import kotlin.math.max

object Xml {
    private const val NULL = "null"
    private const val ELEMENT_TEXT = "element"
    private const val CDATA = "#cdata-section"
    private const val COMMENT = "#comment"
    private const val ENCODING = "#encoding"
    private const val STANDALONE = "#standalone"
    private const val OMITXMLDECLARATION = "#omit-xml-declaration"
    private const val YES = "yes"
    private const val TEXT = "#text"
    private const val NUMBER = "-number"
    private const val ELEMENT = "<$ELEMENT_TEXT>"
    private const val CLOSED_ELEMENT = ""
    private const val EMPTY_ELEMENT = ELEMENT + CLOSED_ELEMENT
    private const val NULL_TRUE = " $NULL=\"true\"/>"
    private const val NUMBER_TEXT = " number=\"true\""
    private const val NUMBER_TRUE = "$NUMBER_TEXT>"
    private const val ARRAY = "-array"
    private const val ARRAY_TRUE = " array=\"true\""
    private const val NULL_ELEMENT = "<$ELEMENT_TEXT$NULL_TRUE"
    private const val BOOLEAN = "-boolean"
    private const val TRUE = "true"
    private const val SELF_CLOSING = "-self-closing"
    private const val STRING = "-string"
    private const val NULL_ATTR = "-null"
    private const val EMPTY_ARRAY = "-empty-array"
    private const val QUOT = """
    private const val XML_HEADER = " = HashMap()
    private val DOCUMENT = Document.createDocument()

    init {
        XML_UNESCAPE[QUOT] = "\""
        XML_UNESCAPE["&"] = "&"
        XML_UNESCAPE["<"] = "<"
        XML_UNESCAPE[">"] = ">"
        XML_UNESCAPE["'"] = "'"
    }

    @JvmOverloads
    @JvmStatic
    fun toXml(collection: Collection<*>?, identStep: XmlStringBuilder.Step = XmlStringBuilder.Step.TWO_SPACES): String {
        val builder: XmlStringBuilder = XmlStringBuilderWithoutRoot(identStep, StandardCharsets.UTF_8.name(), "")
        writeArray(collection, builder, ARRAY_TRUE)
        return builder.toString()
    }

    @JvmOverloads
    @JvmStatic
    fun toXml(
        map: Map<*, *>?,
        identStep: XmlStringBuilder.Step = XmlStringBuilder.Step.TWO_SPACES,
        newRootName: String = ROOT,
        arrayTrue: ArrayTrue = ArrayTrue.ADD
    ): String {
        val builder: XmlStringBuilder
        val localMap: Map<*, *>?
        if (map != null && map.containsKey(ENCODING)) {
            localMap = (map as LinkedHashMap<*, *>).clone() as MutableMap<*, *>
            builder = checkStandalone(localMap.remove(ENCODING).toString(), identStep, localMap)
        } else if (map != null && map.containsKey(STANDALONE)) {
            localMap = (map as LinkedHashMap<*, *>).clone() as MutableMap<*, *>
            builder = XmlStringBuilderWithoutRoot(
                identStep,
                StandardCharsets.UTF_8.name(),
                " standalone=\""
                        + (if (YES == map[STANDALONE]) YES else "no")
                        + "\""
            )
            localMap.remove(STANDALONE)
        } else if (map != null && map.containsKey(OMITXMLDECLARATION)) {
            localMap = (map as LinkedHashMap<*, *>).clone() as MutableMap<*, *>
            builder = XmlStringBuilderWithoutHeader(identStep, 0)
            localMap.remove(OMITXMLDECLARATION)
        } else {
            builder = XmlStringBuilderWithoutRoot(identStep, StandardCharsets.UTF_8.name(), "")
            localMap = map
        }
        checkLocalMap(builder, localMap, newRootName, if (arrayTrue == ArrayTrue.ADD) ARRAY_TRUE else "")
        return builder.toString()
    }

    private fun checkLocalMap(
        builder: XmlStringBuilder,
        localMap: Map<*, *>?,
        newRootName: String,
        arrayTrue: String
    ) {
        val localMap2: Map<*, *>?
        if (localMap != null && localMap.containsKey(DOCTYPE_TEXT)) {
            localMap2 = (localMap as LinkedHashMap<*, *>).clone() as MutableMap<*, *>
            localMap2.remove(DOCTYPE_TEXT)
            builder.append(DOCTYPE_HEADER)
                .append(localMap[DOCTYPE_TEXT].toString())
                .append(">")
                .newLine()
        } else {
            localMap2 = localMap
        }
        if (localMap2 == null || localMap2.size != 1 || XmlValue.getMapKey(localMap2).startsWith("-")
            || XmlValue.getMapValue(localMap2) is List<*>
        ) {
            if (ROOT == XmlValue.getMapKey(localMap2)) {
                writeArray(XmlValue.getMapValue(localMap2) as List<*>?, builder, arrayTrue)
            } else {
                XmlObject.writeXml(
                    localMap2,
                    getRootName(localMap2, newRootName),
                    builder,
                    false,
                    LinkedHashSet(),
                    false,
                    arrayTrue
                )
            }
        } else {
            XmlObject.writeXml(
                localMap2,
                getRootName(localMap2, newRootName),
                builder,
                false,
                LinkedHashSet(),
                false,
                arrayTrue
            )
        }
    }

    private fun writeArray(
        collection: Collection<*>?, builder: XmlStringBuilder, arrayTrue: String
    ) {
        builder.append("").incIdent()
        if (collection != null && !collection.isEmpty()) {
            builder.newLine()
        }
        XmlArray.writeXml(collection, null, builder, false, LinkedHashSet(), false, arrayTrue)
        if (collection != null && !collection.isEmpty()) {
            builder.newLine()
        }
        builder.append("")
    }

    private fun checkStandalone(
        encoding: String, identStep: XmlStringBuilder.Step, localMap: MutableMap<*, *>
    ): XmlStringBuilder {
        val builder: XmlStringBuilder
        if (localMap.containsKey(STANDALONE)) {
            builder = XmlStringBuilderWithoutRoot(
                identStep,
                encoding,
                " standalone=\""
                        + (if (YES == localMap[STANDALONE]) YES else "no")
                        + "\""
            )
            localMap.remove(STANDALONE)
        } else {
            builder = XmlStringBuilderWithoutRoot(identStep, encoding, "")
        }
        return builder
    }

    private fun getRootName(localMap: Map<*, *>?, newRootName: String): String? {
        var foundAttrs = 0
        var foundElements = 0
        var foundListElements = 0
        if (localMap != null) {
            for ((key, value) in localMap.entries) {
                if (key.toString().startsWith("-")) {
                    foundAttrs += 1
                } else if (!key.toString().startsWith(COMMENT)
                    && !key.toString().startsWith(CDATA)
                    && !key.toString().startsWith("?")
                ) {
                    if (value is List<*> && value.size > 1) {
                        foundListElements += 1
                    }
                    foundElements += 1
                }
            }
        }
        return if (foundAttrs == 0 && foundElements == 1 && foundListElements == 0) null else newRootName
    }

    @Suppress("UNCHECKED_CAST")
    private operator fun getValue(name: String?, value: Any?, fromType: FromType): Any? {
        val localValue: Any? = if (value is Map<*, *> && (value as Map).entries.size == 1) {
                val (key, value1) = (value as Map).entries.iterator().next()
                if (TEXT == key || fromType == FromType.FOR_CONVERT && ELEMENT_TEXT == key) {
                    value1
                } else {
                    value
                }
            } else {
                value
            }
        return (if (localValue is String && name!!.startsWith("-")) XmlValue.unescape(localValue as String?) else localValue)
    }

    @JvmStatic
    fun stringToNumber(number: String): Any {
        val localValue: Any = if (number.contains(".") || number.contains("e") || number.contains("E")) {
            if (number.length > 9
                || number.contains(".") && number.length - number.lastIndexOf('.') > 2
                && number[number.length - 1] == '0'
            ) {
                BigDecimal(number)
            } else {
                number.toDouble()
            }
        } else {
            if (number.length > 19) {
                BigInteger(number)
            } else {
                number.toLong()
            }
        }
        return localValue
    }

    private fun createMap(
        node: Node,
        elementMapper: BiFunction, String?>,
        nodeMapper: Function,
        attrMap: Map,
        uniqueIds: IntArray,
        source: String,
        sourceIndex: IntArray,
        namespaces: MutableSet,
        fromType: FromType
    ): Any {
        val map = LinkedHashMap()
        map.putAll(attrMap)
        val nodeList = node.childNodes
        for (index in 0 until nodeList.length) {
            val currentNode = nodeList.item(index)
            val name: String = if (currentNode.nodeType == Node.PROCESSING_INSTRUCTION_NODE) {
                "?" + currentNode.nodeName
            } else {
                currentNode.nodeName
            }
            val value: Any?
            if (currentNode.nodeType == Node.ELEMENT_NODE) {
                sourceIndex[0] = source.indexOf("<$name", sourceIndex[0]) + name.length + 1
                value = addElement(
                    sourceIndex,
                    source,
                    elementMapper,
                    nodeMapper,
                    uniqueIds,
                    currentNode,
                    namespaces,
                    fromType
                )
            } else {
                if (COMMENT == name) {
                    sourceIndex[0] = source.indexOf("-->", sourceIndex[0]) + 3
                } else if (CDATA == name) {
                    sourceIndex[0] = source.indexOf("]]>", sourceIndex[0]) + 3
                }
                value = currentNode.textContent
            }
            if (TEXT == name && node.childNodes.length > 1 && value.toString().trim { it <= ' ' }.isEmpty()) {
                continue
            }
            if (currentNode.nodeType == Node.DOCUMENT_TYPE_NODE) {
                addNodeValue(
                    map,
                    DOCTYPE_TEXT,
                    getDoctypeValue(source),
                    elementMapper,
                    nodeMapper,
                    uniqueIds,
                    namespaces,
                    fromType
                )
            } else {
                addNodeValue(
                    map,
                    name,
                    value,
                    elementMapper,
                    nodeMapper,
                    uniqueIds,
                    namespaces,
                    fromType
                )
            }
        }
        return checkNumberAndBoolean(map, node.nodeName)
    }

    @Suppress("UNCHECKED_CAST")
    private fun checkNumberAndBoolean(map: MutableMap, name: String): Any {
        val localMap: MutableMap
        if (map.containsKey(NUMBER) && TRUE == map[NUMBER] && map.containsKey(TEXT)) {
            localMap = (map as LinkedHashMap).clone() as MutableMap
            localMap.remove(NUMBER)
            localMap[TEXT] = stringToNumber(localMap[TEXT].toString())
        } else {
            localMap = map
        }
        val localMap2: MutableMap
        if (map.containsKey(BOOLEAN) && TRUE == map[BOOLEAN] && map.containsKey(TEXT)) {
            localMap2 = (localMap as LinkedHashMap).clone() as MutableMap
            localMap2.remove(BOOLEAN)
            localMap2[TEXT] = localMap[TEXT].toString().toBoolean()
        } else {
            localMap2 = localMap
        }
        return checkArray(localMap2, name)
    }

    @Suppress("UNCHECKED_CAST")
    private fun checkArray(map: MutableMap, name: String): Any {
        val localMap = checkNullAndString(map)
        val `object`: Any = if (map.containsKey(ARRAY) && TRUE == map[ARRAY]) {
            val localMap4: MutableMap =
                (localMap as LinkedHashMap).clone() as MutableMap
            localMap4.remove(ARRAY)
            localMap4.remove(SELF_CLOSING)
            if (name == XmlValue.getMapKey(localMap4)) ArrayList(
                listOf(
                    getValue(
                        name,
                        XmlValue.getMapValue(localMap4),
                        FromType.FOR_CONVERT
                    )
                )
            ) else ArrayList(
                listOf(
                    getValue(name, localMap4, FromType.FOR_CONVERT)
                )
            )
        } else {
            localMap
        }
        val object2: Any
        if (map.containsKey(EMPTY_ARRAY) && TRUE == map[EMPTY_ARRAY]) {
            val localMap4: MutableMap =
                (map as LinkedHashMap).clone() as MutableMap
            localMap4.remove(EMPTY_ARRAY)
            if (localMap4.containsKey(ARRAY) && TRUE == localMap4[ARRAY] && localMap4.size == 1) {
                object2 = ArrayList()
                (object2 as MutableList).add(ArrayList())
            } else {
                object2 = if (localMap4.isEmpty()) ArrayList() else localMap4
            }
        } else {
            object2 = `object`
        }
        return object2
    }

    @Suppress("UNCHECKED_CAST")
    private fun checkNullAndString(map: MutableMap): Map {
        val localMap: MutableMap
        if (map.containsKey(NULL_ATTR) && TRUE == map[NULL_ATTR]) {
            localMap = (map as LinkedHashMap).clone() as MutableMap
            localMap.remove(NULL_ATTR)
            if (!map.containsKey(TEXT)) {
                localMap[TEXT] = null
            }
        } else {
            localMap = map
        }
        val localMap2: MutableMap
        if (map.containsKey(STRING) && TRUE == map[STRING]) {
            localMap2 = (localMap as LinkedHashMap).clone() as MutableMap
            localMap2.remove(STRING)
            if (!map.containsKey(TEXT)) {
                localMap2[TEXT] = ""
            }
        } else {
            localMap2 = localMap
        }
        return localMap2
    }

    private fun addElement(
        sourceIndex: IntArray,
        source: String,
        elementMapper: BiFunction, String?>,
        nodeMapper: Function,
        uniqueIds: IntArray,
        currentNode: Node,
        namespaces: MutableSet,
        fromType: FromType
    ): Any {
        val attrMapLocal = LinkedHashMap()
        if (currentNode.attributes.length > 0) {
            val attributes = parseAttributes(getAttributes(sourceIndex[0], source))
            for ((key) in attributes) {
                if (key.startsWith("xmlns:")) {
                    namespaces.add(key.substring(6))
                }
            }
            for ((key, value) in attributes) {
                addNodeValue(
                    attrMapLocal,
                    "-$key",
                    value,
                    elementMapper,
                    nodeMapper,
                    uniqueIds,
                    namespaces,
                    fromType
                )
            }
        }
        if (getAttributes(sourceIndex[0], source).endsWith("/")
            && !attrMapLocal.containsKey(SELF_CLOSING)
            && (attrMapLocal.size != 1
                    || ((!attrMapLocal.containsKey(STRING)
                    || TRUE != attrMapLocal[STRING])
                    && (!attrMapLocal.containsKey(NULL_ATTR)
                    || TRUE != attrMapLocal[NULL_ATTR])))
        ) {
            attrMapLocal[SELF_CLOSING] = TRUE
        }
        return createMap(
            currentNode,
            elementMapper,
            nodeMapper,
            attrMapLocal,
            uniqueIds,
            source,
            sourceIndex,
            namespaces,
            fromType
        )
    }

    @JvmStatic
    fun parseAttributes(source: String): Map {
        val result: MutableMap = LinkedHashMap()
        val key = StringBuilder()
        val value = StringBuilder()
        var quoteFound = false
        var equalFound = false
        var index = 0
        while (index < source.length) {
            if (source[index] == '=') {
                equalFound = !equalFound
                index += 1
                continue
            }
            if (source[index] == '"') {
                if (quoteFound && equalFound) {
                    result[key.toString()] = value.toString()
                    key.setLength(0)
                    value.setLength(0)
                    equalFound = false
                }
                quoteFound = !quoteFound
            } else if (quoteFound || SKIPPED_CHARS.contains(source[index])) {
                if (quoteFound) {
                    value.append(source[index])
                }
            } else {
                key.append(source[index])
            }
            index += 1
        }
        return result
    }

    @JvmStatic
    fun getAttributes(sourceIndex: Int, source: String): String {
        var scanQuote = false
        var index = sourceIndex
        while (index < source.length) {
            if (source[index] == '"') {
                scanQuote = !scanQuote
                index += 1
                continue
            }
            if (!scanQuote && source[index] == '>') {
                return source.substring(sourceIndex, index)
            }
            index += 1
        }
        return ""
    }

    private fun unescapeName(name: String?): String? {
        if (name == null) {
            return null
        }
        val length = name.length
        if ("__EE__EMPTY__EE__" == name) {
            return ""
        }
        if ("-__EE__EMPTY__EE__" == name) {
            return "-"
        }
        if (!name.contains("__")) {
            return name
        }
        val result = StringBuilder()
        var underlineCount = 0
        val lastChars = StringBuilder()
        var i = 0
        outer@ while (i < length) {
            val ch = name[i]
            if (ch == '_') {
                lastChars.append(ch)
            } else {
                if (lastChars.length == 2) {
                    val nameToDecode = StringBuilder()
                    for (j in i until length) {
                        if (name[j] == '_') {
                            underlineCount += 1
                            if (underlineCount == 2) {
                                try {
                                    result.append(decode(nameToDecode.toString()))
                                } catch (ex: DecodingException) {
                                    result.append("__").append(nameToDecode).append(lastChars)
                                }
                                i = j
                                underlineCount = 0
                                lastChars.setLength(0)
                                i++
                                continue@outer
                            }
                        } else {
                            nameToDecode.append(name[j])
                            underlineCount = 0
                        }
                    }
                }
                result.append(lastChars).append(ch)
                lastChars.setLength(0)
            }
            i++
        }
        return result.append(lastChars).toString()
    }

    @Suppress("UNCHECKED_CAST")
    private fun addNodeValue(
        map: MutableMap,
        name: String,
        value: Any?,
        elementMapper: BiFunction, String?>,
        nodeMapper: Function,
        uniqueIds: IntArray,
        namespaces: Set,
        fromType: FromType
    ) {
        val elementName = unescapeName(elementMapper.apply(name, namespaces))
        if (map.containsKey(elementName)) {
            if (TEXT == elementName) {
                map[elementName + uniqueIds[0]] = nodeMapper.apply(getValue(name, value, fromType))
                uniqueIds[0] += 1
            } else if (COMMENT == elementName) {
                map[elementName + uniqueIds[1]] = nodeMapper.apply(getValue(name, value, fromType))
                uniqueIds[1] += 1
            } else if (CDATA == elementName) {
                map[elementName + uniqueIds[2]] = nodeMapper.apply(getValue(name, value, fromType))
                uniqueIds[2] += 1
            } else {
                val `object` = map[elementName]
                if (`object` is List<*>) {
                    addText(map, elementName, `object` as MutableList, value, fromType)
                } else {
                    val objects = ArrayList()
                    objects.add(`object`)
                    addText(map, elementName, objects, value, fromType)
                    map[elementName] = objects
                }
            }
        } else {
            if (elementName != null) {
                map[elementName] = nodeMapper.apply(getValue(name, value, fromType))
            }
        }
    }

    private fun addText(
        map: MutableMap,
        name: String?,
        objects: MutableList,
        value: Any?,
        fromType: FromType
    ) {
        var lastIndex = map.size - 1
        val index = objects.size
        while (true) {
            val (key) = map.entries.toTypedArray()[lastIndex]
            if (name == key.toString()) {
                break
            }
            val item = LinkedHashMap()
            val text = LinkedHashMap()
            text[key.toString()] = map.remove(key)
            item["#item"] = text
            objects.add(index, item)
            lastIndex -= 1
        }
        val newValue = getValue(name, value, fromType)
        if (newValue is List<*>) {
            objects.add(newValue[0])
        } else {
            objects.add(newValue)
        }
    }

    @JvmStatic
    @JvmOverloads
    fun fromXml(xml: String?, fromType: FromType = FromType.FOR_CONVERT): Any? {
        return if (xml == null) {
            null
        } else try {
            val document = Document.createDocument(xml)
            val result = createMap(
                document,
                { `object`: Any, _: Set? -> `object`.toString() },
                { `object`: Any? -> `object` }, emptyMap(), intArrayOf(1, 1, 1),
                xml, intArrayOf(0),
                LinkedHashSet(),
                fromType
            )
            if (checkResult(xml, document, result, fromType)) {
                ((result as Map<*, *>).entries.iterator().next() as Map.Entry<*, *>).value
            } else result
        } catch (ex: Exception) {
            throw IllegalArgumentException(ex)
        }
    }

    @Suppress("UNCHECKED_CAST")
    private fun checkResult(
        xml: String,
        document: org.w3c.dom.Document,
        result: Any,
        fromType: FromType
    ): Boolean {
        val headerAttributes = getHeaderAttributes(xml)
        if (document.xmlEncoding != null
            && !"UTF-8".equals(document.xmlEncoding, ignoreCase = true)
        ) {
            (result as MutableMap)[ENCODING] = document.xmlEncoding
            if (headerAttributes.containsKey(STANDALONE.substring(1))) {
                result[STANDALONE] = headerAttributes[STANDALONE.substring(1)]
            }
        } else if (headerAttributes.containsKey(STANDALONE.substring(1))) {
            (result as MutableMap)[STANDALONE] = headerAttributes[STANDALONE.substring(1)]
        } else if (fromType == FromType.FOR_CONVERT && XmlValue.getMapKey(result) == ROOT && (XmlValue.getMapValue(
                result
            ) is List<*>
                    || XmlValue.getMapValue(result) is Map<*, *>)
        ) {
            if (xml.startsWith(XML_HEADER)) {
                return true
            } else {
                (result as MutableMap)[OMITXMLDECLARATION] = YES
            }
        } else if (!xml.startsWith(XML_HEADER)) {
            (result as MutableMap)[OMITXMLDECLARATION] = YES
        }
        return false
    }

    private fun getHeaderAttributes(xml: String): Map {
        val result: MutableMap = LinkedHashMap()
        if (xml.startsWith(XML_HEADER)) {
            val xmlLocal = xml.substring(
                XML_HEADER.length,
                max(XML_HEADER.length.toDouble(), xml.indexOf("?>", XML_HEADER.length).toDouble()).toInt()
            )
            val attributes = parseAttributes(xmlLocal)
            for ((key, value) in attributes) {
                result[key] = value
            }
        }
        return result
    }

    @JvmStatic
    fun getDoctypeValue(xml: String): String {
        val startIndex = xml.indexOf(DOCTYPE_HEADER) + DOCTYPE_HEADER.length
        var charToFind = '>'
        var endIndexPlus = 0
        var endIndex = startIndex
        while (endIndex < xml.length) {
            if (xml[endIndex] == '[') {
                charToFind = ']'
                endIndexPlus = 1
                endIndex += 1
                continue
            }
            if (xml[endIndex] == charToFind) {
                return xml.substring(startIndex, endIndex + endIndexPlus)
            }
            endIndex += 1
        }
        return ""
    }

    @JvmStatic
    fun fromXmlMakeArrays(xml: String): Any {
        return try {
            val document = Document.createDocument(xml)
            val result = createMap(
                document,
                { `object`: Any, _: Set? -> `object`.toString() },
                { `object`: Any? -> `object` as? List<*> ?: ArrayList(listOf(`object`)) },
                emptyMap(),
                intArrayOf(1, 1, 1),
                xml,
                intArrayOf(0),
                LinkedHashSet(),
                FromType.FOR_CONVERT
            )
            if (checkResult(xml, document, result, FromType.FOR_CONVERT)) {
                ((result as Map<*, *>).entries.iterator().next() as Map.Entry<*, *>).value!!
            } else result
        } catch (ex: Exception) {
            throw IllegalArgumentException(ex)
        }
    }

    private fun fromXmlWithElementMapper(
        xml: String, elementMapper: BiFunction, String?>
    ): Any {
        return try {
            val document = Document.createDocument(xml)
            val result = createMap(
                document,
                elementMapper,
                { `object`: Any? -> `object` }, emptyMap(), intArrayOf(1, 1, 1),
                xml, intArrayOf(0),
                LinkedHashSet(),
                FromType.FOR_CONVERT
            )
            if (checkResult(xml, document, result, FromType.FOR_CONVERT)) {
                ((result as Map<*, *>).entries.iterator().next() as Map.Entry<*, *>).value!!
            } else result
        } catch (ex: Exception) {
            throw IllegalArgumentException(ex)
        }
    }

    @JvmStatic
    fun fromXmlWithoutNamespaces(xml: String): Any {
        return fromXmlWithElementMapper(
            xml
        ) { `object`: Any, namespaces: Set ->
            val localString = `object`.toString()
            val result: String = if (localString.startsWith("-")
                && namespaces.contains(
                    localString.substring(
                        1, Math.max(1, localString.indexOf(':'))
                    )
                )
            ) {
                ("-"
                        + localString.substring(
                    Math.max(0, localString.indexOf(':') + 1)
                ))
            } else if (namespaces.contains(
                    localString.substring(0, Math.max(0, localString.indexOf(':')))
                )
            ) {
                localString.substring(Math.max(0, localString.indexOf(':') + 1))
            } else {
                `object`.toString()
            }
            result
        }
    }

    @JvmStatic
    fun fromXmlWithoutAttributes(xml: String): Any {
        return fromXmlWithElementMapper(
            xml
        ) { `object`: Any, _: Set? ->
            if (`object`.toString().startsWith("-")) null else `object`.toString()
        }
    }

    @JvmStatic
    fun fromXmlWithoutNamespacesAndAttributes(xml: String): Any {
        return fromXmlWithElementMapper(
            xml
        ) { `object`: Any, namespaces: Set ->
            val localString = `object`.toString()
            val result: String? = if (localString.startsWith("-")) {
                null
            } else if (namespaces.contains(
                    localString.substring(0, Math.max(0, localString.indexOf(':')))
                )
            ) {
                localString.substring(Math.max(0, localString.indexOf(':') + 1))
            } else {
                `object`.toString()
            }
            result
        }
    }

    @JvmStatic
    @JvmOverloads
    fun formatXml(xml: String?, identStep: XmlStringBuilder.Step = XmlStringBuilder.Step.TWO_SPACES): String {
        val result = fromXml(xml, FromType.FOR_FORMAT)
        return toXml(result as Map<*, *>?, identStep, ROOT)
    }

    @Suppress("UNCHECKED_CAST")
    @JvmStatic
    fun changeXmlEncoding(
        xml: String?, identStep: XmlStringBuilder.Step, encoding: String?
    ): String? {
        val result = fromXml(xml, FromType.FOR_FORMAT)
        if (result is Map<*, *>) {
            (result as MutableMap)[ENCODING] = encoding
            return toXml(result as Map<*, *>?, identStep, ROOT)
        }
        return xml
    }

    @JvmStatic
    fun changeXmlEncoding(xml: String?, encoding: String?): String? {
        return changeXmlEncoding(xml, XmlStringBuilder.Step.TWO_SPACES, encoding)
    }

    enum class ArrayTrue {
        ADD,
        SKIP
    }

    open class XmlStringBuilder {
        enum class Step(val ident: Int) {
            TWO_SPACES(2),
            THREE_SPACES(3),
            FOUR_SPACES(4),
            COMPACT(0),
            TABS(1)

        }

        protected val builder: StringBuilder
        val identStep: Step
        var ident: Int
            private set

        constructor() {
            builder = StringBuilder("\n\n")
            identStep = Step.TWO_SPACES
            ident = 2
        }

        constructor(builder: StringBuilder, identStep: Step, ident: Int) {
            this.builder = builder
            this.identStep = identStep
            this.ident = ident
        }

        fun append(string: String?): XmlStringBuilder {
            builder.append(string)
            return this
        }

        fun fillSpaces(): XmlStringBuilder {
            builder.append(
                (if (identStep == Step.TABS) '\t' else ' ').toString().repeat(Math.max(0, ident))
            )
            return this
        }

        fun incIdent(): XmlStringBuilder {
            ident += identStep.ident
            return this
        }

        fun decIdent(): XmlStringBuilder {
            ident -= identStep.ident
            return this
        }

        fun newLine(): XmlStringBuilder {
            if (identStep != Step.COMPACT) {
                builder.append("\n")
            }
            return this
        }

        override fun toString(): String {
            return "$builder\n"
        }
    }

    class XmlStringBuilderWithoutRoot(
        identStep: Step, encoding: String?, standalone: String
    ) : XmlStringBuilder(
        StringBuilder(
            ""
                    + if (identStep == Step.COMPACT) "" else "\n"
        ),
        identStep,
        0
    ) {
        override fun toString(): String {
            return builder.toString()
        }
    }

    open class XmlStringBuilderWithoutHeader(identStep: Step, ident: Int) :
        XmlStringBuilder(StringBuilder(), identStep, ident) {
        override fun toString(): String {
            return builder.toString()
        }
    }

    class XmlStringBuilderText(identStep: Step, ident: Int) : XmlStringBuilderWithoutHeader(identStep, ident)
    object XmlArray {
        @JvmStatic
        fun writeXml(
            collection: Collection<*>?,
            name: String?,
            builder: XmlStringBuilder,
            parentTextFound: Boolean,
            namespaces: MutableSet,
            addArray: Boolean,
            arrayTrue: String
        ) {
            if (collection == null) {
                builder.append(NULL)
                return
            }
            if (name != null) {
                builder.fillSpaces().append("<").append(XmlValue.escapeName(name, namespaces))
                if (addArray) {
                    builder.append(arrayTrue)
                }
                if (collection.isEmpty()) {
                    builder.append(" empty-array=\"true\"")
                }
                builder.append(">").incIdent()
                if (!collection.isEmpty()) {
                    builder.newLine()
                }
            }
            writeXml(collection, builder, name, parentTextFound, namespaces, arrayTrue)
            if (name != null) {
                builder.decIdent()
                if (!collection.isEmpty()) {
                    builder.newLine().fillSpaces()
                }
                builder.append("")
            }
        }

        fun writeXml(
            collection: Collection<*>,
            builder: XmlStringBuilder,
            name: String?,
            parentTextFound: Boolean,
            namespaces: MutableSet,
            arrayTrue: String
        ) {
            var localParentTextFound = parentTextFound
            val entries = ArrayList(collection)
            var index = 0
            while (index < entries.size) {
                val value = entries[index]
                val addNewLine = (index < entries.size - 1
                        && !XmlValue.getMapKey(XmlValue.getMapValue(entries[index + 1]))
                    .startsWith(TEXT))
                if (value == null) {
                    builder.fillSpaces()
                        .append(
                            "<"
                                    + (if (name == null) ELEMENT_TEXT else XmlValue.escapeName(name, namespaces))
                                    + (if (collection.size == 1) arrayTrue else "")
                                    + NULL_TRUE
                        )
                } else {
                    if (value is Map<*, *> && value.size == 1 && XmlValue.getMapKey(value) == "#item" && XmlValue.getMapValue(
                            value
                        ) is Map<*, *>
                    ) {
                        XmlObject.writeXml(
                            XmlValue.getMapValue(value) as Map<*, *>?,
                            null,
                            builder,
                            localParentTextFound,
                            namespaces,
                            true,
                            arrayTrue
                        )
                        if (XmlValue.getMapKey(XmlValue.getMapValue(value)).startsWith(TEXT)) {
                            localParentTextFound = true
                            index += 1
                            continue
                        }
                    } else {
                        XmlValue.writeXml(
                            value,
                            name ?: ELEMENT_TEXT,
                            builder,
                            localParentTextFound,
                            namespaces,
                            collection.size == 1 || value is Collection<*>,
                            arrayTrue
                        )
                    }
                    localParentTextFound = false
                }
                if (addNewLine) {
                    builder.newLine()
                }
                index += 1
            }
        }

        @JvmStatic
        fun writeXml(array: ByteArray?, builder: XmlStringBuilder) {
            if (array == null) {
                builder.fillSpaces().append(NULL_ELEMENT)
            } else if (array.isEmpty()) {
                builder.fillSpaces().append(EMPTY_ELEMENT)
            } else {
                for (i in array.indices) {
                    builder.fillSpaces().append(ELEMENT)
                    builder.append(array[i].toString())
                    builder.append(CLOSED_ELEMENT)
                    if (i != array.size - 1) {
                        builder.newLine()
                    }
                }
            }
        }

        @JvmStatic
        fun writeXml(array: ShortArray?, builder: XmlStringBuilder) {
            if (array == null) {
                builder.fillSpaces().append(NULL_ELEMENT)
            } else if (array.isEmpty()) {
                builder.fillSpaces().append(EMPTY_ELEMENT)
            } else {
                for (i in array.indices) {
                    builder.fillSpaces().append(ELEMENT)
                    builder.append(array[i].toString())
                    builder.append(CLOSED_ELEMENT)
                    if (i != array.size - 1) {
                        builder.newLine()
                    }
                }
            }
        }

        @JvmStatic
        fun writeXml(array: IntArray?, builder: XmlStringBuilder) {
            if (array == null) {
                builder.fillSpaces().append(NULL_ELEMENT)
            } else if (array.isEmpty()) {
                builder.fillSpaces().append(EMPTY_ELEMENT)
            } else {
                for (i in array.indices) {
                    builder.fillSpaces().append(ELEMENT)
                    builder.append(array[i].toString())
                    builder.append(CLOSED_ELEMENT)
                    if (i != array.size - 1) {
                        builder.newLine()
                    }
                }
            }
        }

        @JvmStatic
        fun writeXml(array: LongArray?, builder: XmlStringBuilder) {
            if (array == null) {
                builder.fillSpaces().append(NULL_ELEMENT)
            } else if (array.isEmpty()) {
                builder.fillSpaces().append(EMPTY_ELEMENT)
            } else {
                for (i in array.indices) {
                    builder.fillSpaces().append(ELEMENT)
                    builder.append(array[i].toString())
                    builder.append(CLOSED_ELEMENT)
                    if (i != array.size - 1) {
                        builder.newLine()
                    }
                }
            }
        }

        @JvmStatic
        fun writeXml(array: FloatArray?, builder: XmlStringBuilder) {
            if (array == null) {
                builder.fillSpaces().append(NULL_ELEMENT)
            } else if (array.isEmpty()) {
                builder.fillSpaces().append(EMPTY_ELEMENT)
            } else {
                for (i in array.indices) {
                    builder.fillSpaces().append(ELEMENT)
                    builder.append(array[i].toString())
                    builder.append(CLOSED_ELEMENT)
                    if (i != array.size - 1) {
                        builder.newLine()
                    }
                }
            }
        }

        @JvmStatic
        fun writeXml(array: DoubleArray?, builder: XmlStringBuilder) {
            if (array == null) {
                builder.fillSpaces().append(NULL_ELEMENT)
            } else if (array.isEmpty()) {
                builder.fillSpaces().append(EMPTY_ELEMENT)
            } else {
                for (i in array.indices) {
                    builder.fillSpaces().append(ELEMENT)
                    builder.append(array[i].toString())
                    builder.append(CLOSED_ELEMENT)
                    if (i != array.size - 1) {
                        builder.newLine()
                    }
                }
            }
        }

        @JvmStatic
        fun writeXml(array: BooleanArray?, builder: XmlStringBuilder) {
            if (array == null) {
                builder.fillSpaces().append(NULL_ELEMENT)
            } else if (array.isEmpty()) {
                builder.fillSpaces().append(EMPTY_ELEMENT)
            } else {
                for (i in array.indices) {
                    builder.fillSpaces().append(ELEMENT)
                    builder.append(array[i].toString())
                    builder.append(CLOSED_ELEMENT)
                    if (i != array.size - 1) {
                        builder.newLine()
                    }
                }
            }
        }

        @JvmStatic
        fun writeXml(array: CharArray?, builder: XmlStringBuilder) {
            if (array == null) {
                builder.fillSpaces().append(NULL_ELEMENT)
            } else if (array.isEmpty()) {
                builder.fillSpaces().append(EMPTY_ELEMENT)
            } else {
                for (i in array.indices) {
                    builder.fillSpaces().append(ELEMENT)
                    builder.append(array[i].toString())
                    builder.append(CLOSED_ELEMENT)
                    if (i != array.size - 1) {
                        builder.newLine()
                    }
                }
            }
        }

        @JvmStatic
        fun writeXml(
            array: Array?,
            name: String?,
            builder: XmlStringBuilder,
            parentTextFound: Boolean,
            namespaces: MutableSet,
            arrayTrue: String
        ) {
            if (array == null) {
                builder.fillSpaces().append(NULL_ELEMENT)
            } else if (array.isEmpty()) {
                builder.fillSpaces().append(EMPTY_ELEMENT)
            } else {
                for (i in array.indices) {
                    XmlValue.writeXml(
                        array[i],
                        name ?: ELEMENT_TEXT,
                        builder,
                        parentTextFound,
                        namespaces,
                        false,
                        arrayTrue
                    )
                    if (i != array.size - 1) {
                        builder.newLine()
                    }
                }
            }
        }
    }

    object XmlObject {
        @Suppress("UNCHECKED_CAST")
        fun writeXml(
            map: Map<*, *>?,
            name: String?,
            builder: XmlStringBuilder,
            parentTextFound: Boolean,
            namespaces: MutableSet,
            addArray: Boolean,
            arrayTrue: String
        ) {
            if (map == null) {
                XmlValue.writeXml(NULL, name, builder, false, namespaces, addArray, arrayTrue)
                return
            }
            val elems = ArrayList()
            val attrs = ArrayList()
            val identStep = builder.identStep
            val ident = builder.ident + if (name == null) 0 else builder.identStep.ident
            val entries: List> = ArrayList(map.entries) as List>
            val attrKeys = LinkedHashSet()
            fillNamespacesAndAttrs(map, namespaces, attrKeys)
            var index = 0
            while (index < entries.size) {
                val entry = entries[index]
                val addNewLine = (index < entries.size - 1
                        && !entries[index + 1].key.toString().startsWith(TEXT))
                if (entry.key.toString().startsWith("-")
                    && entry.value is String
                ) {
                    attrs.add(
                        " "
                                + XmlValue.escapeName(
                            entry.key.toString().substring(1), namespaces
                        )
                                + "=\""
                                + XmlValue.escape(entry.value.toString())
                            .replace("\"", QUOT)
                                + "\""
                    )
                } else if (entry.key.toString().startsWith(TEXT)) {
                    addText(entry, elems, identStep, ident, attrKeys, attrs)
                } else {
                    val localParentTextFound = (elems.isNotEmpty()
                            && elems[elems.size - 1] is XmlStringBuilderText
                            || parentTextFound)
                    processElements(
                        entry,
                        identStep,
                        ident,
                        addNewLine,
                        elems,
                        namespaces,
                        localParentTextFound,
                        arrayTrue
                    )
                }
                index += 1
            }
            if (addArray && !attrKeys.contains(ARRAY)) {
                attrs.add(arrayTrue)
            }
            addToBuilder(name, parentTextFound, builder, namespaces, attrs, elems)
        }

        private fun fillNamespacesAndAttrs(
            map: Map<*, *>, namespaces: MutableSet, attrKeys: MutableSet
        ) {
            for ((key, value) in map.entries) {
                if (key.toString().startsWith("-")
                    && value !is Map<*, *>
                    && value !is List<*>
                ) {
                    if (key.toString().startsWith("-xmlns:")) {
                        namespaces.add(key.toString().substring(7))
                    }
                    attrKeys.add(key.toString())
                }
            }
        }

        private fun addToBuilder(
            name: String?,
            parentTextFound: Boolean,
            builder: XmlStringBuilder,
            namespaces: Set,
            attrs: MutableList,
            elems: List
        ) {
            val selfClosing = attrs.remove(" self-closing=\"true\"")
            addOpenElement(name, parentTextFound, builder, namespaces, selfClosing, attrs, elems)
            if (!selfClosing) {
                for (localBuilder1 in elems) {
                    builder.append(localBuilder1.toString())
                }
            }
            if (name != null) {
                builder.decIdent()
                if (elems.isNotEmpty()
                    && elems[elems.size - 1] !is XmlStringBuilderText
                ) {
                    builder.newLine().fillSpaces()
                }
                if (!selfClosing) {
                    builder.append("")
                }
            }
        }

        private fun addOpenElement(
            name: String?,
            parentTextFound: Boolean,
            builder: XmlStringBuilder,
            namespaces: Set,
            selfClosing: Boolean,
            attrs: List,
            elems: List
        ) {
            if (name != null) {
                if (!parentTextFound) {
                    builder.fillSpaces()
                }
                builder.append("<")
                    .append(XmlValue.escapeName(name, namespaces))
                    .append(attrs.joinToString(""))
                if (selfClosing) {
                    builder.append("/")
                }
                builder.append(">").incIdent()
                if (elems.isNotEmpty() && elems[0] !is XmlStringBuilderText) {
                    builder.newLine()
                }
            }
        }

        private fun processElements(
            entry: Map.Entry<*, *>,
            identStep: XmlStringBuilder.Step,
            ident: Int,
            addNewLine: Boolean,
            elems: MutableList,
            namespaces: MutableSet,
            parentTextFound: Boolean,
            arrayTrue: String
        ) {
            if (entry.key.toString().startsWith(COMMENT)) {
                addComment(entry, identStep, ident, parentTextFound, addNewLine, elems)
            } else if (entry.key.toString().startsWith(CDATA)) {
                addCdata(entry, identStep, ident, addNewLine, elems)
            } else if (entry.value is List<*> && (entry.value as List<*>).isNotEmpty()) {
                addElements(identStep, ident, entry, namespaces, elems, addNewLine, arrayTrue)
            } else {
                addElement(identStep, ident, entry, namespaces, elems, addNewLine, arrayTrue)
            }
        }

        private fun addText(
            entry: Map.Entry<*, *>,
            elems: MutableList,
            identStep: XmlStringBuilder.Step,
            ident: Int,
            attrKeys: Set,
            attrs: MutableList
        ) {
            if (entry.value is List<*>) {
                for (value in entry.value as List<*>) {
                    elems.add(
                        XmlStringBuilderText(identStep, ident)
                            .append(XmlValue.escape(value.toString()))
                    )
                }
            } else {
                if (entry.value is Number && !attrKeys.contains(NUMBER)) {
                    attrs.add(NUMBER_TEXT)
                } else if (entry.value is Boolean && !attrKeys.contains(BOOLEAN)) {
                    attrs.add(" boolean=\"true\"")
                } else if (entry.value == null && !attrKeys.contains(NULL_ATTR)) {
                    attrs.add(" null=\"true\"")
                    return
                } else if ("" == entry.value && !attrKeys.contains(STRING)) {
                    attrs.add(" string=\"true\"")
                    return
                }
                elems.add(
                    XmlStringBuilderText(identStep, ident)
                        .append(XmlValue.escape(entry.value.toString()))
                )
            }
        }

        private fun addElements(
            identStep: XmlStringBuilder.Step,
            ident: Int,
            entry: Map.Entry<*, *>,
            namespaces: MutableSet,
            elems: MutableList,
            addNewLine: Boolean,
            arrayTrue: String
        ) {
            val parentTextFound = elems.isNotEmpty() && elems[elems.size - 1] is XmlStringBuilderText
            val localBuilder: XmlStringBuilder = XmlStringBuilderWithoutHeader(identStep, ident)
            XmlArray.writeXml(
                entry.value as List<*>,
                localBuilder, entry.key.toString(),
                parentTextFound,
                namespaces,
                arrayTrue
            )
            if (addNewLine) {
                localBuilder.newLine()
            }
            elems.add(localBuilder)
        }

        private fun addElement(
            identStep: XmlStringBuilder.Step,
            ident: Int,
            entry: Map.Entry<*, *>,
            namespaces: MutableSet,
            elems: MutableList,
            addNewLine: Boolean,
            arrayTrue: String
        ) {
            val parentTextFound = elems.isNotEmpty() && elems[elems.size - 1] is XmlStringBuilderText
            val localBuilder: XmlStringBuilder = XmlStringBuilderWithoutHeader(identStep, ident)
            XmlValue.writeXml(
                entry.value, entry.key.toString(),
                localBuilder,
                parentTextFound,
                namespaces,
                false,
                arrayTrue
            )
            if (addNewLine) {
                localBuilder.newLine()
            }
            elems.add(localBuilder)
        }

        private fun addComment(
            entry: Map.Entry<*, *>,
            identStep: XmlStringBuilder.Step,
            ident: Int,
            parentTextFound: Boolean,
            addNewLine: Boolean,
            elems: MutableList
        ) {
            if (entry.value is List<*>) {
                val iterator = (entry.value as List<*>).iterator()
                while (iterator.hasNext()) {
                    elems.add(
                        addCommentValue(
                            identStep,
                            ident, iterator.next().toString(),
                            parentTextFound,
                            iterator.hasNext() || addNewLine
                        )
                    )
                }
            } else {
                elems.add(
                    addCommentValue(
                        identStep,
                        ident, entry.value.toString(),
                        parentTextFound,
                        addNewLine
                    )
                )
            }
        }

        private fun addCommentValue(
            identStep: XmlStringBuilder.Step,
            ident: Int,
            value: String,
            parentTextFound: Boolean,
            addNewLine: Boolean
        ): XmlStringBuilder {
            val localBuilder: XmlStringBuilder = XmlStringBuilderWithoutHeader(identStep, ident)
            if (!parentTextFound) {
                localBuilder.fillSpaces()
            }
            localBuilder.append("")
            if (addNewLine) {
                localBuilder.newLine()
            }
            return localBuilder
        }

        private fun addCdata(
            entry: Map.Entry<*, *>,
            identStep: XmlStringBuilder.Step,
            ident: Int,
            addNewLine: Boolean,
            elems: MutableList
        ) {
            if (entry.value is List<*>) {
                val iterator = (entry.value as List<*>).iterator()
                while (iterator.hasNext()) {
                    elems.add(
                        addCdataValue(
                            identStep,
                            ident, iterator.next().toString(),
                            iterator.hasNext() || addNewLine
                        )
                    )
                }
            } else {
                elems.add(
                    addCdataValue(
                        identStep, ident, entry.value.toString(), addNewLine
                    )
                )
            }
        }

        private fun addCdataValue(
            identStep: XmlStringBuilder.Step, ident: Int, value: String, addNewLine: Boolean
        ): XmlStringBuilder {
            val localBuilder: XmlStringBuilder = XmlStringBuilderText(identStep, ident)
            localBuilder.append("")
            if (addNewLine) {
                localBuilder.newLine()
            }
            return localBuilder
        }
    }

    object XmlValue {
        fun writeXml(
            value: Any?,
            name: String?,
            builder: XmlStringBuilder,
            parentTextFound: Boolean,
            namespaces: MutableSet,
            addArray: Boolean,
            arrayTrue: String
        ) {
            if (value is Map<*, *>) {
                XmlObject.writeXml(
                    value as Map<*, *>?,
                    name,
                    builder,
                    parentTextFound,
                    namespaces,
                    addArray,
                    arrayTrue
                )
                return
            }
            if (value is Collection<*>) {
                XmlArray.writeXml(
                    value as Collection<*>?,
                    name,
                    builder,
                    parentTextFound,
                    namespaces,
                    addArray,
                    arrayTrue
                )
                return
            }
            if (!parentTextFound) {
                builder.fillSpaces()
            }
            if (value == null) {
                builder.append("<" + escapeName(name, namespaces) + NULL_TRUE)
            } else if (value is String) {
                if (value.isEmpty()) {
                    builder.append(
                        "<"
                                + escapeName(name, namespaces)
                                + if (addArray) arrayTrue else ""
                    )
                    if (name!!.startsWith("?")) {
                        builder.append("?>")
                    } else {
                        builder.append(" string=\"true\"/>")
                    }
                } else {
                    builder.append(
                        "<"
                                + escapeName(name, namespaces)
                                + (if (addArray) arrayTrue else "")
                                + if (name!!.startsWith("?")) " " else ">"
                    )
                    builder.append(escape(value as String?))
                    if (name.startsWith("?")) {
                        builder.append("?>")
                    } else {
                        builder.append("")
                    }
                }
            } else {
                processArrays(
                    value, builder, name, parentTextFound, namespaces, addArray, arrayTrue
                )
            }
        }

        private fun processArrays(
            value: Any,
            builder: XmlStringBuilder,
            name: String?,
            parentTextFound: Boolean,
            namespaces: MutableSet,
            addArray: Boolean,
            arrayTrue: String
        ) {
            if (value is Double) {
                if (value.isInfinite() || value.isNaN()) {
                    builder.append(NULL_ELEMENT)
                } else {
                    builder.append(
                        "<"
                                + escapeName(name, namespaces)
                                + (if (addArray) arrayTrue else "")
                                + NUMBER_TRUE
                    )
                    builder.append(value.toString())
                    builder.append("")
                }
            } else if (value is Float) {
                if (value.isInfinite() || value.isNaN()) {
                    builder.append(NULL_ELEMENT)
                } else {
                    builder.append("<" + escapeName(name, namespaces) + NUMBER_TRUE)
                    builder.append(value.toString())
                    builder.append("")
                }
            } else if (value is Number) {
                builder.append(
                    "<"
                            + escapeName(name, namespaces)
                            + (if (addArray) arrayTrue else "")
                            + NUMBER_TRUE
                )
                builder.append(value.toString())
                builder.append("")
            } else if (value is Boolean) {
                builder.append(
                    "<"
                            + escapeName(name, namespaces)
                            + (if (addArray) arrayTrue else "")
                            + " boolean=\"true\">"
                )
                builder.append(value.toString())
                builder.append("")
            } else {
                builder.append("<" + escapeName(name, namespaces) + ">")
                if (value is ByteArray) {
                    builder.newLine().incIdent()
                    XmlArray.writeXml(value, builder)
                    builder.decIdent().newLine().fillSpaces()
                } else if (value is ShortArray) {
                    builder.newLine().incIdent()
                    XmlArray.writeXml(value, builder)
                    builder.decIdent().newLine().fillSpaces()
                } else {
                    processArrays2(value, builder, name, parentTextFound, namespaces, arrayTrue)
                }
                builder.append("")
            }
        }

        @Suppress("UNCHECKED_CAST")
        private fun processArrays2(
            value: Any,
            builder: XmlStringBuilder,
            name: String?,
            parentTextFound: Boolean,
            namespaces: MutableSet,
            arrayTrue: String
        ) {
            if (value is IntArray) {
                builder.newLine().incIdent()
                XmlArray.writeXml(value, builder)
                builder.decIdent().newLine().fillSpaces()
            } else if (value is LongArray) {
                builder.newLine().incIdent()
                XmlArray.writeXml(value, builder)
                builder.decIdent().newLine().fillSpaces()
            } else if (value is FloatArray) {
                builder.newLine().incIdent()
                XmlArray.writeXml(value, builder)
                builder.decIdent().newLine().fillSpaces()
            } else if (value is DoubleArray) {
                builder.newLine().incIdent()
                XmlArray.writeXml(value, builder)
                builder.decIdent().newLine().fillSpaces()
            } else if (value is BooleanArray) {
                builder.newLine().incIdent()
                XmlArray.writeXml(value, builder)
                builder.decIdent().newLine().fillSpaces()
            } else if (value is CharArray) {
                builder.newLine().incIdent()
                XmlArray.writeXml(value, builder)
                builder.decIdent().newLine().fillSpaces()
            } else if (value is Array<*> && value.isArrayOf()) {
                builder.newLine().incIdent()
                XmlArray.writeXml(
                    value as Array, name, builder, parentTextFound, namespaces, arrayTrue
                )
                builder.decIdent().newLine().fillSpaces()
            } else {
                builder.append(value.toString())
            }
        }

        fun escapeName(name: String?, namespaces: Set): String {
            val length = name!!.length
            if (length == 0) {
                return "__EE__EMPTY__EE__"
            }
            val result = StringBuilder()
            var ch = name[0]
            if (ch != ':') {
                try {
                    if (ch != '?') {
                        DOCUMENT.createElement(ch.toString())
                    }
                    result.append(ch)
                } catch (ex: Exception) {
                    result.append("__").append(encode(Character.toString(ch))).append("__")
                }
            } else {
                result.append("__").append(encode(Character.toString(ch))).append("__")
            }
            for (i in 1 until length) {
                ch = name[i]
                if (ch == ':'
                    && ("xmlns" == name.substring(0, i) || namespaces.contains(name.substring(0, i)))
                ) {
                    result.append(ch)
                } else if (ch != ':') {
                    try {
                        DOCUMENT.createElement("a$ch")
                        result.append(ch)
                    } catch (ex: Exception) {
                        result.append("__")
                            .append(encode(Character.toString(ch)))
                            .append("__")
                    }
                } else {
                    result.append("__").append(encode(Character.toString(ch))).append("__")
                }
            }
            return result.toString()
        }

        @JvmStatic
        fun escape(s: String?): String {
            if (s == null) {
                return ""
            }
            val sb = StringBuilder()
            escape(s, sb)
            return sb.toString()
        }

        private fun escape(s: String, sb: StringBuilder) {
            val len = s.length
            for (i in 0 until len) {
                when (val ch = s[i]) {
                    '\'' -> sb.append("'")
                    '&' -> sb.append("&")
                    '<' -> sb.append("<")
                    '>' -> sb.append(">")
                    '\b' -> sb.append("\\b")
                    '\u000c' -> sb.append("\\f")
                    '\n' -> sb.append("\n")
                    '\r' -> sb.append("
")
                    '\t' -> sb.append("\t")
                    '€' -> sb.append("€")
                    else -> if (ch <= '\u001F' || ch in '\u007F'..'\u009F' || ch in '\u2000'..'\u20FF') {
                        val ss = Integer.toHexString(ch.code)
                        sb.append("&#x")
                        sb.append("0".repeat(4 - ss.length))
                        sb.append(ss.uppercase(Locale.getDefault())).append(";")
                    } else {
                        sb.append(ch)
                    }
                }
            }
        }

        @JvmStatic
        fun unescape(s: String?): String {
            if (s == null) {
                return ""
            }
            val sb = StringBuilder()
            unescape(s, sb)
            return sb.toString()
        }

        private fun unescape(s: String, sb: StringBuilder) {
            val len = s.length
            val localSb = StringBuilder()
            var index = 0
            while (index < len) {
                val skipChars = translate(s, index, localSb)
                index += if (skipChars > 0) {
                    sb.append(localSb)
                    localSb.setLength(0)
                    skipChars
                } else {
                    sb.append(s[index])
                    1
                }
            }
        }

        private fun translate(
            input: CharSequence, index: Int, builder: StringBuilder
        ): Int {
            val shortest = 4
            val longest = 6
            if ('&' == input[index]) {
                var max = longest
                if (index + longest > input.length) {
                    max = input.length - index
                }
                for (i in max downTo shortest) {
                    val subSeq = input.subSequence(index, index + i)
                    val result = XML_UNESCAPE[subSeq.toString()]
                    if (result != null) {
                        builder.append(result)
                        return i
                    }
                }
            }
            return 0
        }

        @JvmStatic
        fun getMapKey(map: Any?): String {
            return if (map is Map<*, *> && map.isNotEmpty())
                (map.entries.iterator().next() as Map.Entry<*, *>).key.toString() else ""
        }

        @JvmStatic
        fun getMapValue(map: Any?): Any? {
            return if (map is Map<*, *> && map.isNotEmpty()) (map.entries.iterator()
                .next() as Map.Entry<*, *>).value else null
        }
    }

    enum class FromType {
        FOR_CONVERT,
        FOR_FORMAT
    }

    private class MyEntityResolver : EntityResolver {
        override fun resolveEntity(publicId: String?, systemId: String?): InputSource {
            return InputSource(StringReader(""))
        }
    }

    object Document {
        @JvmStatic
        @Throws(IOException::class, ParserConfigurationException::class, SAXException::class)
        fun createDocument(xml: String): org.w3c.dom.Document {
            val factory = DocumentBuilderFactory.newInstance()
            factory.isNamespaceAware = true
            try {
                factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true)
            } catch (ignored: Exception) {
                // ignored
            }
            val builder = factory.newDocumentBuilder()
            builder.setErrorHandler(DefaultHandler())
            builder.setEntityResolver(MyEntityResolver())
            return builder.parse(InputSource(StringReader(xml)))
        }

        fun createDocument(): org.w3c.dom.Document {
            return try {
                val factory = DocumentBuilderFactory.newInstance()
                factory.isNamespaceAware = true
                setupFactory(factory)
                val builder = factory.newDocumentBuilder()
                builder.newDocument()
            } catch (ex: ParserConfigurationException) {
                throw IllegalArgumentException(ex)
            }
        }

        private fun setupFactory(factory: DocumentBuilderFactory) {
            try {
                factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true)
            } catch (ignored: Exception) {
                // ignored
            }
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy