StringTemplate Comparison

This page benchmarks Bellevue features to the features of its inspiration StringTemplate. The feature table is taken from StringTemplate cheat sheet . (Written by Terence Parr, last edited by J E Bailey on Jul 01, 2009, Copyright unknown, assumed BSD license as the code).

NOTE: My purpose is not in any way to prove that Bellevue is better than StringTemplate - it would be like comparing apples and fruit cocktail. StringTemplate is designed to produce pretty much any target language or format. Bellevue is designed solely for creating HTML Web pages. As such, it should be much more restricted, but also - hopefully - more productive in that narrow domain.

Expression elements

ST Syntax
ST Description
Bellevue Example (source)
Notes
Example (rendered)
<attribute>
Evaluates to the value of attribute.ToString() if it exists else empty string.
#attributeExample span
{
    text-add: data(ControllerTime);
}
#attributeExample input
{
    attr: value data(ControllerDictTime);
}
Model time: 5/23/2019 4:07:50 AM
This is only a very basic example and is really not comparable to the power of StringTemplate syntax: "<attribute>" could be anywhere, whereas the example here only shows how to replace the entire text or attribute value.

However, Relative positioning allows more precise control on where the text should be added and e.g. text-replace and text-format declarations allow more control within text nodes.
<i>, <i0>
The iteration number indexed from one and from zero, respectively, when referenced within a template being applied to an attribute or attributes.
@template productRowTemplate "#indexExampleRow"
{
    td:nth-child(1) { text-inner: index() }
    td:nth-child(2) { text-inner: index(1) }
    td:nth-child(3) { text-inner: item(Name) }
}

#indexExample tbody
{
    apply-template: model(ProductList) productRowTemplate;
}
Index Number Name
0 1 Gadget
1 2 Widget
2 3
3 4 Gizmo
4 5 Foobar
When a template is applied to a collection, you can call index() function with either 0 or 1 argument.

Note that third item is null in data.
<attribute.property>
Looks for property of attribute as a property (C#), then accessor methods like getProperty() or isProperty(). If that fails, StringTemplate looks for a raw field of the attribute called property. Evaluates to the empty string if no such property is found.
#attrPropExample span
{
    text: data(Product.Name);
}
#attrPropExample input
{
    attr: value data(Product.Name);
}
Widget
Does only checks for normal C# standard properties (i.e. no getProperty() etc.)

Fields and dictionaries within model are currently not supported, but I am considering them.
<attribute.(expr)>
Indirect property lookup. Same as attribute.property except use the value of expr as the property_ name. Evaluates to the empty string if no such property is found.
#attrExpressionExample
{
    text-add: data(data(TimePropName));
}
Model time: 5/23/2019 4:07:50 AM
TimePropName contains randomly either property name "ControllerTime" or "ControllerDictTime".
<multi-valued-attribute>
Concatenation of ToString() invoked on each element. If multi-valued-attribute is missing his evaluates to the empty string.
#multivalAttrExample
{
    text-add: join(data(ProductNames));
} 
Gadget, Widget, , Gizmo, Foobar
Note that the default separator is ", ". Use empty string "" to concatenate without any separators.
<multi-valued-attribute; separator=expr>
Concatenation of ToString() invoked on each element separated by expr.
#multivalAttrSeparExample
{
    html-add: join(data(ProductNames), "<br />");
}
Gadget
Widget

Gizmo
Foobar
See above
<[mine, yours]>
Creates a new multi-valued attribute (a list) with elements of mine first then all of yours.
TODO
<template(argument-list)>
Include template. The argument-list is a list of attribute assignments where each assignment is of the form arg-of-template=expr where expr is evaluated in the context of the surrounding template
not of the invoked template.
TODO
<(expr)(argument-list)>
Include template whose name is computed via expr. The argument-list is a list of attribute assignments where each assignment is of the form attribute=expr. Example $(whichFormat)()$ looks up whichFormat's value and uses that as template name. Can also apply an indirect template to an attribute.
TODO
<attribute:template(argument-list)>
Apply template to attribute. The optional argument-list is evaluated before application so that you can set attributes referenced within template. The default attribute it is set to the value of attribute. If attribute is multi-valued, then it is set to each element in turn and template is invoked n times where n is the number of values in attribute. Example: $name:bold() applies bold() to name's value.
TODO
<attribute:(expr)(argument-list)>
Apply a template, whose name is computed from expr, to each value of attribute. Example $data:(name)()$ looks up name's value and uses that as template name to apply to data.
TODO
<attribute:t1(argument-list): ... :tN(argument-list)>
Apply multiple templates in order from left to right. The result of a template application upon a multi-valued attribute is another multi-valued attribute. The overall expression evaluates to the concatenation of all elements of the final multi-valued attribute resulting from templateN's application.
TODO
<attribute:{anonymous-template}>
Apply an anonymous template to each element of attribute. The iterated it atribute is set automatically.
TODO
<attribute:{argument-name_ | _anonymous-template}>
Apply an anonymous template to each element of attribute. Set the argument-name to the iterated value and also set it.
TODO
<a1,a2,...,aN:{argument-list_ | _anonymous-template}>
Parallel list iteration. March through the values of the attributes a1..aN, setting the values to the arguments in argument-list in the same order. Apply the anonymous template. There is no defined it value unless inherited from an enclosing scope.
TODO
<attribute:t1(),t2(),...,tN()>
Apply an alternating list of templates to the elements of attribute. The template names may include argument lists.
TODO
<first(attr)>
The first or only element of attr. You can combine operations to say things like first(rest(names)) to get second element.
#firstItemExample
{
    text-add: get(first(data(ProductList)), Name);
}
Gadget
Currently, these work only on IEnumerables, but this implementation may still change to work exactly as in StringTemplate (non-IEnumerable is like a an IEnumerable with length 1).
<last(attr)>
The last or only element of attr.
#lastItemExample
{
    text-add: get(last(data(ProductList)), Name);
}
Foobar
<rest(attr)>
All but the first element of attr. Returns nothing if $attr$ a single valued.
#restExample table
{
    apply-template-add: rest(data(ProductList)) productRowTemplate;
}
0 1 Widget
1 2
2 3 Gizmo
3 4 Foobar
<trunc(attr)>
returns all but last element
#truncExample table
{
    apply-template-add: trunc(data(ProductList)) productRowTemplate;
}
0 1 Gadget
1 2 Widget
2 3
3 4 Gizmo
<strip(attr)>
Returns an iterator that skips any null values in $attr$. strip(x)
=x when x is a single-valued attribute.
#stripExample table
{
    apply-template-add: strip(data(ProductList)) productRowTemplate;
}
0 1 Gadget
1 2 Widget
2 3 Gizmo
3 4 Foobar
At the moment, also rest and trunc (probably also first and last) will skip null values in a collection. Need to think this through: is there any value for displaying null values in apply-template or should they always be removed (or alternatively use another method so that they would be shown in rest, trunc etc.) Opinions welcome!
<length(attr)>
Return an integer indicating how many elements in length $attr$ is. Single valued attributes return 1. Strings are not special; i.e., length("foo") is 1 meaning "1 attribute". Nulls are counted in lists so a list of 300 nulls is length 300. If you don't want to count nulls, use length(strip(list)).
#lengthExample
{
    text-add: length(data(ProductList));
}
5
\$ or \<
escaped delimiter prevents $ or < from starting an attribute expression and results in that single character.
- -
Does not apply
<\ >, <\n>, <\t>, <\r>
special character(s): space, newline, tab, carriage return. Can have multiple in single <...> expression.
#specialCharactersExample pre
{
    html: "spaces&#160;&#xa0;&nbsp;
    LF&#10;tab&#9;CR&#13;end";
}
#specialCharactersExample input
{
    value: "spaces&#160;&#xa0;&nbsp;tab&#9;end";
}
spaces   LF
tab	CR
end

Bellevue script is rendered as HTML, which means that you can use HTML entities. There are no specific mechanisms for controlling the HTML source code formatting (comparable to StringTemplate auto indent). The original HTML whitespace is however preserved, which should satisfy most cases.
<\uXXXX>
Unicode character(s). Can have multiple in single <...> expression.
#unicodeExample span 
{
    html: "&copy; &#xa9; &#169;";
}
#unicodeExample input 
{
    value: "&copy; &#xa9; &#169;";
}
© © ©
Bellevue script is rendered as HTML, which means that you can use HTML entities.
<! comment !>, $! comment !$
Comments, ignored by StringTemplate.
/* Comment in bellevue script */
-
Comments in Bellevue script are written as in CSS.

Statements

TODO: Documentation of @if-directive and implementation of elseif and else structures.

Syntax Description
<if(attribute)>subtemplate
<else>subtemplate2
<endif>
If attribute has a value or is a boolean object that evaluates to true, include subtemplate else include subtemplate2. These conditionals may be nested.
<if(x)>subtemplate
<elseif(y)>subtemplate2
<elseif(z)>subtemplate3
<else>subtemplate4
<endif>
First attribute that has a value or is a boolean object that evaluates to true, include that subtemplate. These conditionals may be nested.
<if(!attribute)>subtemplate<endif> If attribute has no value or is a bool object that evaluates to false, include subtemplate. These conditionals may be nested.