Core Concepts

The TemplateEngine

TemplateEngine is the only entry point. Construct it with a template string and call Merge(object) to produce output.

var ngin = new TemplateEngine("Hello {Name}.");
string result = ngin.Merge(new { Name = "World" });

Tag delimiters

Tags use single curly braces. The prefix determines tag behavior:

PrefixTag typeExample
(none)Simple bind{Name}
#ifConditional block{#if Condition}…{/if}
#eachIteration block{#each Items}…{/each}
#withScope-shift block{#with Path}…{/with}
>Partial template{>Partial}
?var:Variable declaration{?var:name = value}
?:Variable reassignment{?:name = value}
:Variable read{:name}
!Comment{! comment here }
@Debug tag{@ expr }

Bracket escaping

Double any literal brace in the template: {{{, }}}. An unmatched } outside a tag is a parse error.

Local scope and $

$ is the current local scope — the object passed to Merge at the root, the current item inside {#each}, or the target inside {#with}. Bare names resolve against local scope automatically, so $ is optional for property access:

new TemplateEngine("Hello {$.FirstName}").Merge(data);
// equivalent to:
new TemplateEngine("Hello {FirstName}").Merge(data);

$ is mandatory when iterating scalars via {#each} (no property name to bind against). It is also a reserved character — it cannot appear in property names.

var result = new TemplateEngine("To: {#each Emails}{$};{/each}")
                 .Merge(new { Emails = new[] { "john@mail.com", "jane@mail.com" } });

Expected output:

To: john@mail.com;jane@mail.com;

Whitespace inside tags

Whitespace inside a tag is insignificant: {Address.City} and { Address.City } are equivalent. Whitespace inside string literal arguments is preserved exactly as quoted.

Falsy semantics

{#if}, {#each}, and {#with} evaluate the bound expression against a fixed falsy ruleset:

  • false
  • null
  • numeric zero
  • empty string
  • empty collection
  • DBNull.Value

Everything else is truthy.

Missing is not falsy. Binding a property, field, or dictionary entry that doesn’t exist on the bound object throws. Model possibly-absent state with a null or default value if you need the falsy path.

Engine state

TemplateEngine is not thread-safe. Use one instance per thread or guard a shared instance externally.

MemberTypeDefaultPurpose
TrimWhitespaceboolfalseApply whitespace trimming to every eligible tag. See Whitespace Control.
LambdaRepoLambdaRepositoryemptyRegistry of named functions callable from any tag. See Lambda Expressions.
MaxStackconst int64Maximum block + partial nesting depth. See Stack Depth Limit.

Next steps