refactored the template experiment1 implementation to add i18n support of numbers & dates

This commit is contained in:
James Strachan
2012-03-13 12:18:25 +00:00
parent 859bab0305
commit 8d47332cd9
3 changed files with 143 additions and 37 deletions
@@ -3,6 +3,12 @@ package kotlin.template.experiment1
import org.w3c.dom.Node import org.w3c.dom.Node
import kotlin.dom.toXmlString import kotlin.dom.toXmlString
import java.lang.Number
import java.text.DateFormat
import java.text.NumberFormat
import java.util.Date
import java.util.Locale
fun StringBuilder.text(value : String) : Unit { fun StringBuilder.text(value : String) : Unit {
this.append(value) this.append(value)
} }
@@ -22,43 +28,61 @@ fun format(fn : (StringBuilder) -> Unit) : String {
} }
abstract class TextBuilder<T>(val output: Appendable) { abstract class TextBuilder(val output : Appendable) {
/* /*
*/ */
/** /**
* Converts the given string template to a String * Converts the given string template to a String
fun toString(fn: (T) -> Unit): String = toString(ToStringFormatter, fn) fun toString(fn: (T) -> Unit): String = toString(ToStringFormatter, fn)
fun toString(formatter : ValueFormatter, fn: (T) -> Unit): String { fun toString(formatter : ValueFormatter, fn: (T) -> Unit): String {
fn(this) fn(this)
return output.toString() ?: "" return output.toString() ?: ""
} }
*/ */
fun text(value: String): Unit { fun text(value : String) : Unit {
output.append(value) output.append(value)
} }
} }
class HtmlBuilder(output: Appendable) : TextBuilder<HtmlBuilder>(output) { open class I18nBuilder(output : Appendable, val formatter : I18nFormatter) : TextBuilder(output) {
var nullText : String = ""
fun expression(value: Node): Unit { fun expression(value : Any?) : Unit {
escape(formatter.format(value))
}
fun expression(value : Number) : Unit {
escape(formatter.formatNumber(value))
}
fun expression(value : Date) : Unit {
escape(formatter.formatDate(value))
}
/**
* By default does no escaping but inherited classes may escape text
*/
open fun escape(text : String) : Unit {
output.append(text)
}
}
open class HtmlBuilder(output : Appendable, formatter : I18nFormatter) : I18nBuilder(output, formatter) {
/**
* Nodes are assumed to already be properly HTML/XML encoded so don't escape
*/
fun expression(value : Node) : Unit {
text(value.toXmlString()) text(value.toXmlString())
} }
fun expression(value: Any?): Unit { /**
if (value == null) { * Escapes text using HTML escaping of sensitive tokens
output.append(nullText) */
} else { override fun escape(text : String) : Unit {
escape(value.toString() ?: "")
}
}
fun escape(text : String) : Unit {
for (c in text) { for (c in text) {
if (c == '<') output.append("&lt;") if (c == '<') output.append("&lt;")
else if (c == '>') output.append("&gt;") else if (c == '>') output.append("&gt;")
@@ -75,22 +99,57 @@ class HtmlBuilder(output: Appendable) : TextBuilder<HtmlBuilder>(output) {
* different types for different kinds of language * different types for different kinds of language
*/ */
trait ValueFormatter { trait ValueFormatter {
fun format(buffer : Appendable, val value : Any?) : Unit fun format(val value : Any?) : String
} }
object ToStringFormatter : ValueFormatter { open class ToStringFormatter : ValueFormatter {
fun toString() = "ToStringFormatter" var nullText : String = "null"
override fun format(buffer : Appendable, value : Any?) { open fun toString() = "ToStringFormatter"
val text = if (value == null) "null" else value.toString()
buffer.append(text) override fun format(value : Any?): String {
return if (value == null) nullText else value.toString()
} }
} }
fun toHtml(fn: (HtmlBuilder) -> Unit): String { open class I18nFormatter(val locale : Locale = Locale.getDefault().sure()): ToStringFormatter() {
val buffer = StringBuilder()
val html = HtmlBuilder(buffer) override fun toString() = "I18nFormatter"
fn(html)
return buffer.toString() ?: "" public var numberFormat : NumberFormat = NumberFormat.getInstance(locale).sure()
public var dateFormat : DateFormat = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, locale).sure()
fun formatNumber(number : Number): String {
return numberFormat.format(number) ?: ""
}
fun formatDate(date : Date): String {
return dateFormat.format(date) ?: ""
}
/**
* Generates the internationalised text for this formatter
*/
fun toString(fn : (I18nBuilder) -> Unit) : String {
val buffer = StringBuilder()
val builder = I18nBuilder(buffer, this)
fn(builder)
return buffer.toString() ?: ""
}
/**
* Generates the HTML for the given function
*/
fun toHtml(fn : (HtmlBuilder) -> Unit) : String {
val buffer = StringBuilder()
val html = HtmlBuilder(buffer, this)
fn(html)
return buffer.toString() ?: ""
}
} }
@@ -13,10 +13,11 @@ class HtmlTemplateTest : TestCase() {
val foo = "James" val foo = "James"
val bar = "x > 1" val bar = "x > 1"
val format = I18nFormatter()
// Code generated by the following template expression: // Code generated by the following template expression:
// //
// val actual = toHtml("<h1>$foo</h1> <p>hey $bar</p>") // val actual = format.toHtml("<h1>$foo</h1> <p>hey $bar</p>")
val actual = toHtml{ val actual = format.toHtml{
it.text("<h1>") it.text("<h1>")
it.expression(foo) it.expression(foo)
it.text("</h1> <p>hey ") it.text("</h1> <p>hey ")
@@ -0,0 +1,46 @@
package template.experiment1
import kotlin.template.experiment1.*
import kotlin.test.assertEquals
import junit.framework.TestCase
import java.util.Date
import java.util.Locale
class I18nTemplateTest : TestCase() {
fun testDefaultLocale() : Unit {
format(I18nFormatter())
}
fun testFrance() : Unit {
format(I18nFormatter(Locale.FRANCE.sure()))
}
fun testGermany() : Unit {
format(I18nFormatter(Locale.GERMANY.sure()))
}
fun format(format: I18nFormatter): Unit {
val name = "James"
// TODO currently numbers cause: java.lang.ClassNotFoundException: jet.Number
//val price: Double = 1.99
val now = Date()
// Code generated by the following template expression:
//
// val actual = format.toI18n("hello $name!")
val actual = format.toString{
it.text("hello ")
it.expression(name)
/*
it.text(" price ")
it.expression(price)
*/
it.text(" date ")
it.expression(now)
}
println("Got text: $actual")
}
}