/
api.kt
130 lines (101 loc) · 3.44 KB
/
api.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
package kotlinx.html
import org.w3c.dom.events.*
import java.util.*
interface TagConsumer<out R> {
fun onTagStart(tag: Tag)
fun onTagAttributeChange(tag: Tag, attribute: String, value: String?)
fun onTagEvent(tag: Tag, event: String, value: (Event) -> Unit)
fun onTagEnd(tag: Tag)
fun onTagContent(content: CharSequence)
fun onTagContentEntity(entity: Entities)
fun onTagContentUnsafe(block: Unsafe.() -> Unit)
fun onTagError(tag: Tag, exception: Throwable): Unit = throw exception
fun finalize(): R
}
interface Tag {
val tagName: String
val consumer: TagConsumer<*>
val namespace: String?
val attributes: MutableMap<String, String>
val attributesEntries: Collection<Map.Entry<String, String>>
val inlineTag: Boolean
val emptyTag: Boolean
operator fun Entities.unaryPlus(): Unit {
consumer.onTagContentEntity(this)
}
operator fun String.unaryPlus(): Unit {
consumer.onTagContent(this)
}
}
interface Unsafe {
operator fun String.unaryPlus()
operator fun Entities.unaryPlus() = +text
}
interface AttributeEnum {
val realValue: String
}
fun <T : Tag> T.visit(block: T.() -> Unit) {
consumer.onTagStart(this)
try {
this.block()
} catch (err: Exception) {
consumer.onTagError(this, err)
} finally {
consumer.onTagEnd(this)
}
}
fun <T : Tag, R> T.visitAndFinalize(consumer: TagConsumer<R>, block: T.() -> Unit): R {
require(this.consumer === consumer)
visit(block)
return consumer.finalize()
}
fun attributesMapOf() = emptyMap
fun attributesMapOf(key: String, value: String?): Map<String, String> = when (value) {
null -> emptyMap
else -> singletonMapOf(key, value)
}
fun attributesMapOf(vararg pairs: String?): Map<String, String> {
var result: LinkedHashMap<String, String>? = null
for (i in 0..pairs.size - 1 step 2) {
val k = pairs[i]
val v = pairs[i + 1]
if (k != null && v != null) {
if (result == null) {
result = LinkedHashMap(pairs.size - i)
}
result[k] = v
}
}
return result ?: emptyMap
}
fun singletonMapOf(key: String, value: String): Map<String, String> = SingletonStringMap(key, value)
fun HTMLTag.unsafe(block: Unsafe.() -> Unit): Unit = consumer.onTagContentUnsafe(block)
val emptyMap: Map<String, String> = emptyMap()
class DefaultUnsafe : Unsafe {
private val sb = StringBuilder()
override fun String.unaryPlus() {
sb.append(this)
}
override fun toString(): String = sb.toString()
}
@DslMarker
annotation class HtmlTagMarker
private data class SingletonStringMap(override val key: String, override val value: String) : Map<String, String>, Map.Entry<String, String> {
override val entries: Set<Map.Entry<String, String>>
get() = setOf(this)
override val keys: Set<String>
get() = setOf(key)
override val size: Int
get() = 1
override val values: Collection<String>
get() = listOf(value)
override fun containsKey(key: String) = key == this.key
override fun containsValue(value: String) = value == this.value
override fun get(key: String): String? = if (key == this.key) value else null
override fun isEmpty() = false
// workaround for https://youtrack.jetbrains.com/issue/KT-14194
@Suppress("UNUSED")
private fun getKey(p: Int = 0) = key
@Suppress("UNUSED")
private fun getValue(p: Int = 0) = value
}