package kotlinx.html.dom
import kotlinx.html.*
import kotlinx.html.consumers.*
import org.w3c.dom.*
import org.w3c.dom.events.*
import kotlin.dom.*
@Suppress("NOTHING_TO_INLINE")
private inline fun HTMLElement.setEvent(name: String, noinline callback : (Event) -> Unit) : Unit {
asDynamic()[name] = callback
}
class JSDOMBuilder(val document : Document) : TagConsumer {
private val path = arrayListOf()
private var lastLeaved : HTMLElement? = null
override fun onTagStart(tag: Tag) {
val element: HTMLElement = when {
tag.namespace != null -> document.createElementNS(tag.namespace!!, tag.tagName).asDynamic()
else -> document.createElement(tag.tagName) as HTMLElement
}
tag.attributesEntries.forEach {
element.setAttribute(it.key, it.value)
}
if (path.isNotEmpty()) {
path.last().appendChild(element)
}
path.add(element)
}
override fun onTagAttributeChange(tag: Tag, attribute: String, value: String?) {
when {
path.isEmpty() -> throw IllegalStateException("No current tag")
path.last().tagName.toLowerCase() != tag.tagName.toLowerCase() -> throw IllegalStateException("Wrong current tag")
else -> path.last().let { node ->
if (value == null) {
node.removeAttribute(attribute)
} else {
node.setAttribute(attribute, value)
}
}
}
}
override fun onTagEvent(tag: Tag, event: String, value: (Event) -> Unit) {
when {
path.isEmpty() -> throw IllegalStateException("No current tag")
path.last().tagName.toLowerCase() != tag.tagName.toLowerCase() -> throw IllegalStateException("Wrong current tag")
else -> path.last().setEvent(event, value)
}
}
override fun onTagEnd(tag: Tag) {
if (path.isEmpty() || path.last().tagName.toLowerCase() != tag.tagName.toLowerCase()) {
throw IllegalStateException("We haven't entered tag ${tag.tagName} but trying to leave")
}
lastLeaved = path.removeAt(path.lastIndex)
}
override fun onTagContent(content: CharSequence) {
if (path.isEmpty()) {
throw IllegalStateException("No current DOM node")
}
path.last().appendChild(document.createTextNode(content.toString()))
}
override fun onTagContentEntity(entity: Entities) {
if (path.isEmpty()) {
throw IllegalStateException("No current DOM node")
}
// stupid hack as browsers doesn't support createEntityReference
val s = document.createElement("span") as HTMLElement
s.innerHTML = entity.text
path.last().appendChild(s.childNodes.asList().filter { it.nodeType == Node.TEXT_NODE }.first())
// other solution would be
// pathLast().innerHTML += entity.text
}
override fun onTagContentUnsafe(block: Unsafe.() -> Unit) {
with(DefaultUnsafe()) {
block()
path.last().innerHTML += toString()
}
}
override fun finalize(): R = lastLeaved?.asR() ?: throw IllegalStateException("We can't finalize as there was no tags")
@Suppress("UNCHECKED_CAST")
private fun HTMLElement.asR(): R = this.asDynamic()
}
fun Document.createTree() : TagConsumer = JSDOMBuilder(this)
val Document.create : TagConsumer
get() = JSDOMBuilder(this)
fun Node.append(block : TagConsumer.() -> Unit) : List =
ArrayList().let { result ->
ownerDocumentExt.createTree().onFinalize { it, partial -> if (!partial) {result.add(it); appendChild(it) } }.block()
result
}
val HTMLElement.append : TagConsumer
get() = ownerDocumentExt.createTree().onFinalize { element, partial -> if (!partial) { this@append.appendChild(element) } }
private val Node.ownerDocumentExt: Document
get() = when {
this is Document -> this
else -> ownerDocument ?: throw IllegalStateException("Node has no ownerDocument")
}