Expressions
Table of Contents for Expressions
Expressions
Attribute References
Named attributes
The most common thing in a template besides plain text is a simple named attribute reference such as:
Your email: $email$
The template will look up the value of email
and insert it into the output stream when you ask the template to print itself out. If email
has no value, then it evaluates to the empty string and nothing is printed out for that attribute expression. When working with group files, if email
is not defined in the formal parameter list of an enclosing template, an exception is thrown.
If the attribute is multi-value such as an instance of a list, the elements are emitted without separator one after the other. If there are null values in the list, these are ignored by default. Given template $values$
with attribute values=9,6,null,2,null then the output would be:
962
To use a separator in between those multiple values, use the separator
option:
$values; separator=", "$
The output would be:
9, 6, 2
To emit a special value for each null element in a list, use the null
option:
$values; null="-1", separator=", "$
Again using values=9,6,null,2,null then the output would be:
9, 6, -1, 2, -1
Property references
If a named attribute is an aggregate with a property or a simple data field, you may reference that property using attribute.property. For example:
Your name: $person.name$ Your email: $person.email$
StringTemplate
ignores the actual object type stored in attribute person
and simply looks for one of the following via reflection (in search order):
Java |
|
---|---|
C# |
|
Python |
|
An exception is thrown if that property is not defined on the target object.
Because the type is ignored, you can pass in whatever existing aggregate (class) you have such as User
or Person
:
Java |
User u = database.lookupPerson("parrt@jguru.com"); st.setAttribute("person", u); |
---|---|
C# |
User u = database.LookupPerson("parrt@jguru.com"); st.SetAttribute("person", u); |
Python |
u = database.lookupPerson("parrt@jguru.com") st["person"] = u |
Or, if a suitable aggregate doesn't exist, you can make a connector or "glue" object and pass that in instead:
Java |
st.setAttribute("person", new Connector()); |
---|---|
C# |
st.SetAttribute("person", new Connector()); |
Python |
st["person"] = Connector() |
where Connector
is defined as:
Java |
public class Connector { public String getName() { return "Terence"; } public String getEmail() { return "parrt@jguru.com"; } } |
---|---|
C# |
public class Connector { public string Name { get {return "Terence";} } public string Email { get { return "parrt@jguru.com";} } } |
Python |
class Connector(object): def getName(self): return "Terence" def getEmail(self): return "parrt@jguru.com" |
The ability to reference aggregrate properties saves you the trouble of having to pull out the properties with code like this:
Java |
User u = database.lookupPerson("parrt@jguru.com"); st.setAttribute("name", u.getName()); st.setAttribute("email", u.getEmail()); |
---|---|
C# |
User u = database.lookupPerson("parrt@jguru.com"); st.SetAttribute("name", u.Name); st.SetAttribute("email", u.Email); |
Python |
u = database.lookupPerson("parrt@jguru.com") st["name"] = u.getName() st["email"] = u.getEmail() |
and having template:
Your name: $name$ Your email: $email$
The latter is more widely applicable and totally decoupled from code and logic; i.e., it's "better" but much less convenient. Be very careful that the property methods do not have any side-effects like updating a counter or whatever. This breaks the rule of order of evaluation independence.
Indirect property names
Sometimes the property name is itself variable, in which case you need to use indirect property access notation:
$person.(propertyName)$
where propertyName
is an attribute whose value is the name of a property to fetch from person
. Using the examples from above, propertyName
could hold the value of either name
or email
.
propertyName
may actually be an expression instead of a simple attribute name.
Map key/value pair access
Java |
You may pass in instances of any object that implements the StringTemplate a = new StringTemplate("$user.name$, $user.phone$"); HashMap user = new HashMap(); user.put("name", "Terence"); user.put("phone", "none-of-your-business"); a.setAttribute("user", user); String results = a.toString();
|
---|---|
C# |
You may pass in instances of type StringTemplate a = new StringTemplate("$user.name$, $user.phone$"); Hashtable user = new Hashtable(); user.Add("name", "Terence"); user.Add("phone", "none-of-your-business"); a.SetAttribute("user", user); string results = a.ToString();
|
Python |
You may pass in instances of type a = stringtemplate3.StringTemplate("$user.name$, $user.phone$") user = {} user["name"] = "Terence" user["phone"] = "none-of-your-business" a["user"] = user results = str(a)
|
StringTemplate interprets Map objects to have two predefined properties: keys
and values
that yield a list of all keys and the list of all values, respectively. When applying a template to a map, StringTemplate iterates over the values so that <aMap>
is a shorthand for <aMap.values>. Similarly <aMap.keys> walks over the keys. You can list all of the elements in a map like this:
<aMap.keys:{k| <k> maps to <aMap.(k)>}>.
Note the use of the indirect property reference <aMap.(k)>
, which says to take the value of the k
as the key in the lookup. Clearly without the parentheses the normal map lookup mechanism would treat k
as a literal and try to look up k
in the map. Also note that the map must have keys that are Strings for indirect property referencing to work, because the key is first rendered into a string by ST and then that is used to look up the value in the map.
Difficult property names
Some property names cause parse errors because of clashes with built in keywords or because they do not match the rules for IDs as used by String Template. These difficult property names can be dealt with by quoting the property name in combination with the indirect property construct:
$person.("first")$ --- Build in keyword $person.("1")$ --- non ID conforment name
Difficult properties names are quite likely to occur when dealing with maps. Map keys can be defined using arbitrary strings, including spaces and syntax characters used to defined templates themselves.
Be careful that the keys are the appropriate type. If person keys are Integer, $person.("1")$ won't work as Strings are never Integers.
Automatic aggregate creation
Creating one-off data aggregates is a pain, you have to define a new class just to associate two pieces of data. StringTemplate
makes it easy to group data during setAttribute()
calls. You may pass in an aggregrate attribute name to setAttribute()
with the data to aggregate:
Java |
StringTemplate st = new StringTemplate("$items:{$it.(\"last\")$, $it.(\"first\")$\n}$"); st.setAttribute("items.{first,last}", "John", "Smith"); st.setAttribute("items.{first,last}", "Baron", "Von Munchhausen"); String expecting = "Smith, John\n" + "Von Munchhausen, Baron\n"; |
---|---|
C# |
StringTemplate st = new StringTemplate("$items:{$it.(\"last\")$, $it.(\"first\")$\n}$"); st.SetAttribute("items.{first,last}", "John", "Smith"); st.SetAttribute("items.{first,last}", "Baron", "Von Munchhausen"); string expecting = "Smith, John\n" + "Von Munchhausen, Baron\n"; |
Python |
st = stringtemplate3.StringTemplate("$items:{$it.(\"last\")$, $it.(\"first\")$\n}$") st.setAttribute("items.{first,last}", "John", "Smith") st.setAttribute("items.{first,last}", "Baron", "Von Munchhausen") expecting = \ "Smith, John\n" + \ "Von Munchhausen, Baron\n" |
Note that the template, st
, expects the items
to be aggregates with properties first
and last
. By using attribute name
items.{first,last}
You are telling StringTemplate
to take the following two arguments as properties first
and last
.
The various overloads of the setAttribute()
method can handle from 1 to 5 arguments. The C# version uses variable-length argument list (using params
keyword).
List construction
As of v2.2, you may combine multiple attributes into a single multi-valued attribute in a syntax similar to the group map feature. Concatenate attributes by placing them in square brackets in a comma-separated list. For example,
$[mine,yours]$
creates a new multi-valued attribute (a list) with both elements - all of mine
first then all of yours
. This feature is handy when the model happens to group attributes differently than you need to access them in the view. This ability to rearrange attributes is consistent with model-view separation because the template cannot alter the data structure nor test its values - the template is merely looking at the data from a new perspective.
Naturally you may combine the list construction with template application:
$[mine,yours]:{ v | ...}$
Note that this is very different from
$mine,yours:{ x,y | ...}$
which iterates max(n,m) times where n and m are the lengths of mine
and yours
, respectively. The [mine,yours]
version iterates n+m times.
Template References
You may reference other templates to have them included just like the C language preprocessor #include
construct behaves. For example, if you are building a web page (page.st
) that has a search box, you might want the search box stored in a separate template file, say, searchbox.st
. This has two advantages:
- You can reuse the template over and over (no cut/paste)
- You can change one template and all search boxes change on the whole site.
Using method call syntax, just reference the foreign template:
<html> <body> ... $searchbox()$ ... </body> </html>
The invoking code would still just create the overall page and the enclosing page template would automatically create an instance of the referenced template and insert it:
Java |
StringTemplateGroup group = new StringTemplateGroup("webpages", "/usr/local/site/templates"); StringTemplate page = group.getInstanceOf("page"); |
---|---|
C# |
StringTemplateGroup group = new StringTemplateGroup("webpages", "C:/Inetpub/wwwroot/site/templates"); StringTemplate page = group.GetInstanceOf("page"); |
Python |
group = stringtemplate3.StringTemplateGroup("webpages", "/usr/local/site/templates") page = group.getInstanceOf("page") |
If the template you want to reference, say searchbox
, is in a subdirectory of the StringTemplateGroup
root directory called misc
, then you must reference the template as: misc/searchbox()
.
The included template may access attributes. How can you set the attribute of an included template? There are two ways: inheriting attributes and passing parameters.
Accessing Attributes Of Enclosing Template
Any included template can reference the attributes of the enclosing template instance. So if searchbox
references an attribute called resource
:
<form ...> ... <input type=hidden name=resource value=$resource$> ... </form>
you could set attribute resource
in the enclosing template page
object:
Java |
StringTemplate page = group.getInstanceOf("page"); page.setAttribute("resource", "faqs"); |
---|---|
C# |
StringTemplate page = group.GetInstanceOf("page"); page.SetAttribute("resource", "faqs"); |
Python |
page = group.getInstanceOf("page") page["resource"] = "faqs" |
This "inheritance" (dynamic scoping really) of attributes feature is particularly handy for setting generally useful attributes like siteFontTag
in the outermost body
template and being able to reference it in any nested template in the body.
Passing Parameters To Another Template
Another, more obvious, way to set the attributes of an included template is to pass in values as parameters, making them look like C macro invocations rather than includes. The syntax looks like a set of attribute assignments:
<html> <body> ... $searchbox(resource="faqs")$ ... </body> </html>
where I am setting the attribute of the included searchbox
to be the string literal "faqs"
.
The right-hand-side of the assignment may be any expression such as an attribute reference or even a reference to another template like this:
$boldMe(item=copyrightNotice())$
You may also use an anonymous template such as:
$bold(it={$firstName$ $lastName$})$
which first computes the template argument and then assigns it to it
.
If you are using StringTemplate
groups, then you have formal parameters and for those templates with a sole formal argument, you can pass just an expression instead of doing an assignment to the argument name. For example, if you do $bold(name)$
and bold
has one formal argument called item
, then item
gets the value of name
just as if you had said {$bold(item=name)$}.
Allowing enclosing attributes to pass through
When template x
calls template y
, the formal arguments of y
hide any x
arguments of the same because the formal parameters force you to define values. This prevents surprises and makes it easy to ensure any parameter value is empty unless you specifically set it for that template. The problem is that you need to factor templates sometimes and want to refine behavior with a subclass or just invoke another shared template but invoking y
as <y()>
hides all of x
's parameters with the same name. Use <y(...)>
syntax to indicate y should inherit all values even those with the same name. <y(name="foo", ...)>
would set one arg, but the others are inherited whereas <y(name="foo")>
only has name
set; all other arguments of template y
are empty. You can set manually with:
Java |
|
---|---|
C# |
|
Python |
|
Argument evaluation scope
The right-hand-side of the argument assignments are evaluated within the scope of the enclosing template whereas the left-hand-side attribute name is the name of an attribute in the target template. Template invocations like $bold(item=item)$
actually make sense because the item
on the right is evaluated in a different scope.
Attribute operators
StringTemplate provides a number of operators that you can apply to attributes to get a new view of that data: first, rest, last, length, strip.
Sometimes you need to treat the first or last element of multi-valued attribute differently than the others. For example, if you have a list of integers in an attribute and you need to generate code to sum those numbers, you could start like this:
<numbers:{ n | sum += <n>;}>
You need to define sum
, however:
int sum = 0; <numbers:{ n | sum += <n>;}>
What if numbers
is empty though? No need to create the sum
definition so you could do this:
<if(numbers)>int sum = 0;<endif> <numbers:{ n | sum += <n>;}>
A more specific strategy (and one that generates slightly better code as it avoids an unnecessary initialization to 0) is the following:
<first(numbers):{ n | int sum = <n>;}> <rest(numbers):{ n | sum += <n>;}>
where first(numbers)
results in the first value of attribute numbers
if any and rest(numbers)
results all values in numbers
but the first value.
The other operator available to you is last, which naturally results in the last value of a multi-valued attribute. Now we have trunc also which returns all but the last value.
Special cases:
- operations on empty attributes yields an empty value
rest
of a single-valued attribute yields an empty value
tail
of a single-valued attribute yields the same asfirst
, the attribute value
You may find it handy to use another operator sometimes: plus "string concatenate". operator. For example, you may want to compute an argument to a template using a literal and an attribute:
...$link(url="/faq/view?ID="+faqid, title=faqtitle)$...
where faqid
and faqtitle
are attributes you have set for
the template that referenced link
.
Terence says
I'm a little uncomfortable with this concatenation operation. Please use a template instead
:
...$link(url={/faq/view?ID=$faqid$}, title=faqtitle)$...
In order to emit the number of attributes in a single or multi-value attribute, use the length
operator:
int data[$length(x)$] = { $x; separator=", "$ };
In this example, with x=5,2,9 the following would be emitted:
int data[3] = { 5, 2, 9 };
Null values are counted by length
but you can use the strip
operator to return a new view of your list without null values:
int data[$length(strip(x))] = { $x; separator=", "$ };
Template Application
Imagine a simple template called bold
:
<b>$item$</b>
Just as with template link
described above, you can reference it from a template by invoking it like a method call:
$bold(item=name)$
What if you want something bold and italicized? You could simply nest the template reference:
$bold(item=italics(item=name))$
(or $bold(italics(name))$
if you're using group file format and have formal parameters). Template italics
is defined as:
<i>$item$</i>
using a different attribute with the same name, item
; the attributes have different values just like you would expect if these template references where method calls in say Java or C# and, item
was a local variable. Parameters and attribute references are scoped like a programming language.
Think about what you are really trying to say here. You want to say "make name italics and then make it bold", or "apply italics to the name and then apply bold." There is an "apply template" syntax that is a literal translation:
$name:italics():bold()$
where the templates are applied in the order specified from left to right. This is much more clear, particularly if you had three templates to apply:
$name:courierFont():italics():bold()$
For this syntax to work, however, the applied templates have to reference a standard attribute because you are not setting the attribute in a parameter assignment. In general for syntax expr:template(), an attribute called it
is set to the value of expr. So, the definition of bold
(and analogously italics
), would have to be:
<b>$it$</b>
to pick up the value of name
in our examples above.
As of 2.2 StringTemplate
, you can avoid using it
as a default parameter by using formal arguments. For expression $x:y()$
, StringTemplate
will assign the value of x
to it
and any sole formal argument of y
. For example, if y
is:
y(item) ::= "_$item$_"
then item
would also have the value of x
.
If the attribute to which you are applying a template is null (i.e., missing), then the application is not done as there is no work to do. Optionally, you can specify what string template should display when the attribute is null a using the null
option:
$name:bold(); null="n/a"$
That is equivalent to the following conditional:
$if(name)$$name:bold()$$else$n/a$endif$
Applying Templates To Multi-Valued Attributes
Where template application really shines though is when an attribute is multi-valued. One of the most common web page generation issues is making lists of items either as bullet lists or table rows etc... Applying a template to a multi-valued attribute means that you want the template applied to each of the values.
Consider a list of names (i.e., you set attribute names
multiple times) that you want in a bullet list. If you have a template called listItem
:
<li>$it$</li>
then you can do this:
<ul> $names:listItem()$ </ul>
and each name will appear as a bullet item. For example, if you set names
to "Terence"
, "Tom"
, and "Kunle"
, then you would see:
<ul> <li>Terence</li> <li>Tom</li> <li>Kunle</li> </ul>
in the output.
Whenever you apply a template to an attribute or multi-valued attribute, the default attribute it
is set. Similarly, attributes i
and i0
(since v3.0) of type integer
are set to the value's index number starting from 1
(i0
starts from 0
). For example, if you wanted to make your own style of numbered list, you could reference i
to get the index:
$names:numberedListItem()$
where template numberedListItem
is defined as:
$i$. $it$<br>
In this case, the output would be:
1. Terence<br> 2. Tom<br> 3. Kunle<br>
If there is only one attribute value, then i
will be 1. However, if template numberedListItem
is defined as:
$i0$. $it$<br>
The output would be:
0. Terence<br> 1. Tom<br> 2. Kunle<br>
As when invoking templates ala "includes", a single formal argument is also set to the iterated value. For example, you could define numberedListItem
as follows in a StringTemplateGroup
file:
numberedListItem(item) ::= "$i$. $item$<br>"
Templates are not applied to null values in multi-valued attributes. StringTemplate behaves as if those values simply did not exist in the list. To emit a special string or template for each null value, use the null
option:
$names:bold(); null="n/a"$
which will emit "n/a" for any null value in attribute names
.
Applying Multiple Templates To Multi-Valued Attributes
The result of applying a template to a multi-valued attribute is another multi-valued attribute containing the results of the application. You may apply another template to the results of the first template application, which comes in handy when you need to format the elements of a list before they go into the list. For example, to bold the elements of a list do the following (given the appropriate template definitions from above):
$names:bold():listItem()$
If you actually want to apply a template to the combined (string) result of a previous template application, enclose the previous application in parenthesis. The parenthesis will force immediate evaluation of the template application, resulting in a string. For example,
$(names:bold()):listItem()$
results in a single list item full of a bunch of bolded names. Without the parenthesis, you get a list of items that are bolded.
Applying Alternating Templates To Multi-Valued Attributes
When generating lists of things, you often need to change the color or other formatting instructions depending on the list position. For example, you might want to alternate the color of the background for the elements of a list. The easiest and most natural way to specify this is with an alternating list of templates to apply to an expression of the form: $
expr:t1(),t2(),...,tN()$
. To make an alternating list of blue and green names, you might say:
$names:blueListItem(),greenListItem()$
where presumably blueListItem
template is an HTML <table>
or something that lets you change background color. names[0]
would get blueListItem()
applied to it, names[1]
would get greenListItem()
, and names[2]
would get blueListItem()
again, etc...
If names
is single-valued, then blueListItem()
is applied and that's it.
Applying Anonymous Templates
Some templates are so simple or so unlikely to be reused that it seems a waste of time making a separate template file and then referencing it. StringTemplate
provides anonymous subtemplates to handle this case. The templates are anonymous in the sense that they are not named; they are directly applied in a single instance.
For example, to show a name list do the following:
<ul> $names:{<li>$it$</li>}$ </ul>
where anything enclosed in curlies is an anonymous subtemplate if, of course, it's within an attribute expression. Note that in the subtemplate, I must enclose the it
reference in the template expression delimiters. You have started a new template exactly like the surrounding template and you must distinguish between text and attribute expressions.
You can apply multiple templates very conveniently. Here is the bold list of names again with anonymous templates:
<ul> $names:{<b>$it$</b>}:{<li>$it$</li>}$ </ul>
The output would look like:
<ul> <li><b>Terence</b></li> <li><b>Tom</b></li> <li><b>Kunle</b></li> </ul>
Anonymous templates work on single-valued attributes as well.
As of 2.2, you may define formal arguments on anonymous templates even if you are not using StringTemplate
groups. This syntax is borrowed from Smalltalk though it is identical in function to lambda
of Python. Use a comma-separated list of argument names followed by the '|
' "pipe" symbol. Any single whitespace character immediately following the pipe is ignored. The following example bolds the names in a list using an argument to avoid the monotonous use of it
:
<ul> $names:{ n | <b>$n$</b>}$ </ul>
Clearly only one argument may be defined in this situation: the iterated value of a single list.
Anonymous template application to multiple attributes
In some cases, the model may present data to the view as separate columns of data rather than as a single list of objects, such as multi-valued attributes names
and phones
rather than a single users
multi-valued attribute. As of 2.2, you may iterate over multiple attributes:
$names,phones:{ n,p | $n$: $p$}$
An error is generated if you have too many arguments for the number of attributes. Iteration proceeds while at least one of the attributes (names
or phones
, in this case) has values.
Indirect template references
Sometimes the name of the template you would like to include is itself a variable. So, rather than using "<item:format()>
" you want the name of the template, format
, to be a variable rather than a literal. Just enclose the template name in parenthesis to indicate you want the immediate value of that attribute and then add ()
like a normal template invocation and you get "<item:(someFormat)()>
", which means "look up attribute someFormat
and use its value as a template name; appy to item
." This deliberately looks similar to the C function call indirection through a function pointer (e.g., "(*fp)()
" where fp is a pointer to a function). A better way to look at it though is that the (someFormat)
implies immediately evaluate someFormat and use as the template name.
Usually this "variable template" situation occurs when you have a list of items to format and each element may require a different template. Rather than have the controller code create a bunch of instances, one could consider it better to have StringTemplate
do the creation--the controller just names what format to use.
If StringTemplate
did not have a map definition, you could simulate its functionality. Consider generating a list of C# declarations that are initialized to 0, false, null, etc... You could define a template for int
, Object
, Array
, etc... declarations and then pass in an aggregate object that has the variable declaration object and the format. In a template group file you might have:
group Java; file(variables,methods) ::= << <variables:{ v | <v.decl:(v.format)()>}; separator="\n"> <methods> \>> intdecl(decl) ::= "int <decl.name> = 0;" intarray(decl) ::= "int[] <decl.name> = null;"
Your code might look like:
Java |
StringTemplateGroup group = new StringTemplateGroup(new StringReader(templates), AngleBracketTemplateLexer.class); StringTemplate f = group.getInstanceOf("file"); f.setAttribute("variables.{decl,format}", new Decl("i","int"), "intdecl"); f.setAttribute("variables.{decl,format}", new Decl("a","int-array"), "intarray"); System.out.println("f="+f); String expecting = ""+newline+newline; |
---|---|
C# |
StringTemplateGroup group = new StringTemplateGroup(new StringReader(templates), typeof(AngleBracketTemplateLexer)); StringTemplate f = group.GetInstanceOf("file"); f.setAttribute("variables.{decl,format}", new Decl("i","int"), "intdecl"); f.setAttribute("variables.{decl,format}", new Decl("a","int-array"), "intarray"); Console.Out.WriteLine("f="+f); string expecting = ""+newline+newline; |
Python |
group = stringtemplate3.StringTemplateGroup(file=StringIO(templates), lexer="angle-bracket") f = group.getInstanceOf("file") f.setAttribute("variables.{decl,format}", Decl("i","int"), "intdecl") f.setAttribute("variables.{decl,format}", Decl("a","int-array"), "intarray") print "f =", f expecting = ""+os.linesep |
For this simple unit test, the following dummy decl class is used:
Java |
public static class Decl { String name; String type; public Decl(String name, String type) {this.name=name; this.type=type;} public String getName() {return name;} public String getType() {return type;} } |
---|---|
C# |
public class Decl { string name; string type; public Decl(string name, string type) {this.name=name; this.type=type;} public string Name { get {return name;} } public string Type { get {return type;} } } |
Python |
class Decl(object): def __init__(self, name, type_): self.name = name self.type = type_ def getName(self): return self.name def getType(self): return self.type |
The value of f.ToString()
is:
int i = 0; int[] a = null;
Missing attributes (i.e., null
valued attributes) used as indirect template attribute generate nothing just like referencing a missing attribute.