A lightweight templating engine for .NET Framework 4.5+, .NET Core 2.0+ and Mono.
XtraLiteTemplates is a fully managed .NET Framework library that offers easy string templating features. XtraLiteTemplates offers support for custom template constructs
(akin to statements), and support for expressions
with custom operators. XtraLiteTemplates is not a programming language or a domain-specific language in its own right, its sole purpose is to aid in the creation and manipulation of string templates. Anything else is out of scope of this project.
XtraLiteTemplates does not use the C# compiler services. No intermediate .NET source-code is generated in the process, and no assemblies are compiled as a result.
The easiest way to evaluate a template is by using the built-in facade class XLTemplate
. It works in conjunction with an instance of IDialect
interface to parse and evaluate a template:
var @object = new
{
FirstName = "John",
LastName = "McMann",
Age = 31,
Loves = new String [] { "Apples", "Bikes", "Everything Nice" }
};
var result = XLTemplate.Evaluate(CodeMonkeyDialect.DefaultIgnoreCase,
@"{pre}Hello, {customer.FirstName} {customer.LastName}. You are {customer.Age} years old and you love: {for entity in customer.loves}{entity}, {end}{end}", customer => object);
Console.WriteLine(result);
Will display "Hello, John McMann. You are 31 years old and you love: Apples,Bikes,Everything Nice,"
{preformatted}
Hello {Customer.FirstName},
You have made a purchase on {Order.SourceWebsite.Host} on {Order.DateTime:'g'} in amount of {Order.Currency} {Order.Total:'N'}.
Your items include:
{for each purchased_item in Order.Items} * {purchased_item.Name} with a value of {Order.Currency} {purchased_item.Amount:'N'}{end}
{if Order.DeliveryType == Postal}Your items will be delivered by post on {Order.DeliveryDate:'D'}{end}
{'* This order is a gift' IF Order.IsGift}
With respect,
Somebody responsible.
{end}
The above code block exemplifies a possible email template that uses the Standard
dialect. One is in no way forced to use it; the library allows creation of custom dialects with custom constructs, custom special characters, etc. Nothing is "reserved" by the engine. There are a few small restrictions resulting from the tokenization process but those will be covered in a later area.
And a more exotic example:
var template = "{FOR Member IN GetType().GetMembers()}{Member.Name}{WITH}{PRE}, {END}{END}";
var result = XLTemplate.Evaluate(CodeMonkeyDialect.DefaultIgnoreCase, template);
/* Results in a string (introspection of the IDialect.Self object:
* "get_TypeConverter, String, Number, Boolean, ToString, Equals, GetHashCode, GetType, .ctor, TypeConverter"
*/
Put in the simplest of terms, the process of evaluation of a template follows the following pattern:
- Tokenization, in which a string is split into tokens. The
tokenizer
requires a set of control characters that drive the process (such as the directive open & close characters or string literal escape characters). - Lexical analysis. The
lexer
tries to make sense of the tokens supplied by the tokenization process using a set of given tags and expression operators. - Interpretation. The output of lexical analysis is a stream of lex objects. The
interpreter
will try to match these objects with known directives and build a very simplisticAST
. The interpretation process will return anIEvaluable
interface to the caller. This can be considered as being a compiled template -- ready to be evaluated. - Evaluation can pe performed any number of times on the compiled template. The caller must provide an
IEvaluationContext
object that offers the state, variable access, and other functionality.
The expression builder in XtraLiteTemplates allows for:
- Any number of operators to be defined.
- Either symbols or words can be used to define an operator, but not both at the same time (e.g.
type-of
is not allowed, whiletype_of
is). - Unary and binary operators are supported.
- Short-circuiting is supported (if implemented by the operators).
- Groups are supported, including the comma character. Groups are evaluated to enumerables.
- Operators operate on
object
. - The standard set of operators provided in XtraLiteTemplates tries to emulate the behaviour of JavaScript as much as possible.
- Support for invoking object methods, properties and fields. Runtime selection of overloaded methods is supported to the best possible extent.
- Can invoke methods and properties on dynamic objects using DLR. Conversions and operators are not supported due to conflicting needs.
A tag is a collection of keywords, identifier rules and expressions. For example IF $ THEN
defines a tag that accepts IF
as a first keyword, followed by an expression and then by THEN
keyword. A tag of the following form: FOREACH ? IN $ DO
will match any phrase that starts with the FOREACH
keyword, followed by any identifier, then by IN
keyword, an expression and lastly by the DO
keyword.
Tags are the core building block of directives.
A directive is a collection of one or more tags. A directive can be seen as the priamary language construct that is evaluated at run-time. An example common directive, is the IF
statement - composed of the starting tag IF $ THEN
and an ending tag END
. Such a directive will be configured to evaluate all content found between its tags if the given expression evaluates to TRUE
.
Example:
Hello {IF Customer.Title THEN}{Customer.Title}, {END}{Customer.FirstName} {Customer.LastName}
This template will check whether the Title
property of a provided Customer
object is not empty, in which case the said property will be evaluated, followed by the FirstName
and LastName
properties of the same object.
The interpreter is smart enough to distinguish between similar directives that have one or more differences in their composing tags. As such the following two directives: {IF $ THEN}...{END}
and {IF $ THEN}...{ELSE}...{END}
can coexist and be properly selected by the interpreter.
A dialect is a special object that supplies all the required properties and behaviours that define a language. These include:
- All supported directives,
- Expression operators and the flow control symbols (such as group open and close and member access),
- String comparison and culture settings, and finally,
- The behaviour of unparsed text blocks.