diff --git a/butterflycore/src/main/java/com/buttongames/butterflycore/xml/kbinxml/ByteConv.kt b/butterflycore/src/main/java/com/buttongames/butterflycore/xml/kbinxml/ByteConv.kt index b4d154e..66a811f 100644 --- a/butterflycore/src/main/java/com/buttongames/butterflycore/xml/kbinxml/ByteConv.kt +++ b/butterflycore/src/main/java/com/buttongames/butterflycore/xml/kbinxml/ByteConv.kt @@ -1,11 +1,11 @@ package com.buttongames.butterflycore.xml.kbinxml import com.buttongames.butterflycore.xml.kbinxml.ByteConv.E.longToBytes - import java.lang.Double import java.lang.Float import java.math.BigInteger import java.nio.ByteBuffer +import java.util.* import kotlin.experimental.and internal class ByteConv { @@ -67,8 +67,8 @@ internal class ByteConv { fun stringToIp(string: String) = string.split(".").map { it.toUByte().toByte() }.toByteArray() - fun floatToString(array: ByteArray) = String.format("%.6f", ByteBuffer.wrap(array).float) - fun doubleToString(array: ByteArray) = String.format("%.6f", ByteBuffer.wrap(array).double) + fun floatToString(array: ByteArray) = String.format(Locale.US, "%.6f", ByteBuffer.wrap(array).float) + fun doubleToString(array: ByteArray) = String.format(Locale.US, "%.6f", ByteBuffer.wrap(array).double) fun stringToFloat(string: String) = Float.floatToRawIntBits(Float.parseFloat(string)).toByteArray() fun stringToDouble(string: String) = Double.doubleToRawLongBits(Double.parseDouble(string)).toByteArray() @@ -98,4 +98,6 @@ internal fun String.toLongBytes() = BigInteger(this).toLong().toByteArray() internal inline fun String.toUByteA() = this.toByteA() internal inline fun String.toUShortBytes() = this.toShortBytes() internal inline fun String.toUIntBytes() = this.toIntBytes() -internal inline fun String.toULongBytes() = this.toLongBytes() \ No newline at end of file +internal inline fun String.toULongBytes() = this.toLongBytes() + +internal inline fun Byte.posInt() = this.toUByte().toInt() \ No newline at end of file diff --git a/butterflycore/src/main/java/com/buttongames/butterflycore/xml/kbinxml/Constants.kt b/butterflycore/src/main/java/com/buttongames/butterflycore/xml/kbinxml/Constants.kt index 14780aa..787259d 100644 --- a/butterflycore/src/main/java/com/buttongames/butterflycore/xml/kbinxml/Constants.kt +++ b/butterflycore/src/main/java/com/buttongames/butterflycore/xml/kbinxml/Constants.kt @@ -23,19 +23,29 @@ internal enum class ControlTypes { } internal val ControlTypeMap = mapOf( - 1 to ControlTypes.NodeStart, - 46 to ControlTypes.Attribute, - 190 to ControlTypes.NodeEnd, - 191 to ControlTypes.FileEnd - //254 to ControlTypes.NodeEnd, - //255 to ControlTypes.FileEnd + 1 to ControlTypes.NodeStart, + 46 to ControlTypes.Attribute, + 190 to ControlTypes.NodeEnd, + 191 to ControlTypes.FileEnd + //254 to ControlTypes.NodeEnd, + //255 to ControlTypes.FileEnd ) internal class KbinConverter(val fromString: (String) -> ByteArray, val toString: (ByteArray) -> String) -internal class KbinType(var names: List, val size: Int, private val handler: KbinConverter, val count: Int = 1) { +internal class KbinType( + var names: List, + val size: Int, + private val handler: KbinConverter, + val count: Int = 1 +) { - constructor(name: String, size: Int, handler: KbinConverter, count: Int = 1) : this(mutableListOf(name), size, handler, count) + constructor(name: String, size: Int, handler: KbinConverter, count: Int = 1) : this( + mutableListOf(name), + size, + handler, + count + ) val name: String get() = names[0] @@ -60,10 +70,10 @@ internal operator fun Int.times(t: KbinType): KbinType { val newNames = t.names.map { this.toString() + it } val newSize = t.size * this fun newToString(input: ByteArray) = - input.asIterable().chunked(t.size).joinToString(" ") { t.toString(it.toByteArray()) } + input.asIterable().chunked(t.size).joinToString(" ") { t.toString(it.toByteArray()) } fun newFromString(input: String) = - input.split(" ").flatMap { t.fromString(it).asIterable() }.toByteArray() + input.split(" ").flatMap { t.fromString(it).asIterable() }.toByteArray() return KbinType(newNames, newSize, KbinConverter(::newFromString, ::newToString), this * t.count) } @@ -79,7 +89,7 @@ internal class Types { val s64 = KbinType("s64", 8, Converters.s64) val u64 = KbinType("u64", 8, Converters.u64) - val bin = KbinType(listOf("bin", "binary"), 1, Converters.stub) + val binStub = KbinType(listOf("bin", "binary"), 1, Converters.stub) val strStub = KbinType(listOf("str", "string"), 1, Converters.stub) val ip4 = KbinType("ip4", 4, Converters.ip4) @@ -92,60 +102,61 @@ internal class Types { internal val kbinTypeMap = with(Types) { mapOf( - 2 to s8, - 3 to u8, - 4 to s16, - 5 to u16, - 6 to s32, - 7 to u32, - 8 to s64, - 9 to u64, - 10 to bin, - 11 to strStub, - 12 to ip4, - 13 to time, - 14 to float, - 15 to double, - 16 to 2 * s8, - 17 to 2 * u8, - 18 to 2 * s16, - 19 to 2 * u16, - 20 to 2 * s32, - 21 to 2 * u32, - 22 to (2 * s64).alias("vs64"), - 23 to (2 * u64).alias("vu64"), - 24 to (2 * float).rename("2f"), - 25 to (2 * double).rename("2d").alias("vd"), - 26 to 3 * s8, - 27 to 3 * u8, - 28 to 3 * s16, - 29 to 3 * u16, - 30 to 3 * s32, - 31 to 3 * u32, - 32 to 3 * s64, - 33 to 3 * u64, - 34 to (3 * float).rename("3f"), - 35 to (3 * double).rename("3d"), - 36 to 4 * s8, - 37 to 4 * u8, - 38 to 4 * s16, - 39 to 4 * u16, - 40 to (4 * s32).alias("vs32"), - 41 to (4 * u32).alias("vu32"), - 42 to 4 * s64, - 43 to 4 * u64, - 44 to (4 * float).rename("4f").alias("vf"), - 45 to (4 * double).rename("4d"), - 48 to (16 * s8).rename("vs8"), - 49 to (16 * u8).rename("vu8"), - 50 to (8 * s16).rename("vs16"), - 51 to (8 * u16).rename("vu16"), - 52 to bool, - 53 to (2 * bool).rename("2b"), - 54 to (3 * bool).rename("3b"), - 55 to (4 * bool).rename("4b"), - 56 to (16 * bool).rename("vb") + 2 to s8, + 3 to u8, + 4 to s16, + 5 to u16, + 6 to s32, + 7 to u32, + 8 to s64, + 9 to u64, + 10 to binStub, + 11 to strStub, + 12 to ip4, + 13 to time, + 14 to float, + 15 to double, + 16 to 2 * s8, + 17 to 2 * u8, + 18 to 2 * s16, + 19 to 2 * u16, + 20 to 2 * s32, + 21 to 2 * u32, + 22 to (2 * s64).alias("vs64"), + 23 to (2 * u64).alias("vu64"), + 24 to (2 * float).rename("2f"), + 25 to (2 * double).rename("2d").alias("vd"), + 26 to 3 * s8, + 27 to 3 * u8, + 28 to 3 * s16, + 29 to 3 * u16, + 30 to 3 * s32, + 31 to 3 * u32, + 32 to 3 * s64, + 33 to 3 * u64, + 34 to (3 * float).rename("3f"), + 35 to (3 * double).rename("3d"), + 36 to 4 * s8, + 37 to 4 * u8, + 38 to 4 * s16, + 39 to 4 * u16, + 40 to (4 * s32).alias("vs32"), + 41 to (4 * u32).alias("vu32"), + 42 to 4 * s64, + 43 to 4 * u64, + 44 to (4 * float).rename("4f").alias("vf"), + 45 to (4 * double).rename("4d"), + 48 to (16 * s8).rename("vs8"), + 49 to (16 * u8).rename("vu8"), + 50 to (8 * s16).rename("vs16"), + 51 to (8 * u16).rename("vu16"), + 52 to bool, + 53 to (2 * bool).rename("2b"), + 54 to (3 * bool).rename("3b"), + 55 to (4 * bool).rename("4b"), + 56 to (16 * bool).rename("vb") ) } -internal var reverseKbinTypeMap: Map = kbinTypeMap.entries.associateBy({ it.value.name }) { it.key } \ No newline at end of file +// internal var reverseKbinTypeMap: Map = kbinTypeMap.entries.associateBy({ it.value.name }) { it.key } +internal var reverseKbinTypeMap = mutableMapOf() \ No newline at end of file diff --git a/butterflycore/src/main/java/com/buttongames/butterflycore/xml/kbinxml/KbinDataBuffer.kt b/butterflycore/src/main/java/com/buttongames/butterflycore/xml/kbinxml/KbinDataBuffer.kt index 7ab819e..9004e7d 100644 --- a/butterflycore/src/main/java/com/buttongames/butterflycore/xml/kbinxml/KbinDataBuffer.kt +++ b/butterflycore/src/main/java/com/buttongames/butterflycore/xml/kbinxml/KbinDataBuffer.kt @@ -35,8 +35,8 @@ internal class KbinDataBuffer(bytes: ByteArray, val encoding: Charset) { } else -> throw KbinException("Invalid read of $num bytes") } - realign(num) // println("Read bytes $debug") + realign(num) return result } @@ -67,15 +67,18 @@ internal class KbinDataBuffer(bytes: ByteArray, val encoding: Charset) { if (pos16Follows()) { pos16 = pos32 } - //println("Index 4: $pos32") - //println("Index 2: $pos16") - //println("Index 1: $pos8") + /*println("Index 4: $pos32") + println("Index 2: $pos16") + println("Index 1: $pos8") + println()*/ } fun realign4Byte(num: Int) { - realign(num + (if (num % 4 != 0) { - 4 - (num % 4) - } else 0)) + realign( + num + (if (num % 4 != 0) { + 4 - (num % 4) + } else 0) + ) } fun reset() { @@ -100,15 +103,14 @@ internal class KbinDataBuffer(bytes: ByteArray, val encoding: Charset) { fun readFrom4Byte(num: Int): ByteArray { if (num == 0) return byteArrayOf() val read = data.slice(pos32 until pos32 + num) + // println("Read bytes $pos32 - ${pos32 + num}") realign4Byte(num) return read.toByteArray() } fun readString(length: Int): String { var readBytes = readFrom4Byte(length) - if (readBytes.last() == 0x00.toByte()) { // null bytes are scary - readBytes = readBytes.sliceArray(0 until readBytes.lastIndex) - } + readBytes = readBytes.sliceArray(0 until readBytes.lastIndex) return readBytes.toString(encoding) } @@ -146,7 +148,7 @@ internal class KbinDataBuffer(bytes: ByteArray, val encoding: Charset) { } fun writeString(string: String) { - var bytes = string.toByteArray(encoding) + 0 // null byte + val bytes = string.toByteArray(encoding) + 0 // null byte writeU32(bytes.size.toUInt()) writeTo4Byte(bytes) } diff --git a/butterflycore/src/main/java/com/buttongames/butterflycore/xml/kbinxml/KbinReader.kt b/butterflycore/src/main/java/com/buttongames/butterflycore/xml/kbinxml/KbinReader.kt index 9f59557..4cea856 100644 --- a/butterflycore/src/main/java/com/buttongames/butterflycore/xml/kbinxml/KbinReader.kt +++ b/butterflycore/src/main/java/com/buttongames/butterflycore/xml/kbinxml/KbinReader.kt @@ -1,12 +1,16 @@ package com.buttongames.butterflycore.xml.kbinxml -import nu.xom.Document -import nu.xom.Element import com.buttongames.butterflycore.xml.kbinxml.ControlTypes.* +import com.buttongames.butterflycore.xml.kbinxml.Types.Companion.binStub +import com.buttongames.butterflycore.xml.kbinxml.Types.Companion.strStub +import org.w3c.dom.Document +import org.w3c.dom.Element import java.nio.charset.Charset import java.util.* +import javax.xml.parsers.DocumentBuilderFactory import kotlin.experimental.inv + internal class KbinReader(data: ByteArray) { init { @@ -48,6 +52,11 @@ internal class KbinReader(data: ByteArray) { val nodeStack = ArrayDeque() var end = false + val dbf = DocumentBuilderFactory.newInstance() + val builder = dbf.newDocumentBuilder() + val doc = builder.newDocument() + doc.xmlStandalone = true + while (!end) { val current = nodeBuffer.readU8().toInt() val actual = current and (1 shl 6).inv() @@ -59,7 +68,7 @@ internal class KbinReader(data: ByteArray) { NodeStart -> { val name = nodeBuffer.readString() - val newNode = Element(name) + val newNode = doc.createElement(name) if (currentNode != null) { currentNode.appendChild(newNode) nodeStack.push(currentNode) @@ -81,7 +90,7 @@ internal class KbinReader(data: ByteArray) { //println("Got attribute $name with value $value") - currentNode!!.addAttribute(name, value) + currentNode!!.setAttribute(name, value) } FileEnd -> { if (nodeStack.size == 0) { @@ -95,24 +104,25 @@ internal class KbinReader(data: ByteArray) { val nodeName = nodeBuffer.readString() val arraySize = if (isArray) dataBuffer.readU32().toInt() else valueType.size - nodeStack.push(currentNode) - val newNode = Element(nodeName) - newNode.addAttribute("__type", valueName) - currentNode!!.appendChild(newNode) + nodeStack.push(currentNode!!) + val newNode = doc.createElement(nodeName) + newNode.setAttribute("__type", valueName) + currentNode.appendChild(newNode) currentNode = newNode val numElements = arraySize / valueType.size - when (valueName) { - "bin" -> { - currentNode.addAttribute("__size", arraySize.toString()) + + when (valueType) { + binStub -> { + currentNode.setAttribute("__size", arraySize.toString()) val bytes = dataBuffer.readFrom4Byte(arraySize) currentNode.text = ByteConv.binToString(bytes) } - "str" -> { + strStub -> { currentNode.text = dataBuffer.readString(arraySize) } else -> { - if (isArray) currentNode.addAttribute("__count", numElements.toString()) + if (isArray) currentNode.setAttribute("__count", numElements.toString()) val byteList = dataBuffer.readBytes(arraySize) val stringList = mutableListOf() @@ -128,6 +138,7 @@ internal class KbinReader(data: ByteArray) { throw KbinException("Unsupported node type with ID $actual") } } - return Document(currentNode) + doc.appendChild(currentNode) + return doc } } \ No newline at end of file diff --git a/butterflycore/src/main/java/com/buttongames/butterflycore/xml/kbinxml/KbinWriter.kt b/butterflycore/src/main/java/com/buttongames/butterflycore/xml/kbinxml/KbinWriter.kt index d3dbfa9..caeeed3 100644 --- a/butterflycore/src/main/java/com/buttongames/butterflycore/xml/kbinxml/KbinWriter.kt +++ b/butterflycore/src/main/java/com/buttongames/butterflycore/xml/kbinxml/KbinWriter.kt @@ -1,26 +1,38 @@ package com.buttongames.butterflycore.xml.kbinxml -import nu.xom.Document -import nu.xom.Element +import com.buttongames.butterflycore.xml.kbinxml.Types.Companion.binStub +import com.buttongames.butterflycore.xml.kbinxml.Types.Companion.strStub +import org.w3c.dom.Attr +import org.w3c.dom.Document +import org.w3c.dom.Element +import org.w3c.dom.Node import java.io.ByteArrayOutputStream import java.nio.charset.Charset import kotlin.experimental.inv -internal class KbinWriter(val xml: Document, val encoding: String = "SHIFT_JIS", val compressed: Boolean = true) { + +internal class KbinWriter(val xml: Document, val encoding: String = "UTF-8", val compressed: Boolean = true) { private val header = ByteArray(4) val charset = Charset.forName(encoding) fun getKbin(): ByteArray { + if (reverseKbinTypeMap.isEmpty()) { + for (entry in kbinTypeMap.entries) { + for (name in entry.value.names) { + reverseKbinTypeMap[name] = entry.key + } + } + } val dataBuffer = KbinDataBuffer(charset) val nodeBuffer = KbinNodeBuffer(compressed, charset) - header[0] = 0xa0u.toByte() + header[0] = 0xA0u.toByte() header[1] = (if (compressed) 0x42u else 0x45u).toByte() header[2] = (Constants.encodingsReverse[encoding]!! shl 5).toByte() header[3] = header[2].inv() - nodeRecurse(xml.rootElement, dataBuffer, nodeBuffer) + nodeRecurse(xml.documentElement, dataBuffer, nodeBuffer) nodeBuffer.writeU8(255u) nodeBuffer.pad() @@ -36,13 +48,27 @@ internal class KbinWriter(val xml: Document, val encoding: String = "SHIFT_JIS", } private fun nodeRecurse(e: Element, dataBuffer: KbinDataBuffer, nodeBuffer: KbinNodeBuffer) { - val typeName = e.getAttribute("__type")?.value + var typeName = e.getAttribute("__type") + val text = e.text + if (typeName == "" && text.trim() != "") { + typeName = "str" + } val typeId = reverseKbinTypeMap[typeName] - if (typeName != null && typeId == null) throw KbinException("Type $typeName is not supported") + if (typeName != "" && typeId == null) + throw KbinException("Type $typeName is not supported") val type = kbinTypeMap[typeId] - val count = (e.getAttribute("__count")?.value ?: e.getAttribute("__size")?.value)?.toInt() - val isArray = (count != null) && type?.name !in listOf("bin", "str") + var count = run { + val count_attr = e.getAttribute("__count") + val size_attr = e.getAttribute("__size") + when { + count_attr != "" -> count_attr + size_attr != "" -> size_attr + else -> null + }?.toInt() + } + + val isArray = (count != null) && type?.name !in strStub.names + binStub.names if (typeId == null) { nodeBuffer.writeU8(1u) @@ -53,45 +79,39 @@ internal class KbinWriter(val xml: Document, val encoding: String = "SHIFT_JIS", } nodeBuffer.writeU8(toWrite) } - nodeBuffer.writeString(e.localName) + nodeBuffer.writeString(e.nodeName) + if (type != null) { - if (type.name == "bin") { - if (count != null) { - dataBuffer.writeU32(count.toUInt()) - } - val toWrite = ByteConv.stringToBin(e.text) + if (type == binStub) { + count = text.length / 2 + dataBuffer.writeU32(count.toUInt()) + val toWrite = ByteConv.stringToBin(text) dataBuffer.writeTo4Byte(toWrite) - } else if (type.name == "str") { - dataBuffer.writeString(e.text) + } else if (type == strStub) { + dataBuffer.writeString(text) } else { if (count != null) { dataBuffer.writeU32((count * type.size).toUInt()) } - val split = e.text.splitAndJoin(type.count) + val split = text.splitAndJoin(type.count) + //val toWrite = split.flatMap { type.fromString(it).asIterable() }.toByteArray() val toWrite = split.flatMap { type.fromString(it).asIterable() }.toByteArray() dataBuffer.writeBytes(toWrite) } } - /*for (a in e) { - val name = a.localName - if (name in arrayOf("__count", "__size", "__type")) - continue - val value = a.value - nodeBuffer.writeU8(46u) - nodeBuffer.writeString(name) - dataBuffer.writeString(value) - }*/ - val attributes = e.iterator().asSequence() - .filter { it.localName !in listOf("__type", "__count", "__size") } - .sortedBy { it.localName } + + val attributes = e.attributeList.filter { it.nodeName !in listOf("__type", "__count", "__size") } + .sortedBy { it.nodeName } for (a in attributes) { + a as Attr nodeBuffer.writeU8(46u) - nodeBuffer.writeString(a.localName) + nodeBuffer.writeString(a.nodeName) dataBuffer.writeString(a.value) } - for (c in e.childElements) { - nodeRecurse(c, dataBuffer, nodeBuffer) + + for (thing in e.childNodes(Node.ELEMENT_NODE)) { + nodeRecurse(thing as Element, dataBuffer, nodeBuffer) } nodeBuffer.writeU8(254u) } diff --git a/butterflycore/src/main/java/com/buttongames/butterflycore/xml/kbinxml/Public.kt b/butterflycore/src/main/java/com/buttongames/butterflycore/xml/kbinxml/Public.kt index 6edcd47..98dda75 100644 --- a/butterflycore/src/main/java/com/buttongames/butterflycore/xml/kbinxml/Public.kt +++ b/butterflycore/src/main/java/com/buttongames/butterflycore/xml/kbinxml/Public.kt @@ -1,10 +1,9 @@ package com.buttongames.butterflycore.xml.kbinxml -import nu.xom.Builder -import nu.xom.Document +import org.w3c.dom.Document -fun kbinEncode(d: Document) = KbinWriter(d).getKbin() -fun kbinEncode(s: String) = kbinEncode(Builder().build(s, null)) +fun kbinEncode(d: Document, charset: String = "UTF-8") = KbinWriter(d, charset).getKbin() +fun kbinEncode(s: String, charset: String = "UTF-8") = kbinEncode(s.toXml(), charset) fun kbinDecode(b: ByteArray) = KbinReader(b).getXml() diff --git a/butterflycore/src/main/java/com/buttongames/butterflycore/xml/kbinxml/Util.kt b/butterflycore/src/main/java/com/buttongames/butterflycore/xml/kbinxml/Util.kt index 2275ec2..2f0b40d 100644 --- a/butterflycore/src/main/java/com/buttongames/butterflycore/xml/kbinxml/Util.kt +++ b/butterflycore/src/main/java/com/buttongames/butterflycore/xml/kbinxml/Util.kt @@ -1,7 +1,5 @@ package com.buttongames.butterflycore.xml.kbinxml -import nu.xom.Attribute -import nu.xom.Element import java.nio.charset.Charset fun byteArrayOfInts(vararg ints: Int) = ByteArray(ints.size) { pos -> ints[pos].toByte() } @@ -17,10 +15,6 @@ internal fun ByteArray.unsigned(): UByteArray { class KbinException internal constructor(override var message: String) : Exception(message) -internal fun Element.addAttribute(key: String, value: String) { - this.addAttribute(Attribute(key, value)) -} - internal fun MutableList.setOrAdd(pos: Int, byte: Byte) { if (pos == this.size) { this.add(byte) @@ -42,14 +36,18 @@ internal fun MutableList.setOrAddAll(pos: Int, bytes: ByteArray) { internal fun ByteArray.toString(encoding: String) = this.toString(Charset.forName(encoding)) -internal fun String.splitAndJoin(count: Int): Array { +/*internal fun String.splitAndJoin(count: Int): Array { val input = this.split(" ") val output = Array(input.size / count) { "" } for (i in 0 until output.size) { output[i] = input.slice(i * count until (i + 1) * count).joinToString(" ") } return output -} +}*/ + +internal fun String.splitAndJoin(count: Int) = + this.split(" ").chunked(count).map { it.joinToString(" ") }.toTypedArray() + fun measureMs(times: Int = 1, function: () -> Unit) { for (i in 0 until times) { @@ -60,6 +58,4 @@ fun measureMs(times: Int = 1, function: () -> Unit) { val duration = endTime - startTime //divide by 1000000 to get milliseconds. println("Took ${duration / 1000000.0} ms") } -} - -internal inline fun Byte.posInt() = this.toUByte().toInt() \ No newline at end of file +} \ No newline at end of file diff --git a/butterflycore/src/main/java/com/buttongames/butterflycore/xml/kbinxml/XmlUtil.kt b/butterflycore/src/main/java/com/buttongames/butterflycore/xml/kbinxml/XmlUtil.kt index 012d13b..49a4fc1 100644 --- a/butterflycore/src/main/java/com/buttongames/butterflycore/xml/kbinxml/XmlUtil.kt +++ b/butterflycore/src/main/java/com/buttongames/butterflycore/xml/kbinxml/XmlUtil.kt @@ -1,77 +1,92 @@ package com.buttongames.butterflycore.xml.kbinxml -import nu.xom.* -import java.io.ByteArrayOutputStream +import org.w3c.dom.Attr +import org.w3c.dom.Document +import org.w3c.dom.Element +import org.w3c.dom.Node +import org.xml.sax.InputSource import java.io.File - -fun deepCompare(a: Document, b: Document): Boolean { - return deepCompare(a.copy().rootElement, b.copy().rootElement) -} - -private fun deepCompare(a: Element, b: Element): Boolean { - a.sortAttributes(); b.sortAttributes() - val nameA = a.localName - val nameB = b.localName - check(nameA == nameB) { "Element names are different: $nameA != $nameB" } - val attrItA = a.iterator() - val attrItB = b.iterator() - while (attrItA.hasNext()) { - val attrA = attrItA.next() - val attrB = attrItB.next() - check(attrA.localName == attrB.localName) { "Attribute names are different: ${attrA.localName} != ${attrB.localName}" } - if (attrB.value == "bib") - println("break") - check(attrA.value == attrB.value) { "Attribute values are different for \"${attrA.localName}\": ${attrA.value} != ${attrB.value}" } - } - if (a.text != "") { - check(a.text == b.text) { "Text inside of tags does not match: ${a.value} != ${b.value}" } - } - val elemItA = a.childElements.iterator() - val elemItB = b.childElements.iterator() - while (elemItA.hasNext()) { - deepCompare(elemItA.next(), elemItB.next()) - } - return true -} - -fun Document.prettyString(): String { - val o = ByteArrayOutputStream() - val serializer = Serializer(o, "UTF-8") - serializer.setIndent(4) - serializer.write(this) - return o.toString("UTF-8") -} +import java.io.StringReader +import java.io.StringWriter +import javax.xml.parsers.DocumentBuilderFactory +import javax.xml.transform.OutputKeys +import javax.xml.transform.TransformerFactory +import javax.xml.transform.dom.DOMSource +import javax.xml.transform.stream.StreamResult var Element.text: String - get() { - for (i in 0 until childCount) { - val e = getChild(i) - if (e is Text) { - return e.value + get() = this.childNodes(Node.TEXT_NODE).firstOrNull()?.nodeValue ?: "" + set(value) { + val children = this.childNodes + for (i in 0 until children.length) { + val c = children.item(i) + if (c.nodeType == Node.TEXT_NODE) { + this.removeChild(c) } } - return "" - } - set(value: String) { - this.appendChild(value) + val e = this.ownerDocument.createTextNode(value) + this.appendChild(e) } - -internal fun Element.sortAttributesRec(): Element { - this.sortAttributes() - for (e in this.childElements) { - sortAttributesRecE(e) - } - return this -} - -private fun sortAttributesRecE(e: Element) { - e.sortAttributes() - for (b in e.childElements) { - sortAttributesRecE(b) +fun Element.childNodes(type: Short): Sequence { + val children = this.childNodes + return sequence { + for (i in 0 until children.length) { + val c = children.item(i) + if (c.nodeType == type) { + yield(c) + } + } } } -fun String.toXml() = Builder().build(this, null)!! -fun File.toXml() = Builder().build(this) \ No newline at end of file +fun Element.firstChild(name: String): Element? { + var child: Node? = this.firstChild + while (child != null) { + if (child is Element && name == child.nodeName) return child + child = child.nextSibling + } + return null +} + +val Element.childElements: Sequence + get() { + return this.childNodes(Node.ELEMENT_NODE) as Sequence + } + +val Element.attributeList: List + get() { + val result = mutableListOf() + val attrs = this.attributes!! + for (i in 0 until attrs.length) { + result.add(attrs.item(i) as Attr) + } + return result + } + +fun Document.prettyString(indent: Int = 4): String { + this.xmlStandalone = true + val stringWriter = StringWriter() + val xmlOutput = StreamResult(stringWriter) + val transformerFactory = TransformerFactory.newInstance() + transformerFactory.setAttribute("indent-number", indent) + val transformer = transformerFactory.newTransformer() + + transformer.setOutputProperty(OutputKeys.INDENT, "yes") + transformer.setOutputProperty(OutputKeys.DOCTYPE_PUBLIC, ""); + transformer.transform(DOMSource(this), xmlOutput) + return xmlOutput.writer.toString() +} + +fun String.toXml(): Document { + val db = DocumentBuilderFactory.newInstance().newDocumentBuilder() + val input = InputSource() + input.characterStream = (StringReader(this)) + return db.parse(input) +} + +fun File.toXml(): Document { + val db = DocumentBuilderFactory.newInstance().newDocumentBuilder() + return db.parse(this) +} \ No newline at end of file diff --git a/butterflyserver/src/main/java/com/buttongames/butterflyserver/http/handlers/BaseRequestHandler.java b/butterflyserver/src/main/java/com/buttongames/butterflyserver/http/handlers/BaseRequestHandler.java index 5c975f2..c8706c1 100644 --- a/butterflyserver/src/main/java/com/buttongames/butterflyserver/http/handlers/BaseRequestHandler.java +++ b/butterflyserver/src/main/java/com/buttongames/butterflyserver/http/handlers/BaseRequestHandler.java @@ -83,7 +83,7 @@ public abstract class BaseRequestHandler { // convert them to binary XML if (!XmlUtils.isBinaryXML(respBytes)) { - respBytes = PublicKt.kbinEncode(new String(respBytes)); + respBytes = PublicKt.kbinEncode(new String(respBytes), "UTF-8"); } response.header(COMPRESSION_HEADER, "none"); diff --git a/lib/xom-1.3.0-SNAPSHOT.jar b/lib/xom-1.3.0-SNAPSHOT.jar deleted file mode 100644 index bd98757..0000000 Binary files a/lib/xom-1.3.0-SNAPSHOT.jar and /dev/null differ diff --git a/lib/xom-1.3.0-diff.txt b/lib/xom-1.3.0-diff.txt deleted file mode 100644 index 7909c71..0000000 --- a/lib/xom-1.3.0-diff.txt +++ /dev/null @@ -1,198 +0,0 @@ -diff --git a/src/nu/xom/Element.java b/src/nu/xom/Element.java -index a810cbc..50cee6a 100644 ---- a/src/nu/xom/Element.java -+++ b/src/nu/xom/Element.java -@@ -21,13 +21,7 @@ - - package nu.xom; - --import java.util.HashMap; --import java.util.HashSet; --import java.util.Iterator; --import java.util.Map; --import java.util.NoSuchElementException; --import java.util.Set; --import java.util.LinkedHashSet; -+import java.util.*; - - /** - *

-@@ -49,7 +43,7 @@ import java.util.LinkedHashSet; - * @version 1.3.0 - * - */ --public class Element extends ParentNode { -+public class Element extends ParentNode implements Iterable { - - private String localName; - private String prefix; -@@ -1839,6 +1833,23 @@ public class Element extends ParentNode { - - } - -+ @Override -+ public Iterator iterator() { -+ return new Iterator() { -+ int i = 0; -+ -+ @Override -+ public boolean hasNext() { -+ return (i < numAttributes && attributes[i] != null); -+ } -+ -+ @Override -+ public Attribute next() { -+ return attributes[i++]; -+ } -+ }; -+ } -+ - - private class AttributeIterator implements Iterator { - -@@ -1863,6 +1874,25 @@ public class Element extends ParentNode { - throw new UnsupportedOperationException(); - } - } -+ -+ public void sortAttributes() { -+ if (attributes == null) return; -+ Arrays.sort(attributes, new Comparator(){ -+ @Override -+ public int compare(Attribute t1, Attribute t2) { -+ if (t1 == null && t2 == null) { -+ return 0; -+ } -+ if (t1 == null) { -+ return 1; -+ } -+ if (t2 == null) { -+ return -1; -+ } -+ return t1.getLocalName().compareTo(t2.getLocalName()); -+ } -+ }); -+ } - - Iterator attributeIterator() { - -diff --git a/src/nu/xom/Elements.java b/src/nu/xom/Elements.java -index 1fd26ea..524cbb0 100644 ---- a/src/nu/xom/Elements.java -+++ b/src/nu/xom/Elements.java -@@ -22,6 +22,7 @@ - package nu.xom; - - import java.util.ArrayList; -+import java.util.Iterator; - import java.util.List; - - /** -@@ -38,7 +39,7 @@ import java.util.List; - * - * - */ --public final class Elements { -+public final class Elements implements Iterable { - - - private List elements = new ArrayList(1); -@@ -82,4 +83,25 @@ public final class Elements { - elements.add(element); - } - -+ @Override -+ public Iterator iterator() { -+ return new Iterator(){ -+ -+ int i = 0; -+ @Override -+ public boolean hasNext() { -+ return i < elements.size(); -+ } -+ -+ @Override -+ public Element next() { -+ return (Element)elements.get(i++); -+ } -+ -+ @Override -+ public void remove() { -+ -+ } -+ }; -+ } - } -\ No newline at end of file -diff --git a/src/nu/xom/Nodes.java b/src/nu/xom/Nodes.java -index a987783..9ca938f 100644 ---- a/src/nu/xom/Nodes.java -+++ b/src/nu/xom/Nodes.java -@@ -22,6 +22,7 @@ - package nu.xom; - - import java.util.ArrayList; -+import java.util.Iterator; - import java.util.List; - - /** -@@ -45,9 +46,9 @@ import java.util.List; - * @version 1.1b4 - * - */ --public final class Nodes { -+public final class Nodes implements Iterable { - -- private final List nodes; -+ private final List nodes; - - - /** -@@ -56,7 +57,7 @@ public final class Nodes { - *

- */ - public Nodes() { -- nodes = new ArrayList(); -+ nodes = new ArrayList(); - } - - -@@ -74,7 +75,7 @@ public final class Nodes { - if (node == null) { - throw new NullPointerException("Nodes content must be non-null"); - } -- nodes = new ArrayList(1); -+ nodes = new ArrayList(1); - nodes.add(node); - - } -@@ -113,7 +114,7 @@ public final class Nodes { - * negative or greater than or equal to the size of the list - */ - public Node get(int index) { -- return (Node) nodes.get(index); -+ return nodes.get(index); - } - - -@@ -131,7 +132,7 @@ public final class Nodes { - * negative or greater than or equal to the size of the list - */ - public Node remove(int index) { -- return (Node) nodes.remove(index); -+ return nodes.remove(index); - } - - -@@ -187,5 +188,9 @@ public final class Nodes { - return nodes.contains(node); - } - -- -+ -+ @Override -+ public Iterator iterator() { -+ return nodes.iterator(); -+ } - } -\ No newline at end of file