Skip to content

Commit

Permalink
added full HTML5 support to the template engine; generating the eleme…
Browse files Browse the repository at this point in the history
…nts and attributes from the HTML5 specification so we're typesafe and complete
  • Loading branch information
jstrachan committed May 23, 2012
1 parent 7d8f364 commit 2c4b205
Show file tree
Hide file tree
Showing 3 changed files with 2,868 additions and 39 deletions.
@@ -1,17 +1,14 @@
package org.koolapp.tools.htmlgen

import org.koolapp.html.*
import kotlin.*
import java.io.FileInputStream
import java.util.*
import kotlin.dom.*
import kotlin.util.*
import org.koolapp.html.*
import org.w3c.dom.*

import org.cyberneko.html.HTMLConfiguration
import org.apache.xerces.parsers.DOMParser
import org.xml.sax.InputSource
import java.net.URL
import java.io.FileInputStream
import java.util.*
import java.io.PrintWriter
import java.io.FileWriter
import java.io.File

fun main(args: Array<String>): Unit {
val tool = GenerateHtmlModel()
Expand All @@ -24,14 +21,22 @@ fun main(args: Array<String>): Unit {
/**
* Parses the HTML5 specification
*/
class GenerateHtmlModel : Runnable {
class GenerateHtmlModel: Runnable {

public var htmlSpecUrl: String = "http://dev.w3.org/html5/spec/section-index.html"
public var htmlGlobalAttributesUrl: String = "http://dev.w3.org/html5/spec/global-attributes.html"

public var outFileName : String = "../koolapp-template/src/main/kotlin/org/koolapp/template/html/GeneratedElements.kt"

var identifierAliases = hashMap("type" to "typeName", "class" to "klass", "for" to "forInput", "object" to "objectElement", "var" to "varElement")

var globalAttributes = ArrayList<String>()

public override fun run() {
println("Loading the HTML5 spec from $htmlSpecUrl")

loadGlobalAttributes()

val document = if (htmlSpecUrl.startsWith("file://")) {
val file = htmlSpecUrl.substring("file://".length())
val inputSource = InputSource(FileInputStream(file))
Expand All @@ -49,37 +54,128 @@ class GenerateHtmlModel : Runnable {
// lets find the next table element
println("Found title ${title.text}")

val nextElements: Iterator<Element> = title.nextElements()
val table = nextElements.find{ it.getTagName() == "table" }
val table = title.nextElements().find{ it.getTagName() == "table" }
println("Found table $table")
if (table != null) {
if (table is Element) {
val tbody = table["tbody"]
if (tbody.notEmpty()) {
val rows = tbody[0]["tr"]
for (row in rows) {
//println("Processing row $row")
val links = row["a"]
if (links.notEmpty()) {
val name = links[0].text
val tds = row["td"]
var empty = false
if (tds.size() > 4) {
val contentTd = tds[1]
val emptyTd = tds[3]
if (emptyTd.text == "empty") {
empty = true
useWriter { writer ->
val rows = tbody[0]["tr"]
for (row in rows) {
//println("Processing row $row")
val links = row["a"]
if (links.notEmpty()) {
val name = links[0].text
val tds = row["td"]
var empty = false
if (tds.size() > 4) {
val description = tds[0]
val emptyTd = tds[3]
if (emptyTd.text == "empty") {
empty = true
}
val attributesTd = tds[4]
val attributeNames = attributesTd["a"].drop(1).map<Element, String>{ it.text } + globalAttributes
println("Element $name empty $empty attributes $attributeNames")
val fnName = safeIdentifier(name)
val elementDescription = description.children().map{ it.toXmlString() }.makeString("")
val safeIdentifiers = attributeNames.map { safeIdentifier(it) }

writer.println("/** Creates a new *$name* element: ${elementDescription} */")
writer.print("fun Node.$fnName(")
if (!empty) writer.print("text: String? = null, ")
for (id in safeIdentifiers) {
writer.print("$id: String? = null, ")
}
writer.println("""init: Element.()-> Unit): Element {""")
writer.print(" val answer = ")
if (empty)
writer.println("""element("$name", init)""")
else
writer.println("""textElement("$name", text, init)""")

for (a in attributeNames) {
val id = safeIdentifier(a)
writer.println(""" if ($id != null) answer.setAttribute("$a", $id)""")
}
writer.println(" return answer")
writer.println("}")
writer.println("")

// lets avoid the use of the initialisation block
writer.println("/** Creates a new *$name* element: ${elementDescription} */")
writer.print("fun Node.$fnName(")
val args = ArrayList<String>()
var first = false
if (!empty) {
args.add("text: String? = null")
}
for (id in safeIdentifiers) {
args.add("$id: String? = null")
}
val arguments = safeIdentifiers.makeString(", ")
writer.println("""${args.makeString(", ")}): Element = $fnName($arguments) {}""")
writer.println("")
}
val attributesTd = tds[4]
// TODO drop 1
val attributeNames = attributesTd["a"].map<Element,String>{it.text}
println("Element $name empty $empty attributes $attributeNames")
}
}
}
}
} else {
println("Could not find table!")
}
println("Done!")
}

protected fun useWriter(block: (PrintWriter) -> Any): Unit {
println("Generating file $outFileName")
val outFile = File(outFileName)
outFile.getParentFile()!!.mkdirs()
val writer = PrintWriter(FileWriter(outFile))
writer.use {
writer.println("""/*
* NOTE - this file is autogenerated - do not edit!!!
*/
package org.koolapp.template.html
import org.w3c.dom.*
import kotlin.dom.*
import org.koolapp.template.Template
import java.io.Writer
""")

block(writer)
}
}

protected fun loadGlobalAttributes() {
println("Parsing global attributes at $htmlGlobalAttributesUrl")
val doc = parseHtml(htmlGlobalAttributesUrl)
val header = doc.id("global-attributes")!!
val ul = header.nextElements().find{ it.getTagName() == "ul" }!!
println("Found ul $ul")
val codes = ul["a"]
for (code in codes) {
val text = code.text
println("Found global attribute $text")
globalAttributes.add(text)
}
}

protected fun safeIdentifier(name: String): String {
return identifierAliases.getOrElse(name) {
val paths = name.split("-")
if (paths.size < 2) {
name
} else {
// lets camelCase the string
paths.fold(""){ a, b -> a + if (a.isEmpty()) b else b.capitalize() }
}
}
}
}

0 comments on commit 2c4b205

Please sign in to comment.