A simple templating engine for .NET Standard 2.0.
var template = "$person.Name's father was $person.Father.Name.";
var variables = new Dictionary<string, object>
{
["person"] = new
{
Name = "Stephen",
Father = new { Name = "Frank" }
}
};
var output = Symple.Parser
.Parse(template)
.Render(variables);
// Output: "Stephen's father was Frank."var template = @"
<h1>Planets</h1>
<ul>@[$planet:$planets] {
<li>
<h2>$planet.Name</h2>?[$planet.Moons] {
<strong>$planet.Name has #$planet.Moons moon?[#$planet.Moons!=1]{s}</strong>
<ul>@[$moon:$planet.Moons] {
<li>$moon</li>}
</ul>} {
<strong>$planet.Name has no moons</strong>}
</li>}
</ul>";
var variables = new Dictionary<string, object>
{
["planets"] = new[]
{
new { Name = "Earth", Moons = new[] { "Moon" } },
new { Name = "Mars", Moons = new[] { "Phobos", "Deimos" } },
new { Name = "Venus", Moons = new string[0] { /* Venus has no moons */ } }
},
};
var output = Symple.Parser
.Parse(template)
.Render(variables);
// Output:
// <h1>Planets</h1>
// <ul>
// <li>
// <h2>Earth</h2>
// <strong>Earth has 1 moon</strong>
// <ul>
// <li>Moon</li>
// </ul>
// </li>
// <li>
// <h2>Mars</h2>
// <strong>Mars has 2 moons</strong>
// <ul>
// <li>Phobos</li>
// <li>Deimos</li>
// </ul>
// </li>
// <li>
// <h2>Venus</h2>
// <strong>Venus has no moons</strong>
// </li>
// </ul>- Variables
$varor$[var]$var.a.b.cfor nested objects.
- Conditionals
?[condition] {if} {else}- or without the else
?[condition] {if}
- Loops
@[$x:$xs] {use $x here}- Variable
$xsmust implementIEnumerable, i.e. pretty much every .NET collection class will work.
- Logical operators - operands are evaluated as
bool.!Logical NOT&&Logical AND||Logical OR
- Comparison operators - operands are evaluated as
decimal<Less than>Greater than<=Less than or equal>=Greater than or equal- Note if any expression that is not a numeric is part of the comparision it will yield
false.
- Equality operators - operands are evaluated as
string.==Equal!=Not Equal
- Grouping in a condition to override default operator precedence
- e.g.
?[$a && ($b || $c)]
- e.g.
- Collection count
?[#$items == 1]{exactly 1 item}- Evaluates to the length of the
IEnumerablevariable as an integer. - If the variable is not an
IEnumerablewill evaluate to""/false.
- Evaluates to the length of the
The core types used in Symple are string for rendering, bool for conditionals and decimal for any numeric comparisons.
You can pass any .NET type as a variable to the template but keep in mind when rendering a variable Symple will call .ToString() on it so using anything other than a string as the final expression can lead to unwanted output. For example, an int[] variable would be rendered as System.Int32[] which is unlikely what you want.
Any expression can be used directly in the conditional expression, e.g. ?[$var] { ... } is valid syntax for any variable. For any type other than bool we will convert the expression's value to a bool by these rules:
null:falsestrings:s.Length > 0IEnumerablee:e.Any()- Any other value v with type T:
!v.Equals(default(T)).- Any numeric type (
int/floatetc) istruewhen not0. charis true when not\0.- Any reference type is
truewhen notnull.
- Any numeric type (
The conditional expression ?[condition] {if} {else} allows the following expressions as condition, where any expression other than bool will be implicitly converted to bool.
- Variables
?[$x]- See in "Implicit bool conversion" how variables are converted to
bool.
- Strings
?["str"]?[$a == "str with $var"]- Note unlike in the template itself it requires
"delimiters. - A string evaluates to
trueunless empty ("").
- Numbers
?[1]?[2.5 > 2]- Numbers evaluate to
trueunless equal to0.
- Logical expressions (
!,&&,||and parentheses)?[$a && $b]?[!$a]?[!$a && ($b || $c)]- Note, this evaluates operands as
bool
- Comparison (
<,>,<=,>=)?[$a > $b && $a < 2]?[$a <= 1.5 || $a >= 4]- Note, this evaluates operands as
decimal
- Equality (
==,!=)?[$a == "value"]?[$b != "other"]- Note, this evaluates operands as
string
- Collection count operator
?[#$items > 1]
- Nested conditionals
?[?[$a]{$b}{$c}]- This condition evaluates
$bif$ais true, otherwise$c. - Based on
$bor$ctheiforelsebranch of the original conditional is executed.
- This condition evaluates
- Possible, though potentially confusing
All the above expressions can be infinitely combined using the various operators.
Symple defines the following special characters:
$?@#"}\
Any character can be escaped using \, even if it is not a special character. For example, the template H\ello\, \World\! will result in Hello, World!.
However, there is generally no need to escape the special characters in a template. The exception is the escape character \, which always escapes the next character thus always needs to be escaped itself. That is, to render C:\Windows the template needs to be C:\\Windows.
Because Symple uses 2 or more characters for most of its syntax there is no need to escape any of the other special characters when they appear by themselves except " (only inside a condition) and } (only inside an if/else/loop branch).
For example, the Symple parser understands that name@example.com is not a loop expression, just like $100 cannot be a variable because 100 is not a valid identifier. Likewise, using a ? in a sentence or using #hashtag is not a problem.
The only things you actually need to escape are these sequences if you want them to render as-is without being considered Symple syntax:
-
\->\\- As mentioned above the escape character
\always needs to be escaped
- As mentioned above the escape character
-
$var->\$var$followed by a letter or_must be escaped to not be considered a variable
-
?[->\?[?[must be escaped to not be considered a conditional expression
-
@[->\@[@[must be escaped to not be considered a loop expression
-
#$var->\#$var#followed by a valid variable needs to be escaped to not be considered a collection count expression
-
?["str with "quotes""]->?["str with \"quotes\""]- A string inside a condition needs to escape
".
- A string inside a condition needs to escape
-
?[1] { use } in here }->?[1] { use \} in here }}inside aniforelsebranch needs to be escaped- Similarly in a loop, i.e.
@[..]{ in here }.
You can use Parser.Escape(string input) to escape special characters.