diff --git a/libraries/stdlib/src/kotlin/template/Templates.kt b/libraries/stdlib/src/kotlin/template/Templates.kt index 25c3d5f9a3f..852c20b0621 100644 --- a/libraries/stdlib/src/kotlin/template/Templates.kt +++ b/libraries/stdlib/src/kotlin/template/Templates.kt @@ -1,14 +1,21 @@ package kotlin.template -import kotlin.dom.* +import kotlin.dom. * import org.w3c.dom.Node import com.sun.org.apache.xalan.internal.xsltc.dom.UnionIterator +import java.util.Locale +import java.text.NumberFormat +import java.text.DateFormat +import java.util.Date // TODO this class should move into the runtime // in jet.StringTemplate -class StringTemplate(val values: Array) { +class StringTemplate(val values : Array) { - fun toString(): String { + /** + * Converts the template into a String + */ + fun toString() : String { val out = StringBuilder() forEach{ out.append(it) } return out.toString() ?: "" @@ -17,20 +24,31 @@ class StringTemplate(val values: Array) { /** * Performs the given function on each value in the collection */ - fun forEach(fn: (Any?) -> Unit): Unit { + fun forEach(fn : (Any?) -> Unit) : Unit { for (v in values) { fn(v) } } } -fun StringTemplate.toString(formatter : ValueFormatter): String { +/** + * Converts the string template into a string using the given formatter + * to encode values as Strings performing any special encoding (such as for HTML) + * or internationalisation. + * + * See [[HtmlFormatter] and [[LocaleFormatter] respectively. + */ +fun StringTemplate.toString(formatter : Formatter) : String { val buffer = StringBuilder() append(buffer, formatter) return buffer.toString() ?: "" } -fun StringTemplate.append(out: Appendable, formatter: ValueFormatter): Unit { +/** + * Appends the text representation of this string template to the given output + * using the supplied formatter + */ +fun StringTemplate.append(out : Appendable, formatter : Formatter) : Unit { var constantText = true this.forEach { if (constantText) { @@ -49,43 +67,106 @@ fun StringTemplate.append(out: Appendable, formatter: ValueFormatter): Unit { } } - -fun StringTemplate.toHtml(formatter : HtmlFormatter = HtmlFormatter()): String = toString(formatter) +/** + * Converts this string template to internationalised text using the supplied + * [[LocaleFormatter]] + */ +fun StringTemplate.toLocale(formatter : LocaleFormatter = LocaleFormatter()) : String = toString(formatter) /** - * Represents a formatter of values in a [[StringTemplate]] which understands how to escape - * different types for different kinds of language + * Converts this string template to HTML text */ -trait ValueFormatter { +fun StringTemplate.toHtml(formatter : HtmlFormatter = HtmlFormatter()) : String = toString(formatter) + +/** + * Represents a formatter and encoder of values in a [[StringTemplate]] which understands + * how to format values for a particular [[Locale]] such as with the [[LocaleFormatter]] or + * to escape particular characters in different output formats such as [[HtmlFormatter] + */ +trait Formatter { fun format(buffer : Appendable, val value : Any?) : Unit } -object ToStringFormatter : ValueFormatter { +/** + * Formats strings with no special encoding other than allowing the null text to be + * configured + */ +open class ToStringFormatter : Formatter { - fun toString() = "ToStringFormatter" + var nullString : String = "null" - override fun format(buffer : Appendable, value : Any?) { - val text = if (value == null) "null" else value.toString() - buffer.append(text) - } -} + open fun toString() = "ToStringFormatter" -class HtmlFormatter : ValueFormatter { - var nullText : String = "" - - override fun format(buffer : Appendable, value : Any?) { + override fun format(out : Appendable, value : Any?) { if (value == null) { - buffer.append(nullText) + format(out, nullString) } else if (value is StringTemplate) { - value.append(buffer, this) - } else if (value is Node) { - buffer.append(value.toXmlString()) + value.append(out, this) } else { - escape(buffer, value.toString() ?: "") + format(out, value.toString()) } } - fun escape(buffer : Appendable, text : String) : Unit { + /** + * Formats the given string allowing derived classes to override this method + * to escape strings with special characters such as for HTML + */ + open fun format(out : Appendable, text : String) : Unit { + out.append(text) + } +} + +protected val defaultLocale : Locale = Locale.getDefault().sure() + +/** + * Formats values using a given [[Locale]] for internationalisation + */ +open class LocaleFormatter(val locale : Locale = defaultLocale) : ToStringFormatter() { + + override fun toString() = "LocaleFormatter{$locale}" + + public var numberFormat : NumberFormat = NumberFormat.getInstance(locale).sure() + + public var dateFormat : DateFormat = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, locale).sure() + + override fun format(out : Appendable, value : Any?) { + /* TODO KT-1583 - uncomment when its fixed + if (value is Number) { + format(out, format(value)) + } else + */ + if (value is Date) { + format(out, format(value)) + } else { + super.format(out, value) + } + } + + fun format(number : Number) : String { + return numberFormat.format(number) ?: "" + } + + fun format(date : Date) : String { + return dateFormat.format(date) ?: "" + } +} + +/** + * Formats values for HTML encoding, escaping special characters in HTML. + */ +class HtmlFormatter(locale : Locale = defaultLocale) : LocaleFormatter(locale) { + + override fun toString() = "HtmlFormatter{$locale}" + + override fun format(out : Appendable, value : Any?) { + if (value is Node) { + out.append(value.toXmlString()) + } else { + super.format(out, value) + } + } + + override fun format(buffer : Appendable, text : String) : Unit { for (c in text) { if (c == '<') buffer.append("<") else if (c == '>') buffer.append(">") @@ -95,3 +176,5 @@ class HtmlFormatter : ValueFormatter { } } } + + diff --git a/libraries/stdlib/test/template/LocaleTemplateTest.kt b/libraries/stdlib/test/template/LocaleTemplateTest.kt new file mode 100644 index 00000000000..eef0c0749d6 --- /dev/null +++ b/libraries/stdlib/test/template/LocaleTemplateTest.kt @@ -0,0 +1,43 @@ +package template + +import kotlin.template.* +import kotlin.test.assertEquals + +import junit.framework.TestCase +import java.util.Date +import java.util.Locale + +class LocaleTemplateTest : TestCase() { + fun testDefaultLocale() : Unit { + format(LocaleFormatter()) + } + + fun testFrance() : Unit { + format(LocaleFormatter(Locale.FRANCE.sure())) + } + + fun testGermany() : Unit { + format(LocaleFormatter(Locale.GERMANY.sure())) + } + + fun format(formatter: LocaleFormatter): 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.toLocale("hello $name!") + + // TODO will use a tuple soon + //val actual = formatter.format(StringTemplate(Tuple2("hello ", name)) + val actual = StringTemplate(array("hello ", name, + // TODO currently numbers cause: java.lang.ClassNotFoundException: jet.Number + // " price ", price, + " data ", now)).toString(formatter) + + println("Got text: $actual") + } +} \ No newline at end of file