package nl.astraeus.komp import kotlinx.html.DefaultUnsafe import kotlinx.html.Entities import kotlinx.html.Tag import kotlinx.html.TagConsumer import kotlinx.html.Unsafe import org.w3c.dom.events.Event interface HtmlConsumer : TagConsumer<VDOMElement> { fun append(node: VDOMElement) } class HtmlBuilder( val baseHash: Int ) : HtmlConsumer { private val path = arrayListOf<VDOMElement>() private var lastLeaved: VDOMElement? = null override fun onTagStart(tag: Tag) { val element = VDOMElement(baseHash, tag.tagName, tag.namespace) for (entry in tag.attributesEntries) { element.setAttribute(entry.key, entry.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().content.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().content.toLowerCase() != tag.tagName.toLowerCase() -> throw IllegalStateException("Wrong current tag") else -> path.last().setKompEvent(event, value) } } override fun onTagEnd(tag: Tag) { if (path.isEmpty() || path.last().content.toLowerCase() != tag.tagName.toLowerCase()) { throw IllegalStateException("We haven't entered tag ${tag.tagName} but trying to leave") } lastLeaved = path.removeAt(path.lastIndex) lastLeaved?.updateChildHash() } override fun onTagContent(content: CharSequence) { if (path.isEmpty()) { throw IllegalStateException("No current DOM node") } path.last().appendChild(VDOMElement(baseHash, content.toString(), type = VDOMElementType.TEXT)) } override fun onTagContentEntity(entity: Entities) { if (path.isEmpty()) { throw IllegalStateException("No current DOM node") } // stupid hack as browsers don't support createEntityReference path.last().appendChild(VDOMElement(baseHash, entity.text, type = VDOMElementType.ENTITY)) } override fun append(node: VDOMElement) { path.last().appendChild(node) } override fun onTagContentUnsafe(block: Unsafe.() -> Unit) { with(DefaultUnsafe()) { block() path.last().appendChild(VDOMElement(baseHash, toString(), type = VDOMElementType.UNSAFE)) } } override fun onTagComment(content: CharSequence) { if (path.isEmpty()) { throw IllegalStateException("No current DOM node") } path.last().appendChild(VDOMElement(baseHash, content.toString(), type = VDOMElementType.COMMENT)) } override fun finalize(): VDOMElement { return lastLeaved ?: throw IllegalStateException("We can't finalize as there was no tags") } companion object { fun create(content: HtmlBuilder.() -> Unit): VDOMElement { val komponent = DummyKomponent() val consumer = HtmlBuilder(komponent.hashCode()) content.invoke(consumer) return consumer.finalize() } } }