From d4e28b87234a5374a2f6139c58e16c9a014a5b1d Mon Sep 17 00:00:00 2001 From: soywiz Date: Mon, 11 Mar 2019 01:34:14 +0100 Subject: [PATCH] Optimize StrReader and Xml parsing --- .../com/soywiz/korio/serialization/xml/Xml.kt | 44 +++++++++++-------- .../kotlin/com/soywiz/korio/util/StrReader.kt | 10 ++++- .../serialization/xml/XmlEntitiesTest.kt | 21 +++++++++ 3 files changed, 54 insertions(+), 21 deletions(-) create mode 100644 korio/src/commonTest/kotlin/com/soywiz/korio/serialization/xml/XmlEntitiesTest.kt diff --git a/korio/src/commonMain/kotlin/com/soywiz/korio/serialization/xml/Xml.kt b/korio/src/commonMain/kotlin/com/soywiz/korio/serialization/xml/Xml.kt index e6356062..2ad5b876 100644 --- a/korio/src/commonMain/kotlin/com/soywiz/korio/serialization/xml/Xml.kt +++ b/korio/src/commonMain/kotlin/com/soywiz/korio/serialization/xml/Xml.kt @@ -168,17 +168,19 @@ data class Xml( fun decode(r: StrReader): String = buildString { while (!r.eof) { @Suppress("LiftReturnOrAssignment") // Performance? - when (val c = r.readChar()) { - '&' -> { - val value = r.readUntilIncluded(';') ?: "" - val full = "&$value" - append(when { - value.startsWith('#') -> "${value.substring(1, value.length - 1).toInt().toChar()}" - entityToChar.contains(full) -> "${entityToChar[full]}" - else -> full - }) - } - else -> append(c) + val plain = r.readUntil('&') + if (plain != null) { + append(plain) + } + if (r.eof) break + + r.skipExpect('&') + val value = r.readUntilIncluded(';') ?: "" + val full = "&$value" + when { + value.startsWith('#') -> append(value.substring(1, value.length - 1).toInt().toChar()) + entityToChar.contains(full) -> append(entityToChar[full]) + else -> append(full) } } } @@ -200,13 +202,14 @@ data class Xml( if (r.eof) break r.skipExpect('<') + var res: Element? = null when { r.tryExpect("![CDATA[") -> { val start = r.pos while (!r.eof) { val end = r.pos if (r.tryExpect("]]>")) { - yield(Element.Text(r.createRange(start, end).text)) + res = Element.Text(r.createRange(start, end).text) break } r.readChar() @@ -217,7 +220,7 @@ data class Xml( while (!r.eof) { val end = r.pos if (r.tryExpect("-->")) { - yield(Element.CommentTag(r.createRange(start, end).text)) + res = Element.CommentTag(r.createRange(start, end).text) break } r.readChar() @@ -253,15 +256,18 @@ data class Xml( val openclose = r.tryExpect('/') val processingInstructionEnd = r.tryExpect('?') r.skipExpect('>') - when { - processingInstruction || processingEntityOrDocType -> - yield(Element.ProcessingInstructionTag(name, attributes)) - openclose -> yield(Element.OpenCloseTag(name, attributes)) - close -> yield(Element.CloseTag(name)) - else -> yield(Element.OpenTag(name, attributes)) + res = when { + processingInstruction || processingEntityOrDocType -> Element.ProcessingInstructionTag(name, attributes) + openclose -> Element.OpenCloseTag(name, attributes) + close -> Element.CloseTag(name) + else -> Element.OpenTag(name, attributes) } } } + + if (res != null) { + yield(res) + } } } diff --git a/korio/src/commonMain/kotlin/com/soywiz/korio/util/StrReader.kt b/korio/src/commonMain/kotlin/com/soywiz/korio/util/StrReader.kt index 89521e37..9c138306 100644 --- a/korio/src/commonMain/kotlin/com/soywiz/korio/util/StrReader.kt +++ b/korio/src/commonMain/kotlin/com/soywiz/korio/util/StrReader.kt @@ -49,8 +49,14 @@ class StrReader(val str: String, val file: String = "file", var pos: Int = 0) { fun peek(): Char = if (hasMore) this.str[this.pos] else '\u0000' fun peekChar(): Char = if (hasMore) this.str[this.pos] else '\u0000' fun read(count: Int): String = this.peek(count).apply { skip(count) } - fun skipUntil(char: Char) = run { while (hasMore && this.peekChar() != char) this.readChar() } - fun skipUntilIncluded(char: Char) = run { while (hasMore && this.readChar() != char) Unit } + fun skipUntil(char: Char) { + val skipPos = this.str.indexOf(char, pos) + pos = (if (skipPos >= 0) skipPos else length) + } + fun skipUntilIncluded(char: Char) { + skipUntil(char) + if (!eof && peekChar() == char) skip(1) + } //inline fun skipWhile(check: (Char) -> Boolean) = run { while (check(this.peekChar())) this.skip(1) } inline fun skipWhile(filter: (Char) -> Boolean) = run { while (hasMore && filter(this.peekChar())) this.readChar() } diff --git a/korio/src/commonTest/kotlin/com/soywiz/korio/serialization/xml/XmlEntitiesTest.kt b/korio/src/commonTest/kotlin/com/soywiz/korio/serialization/xml/XmlEntitiesTest.kt new file mode 100644 index 00000000..605258a7 --- /dev/null +++ b/korio/src/commonTest/kotlin/com/soywiz/korio/serialization/xml/XmlEntitiesTest.kt @@ -0,0 +1,21 @@ +package com.soywiz.korio.serialization.xml + +import kotlin.test.* + +class XmlEntitiesTest { + @Test + fun testDecode() { + assertEquals("hello", Xml.Entities.decode("hello")) + assertEquals("\"", Xml.Entities.decode(""")) + assertEquals("hello\"world", Xml.Entities.decode("hello"world")) + assertEquals("hello\"world\"", Xml.Entities.decode("hello"world"")) + } + + @Test + fun testEncode() { + assertEquals("hello", Xml.Entities.encode("hello")) + assertEquals(""", Xml.Entities.encode("\"")) + assertEquals("hello"world", Xml.Entities.encode("hello\"world")) + assertEquals("hello"world"", Xml.Entities.encode("hello\"world\"")) + } +}