Skip to content
JP edited this page Oct 29, 2020 · 65 revisions

Keyword Structure

All Keywords are enclosed by {{Keyword}} and all Tag Keywords (single statement keywords) start with {{#Keyword. If you write a block keywords (keywords spanning over multiple other keywords) you always have to close it with another statement that starts with {{/Keyword or the corresponding alias.

Note: Most keywords within Blocks are indented for the sake of readability. If you care about indentions that you can ether use the -| and |- suffix/prefix to get rid of all leading and tailing whitespaces before and after an keyword (see chapter "Whitespace control") or use the option TrimTailing = true or TrimLeading = true (see chapter "Set Option")

Note: All keywords are case-insensitive but I recommend writing keywords upper-cased to easily distingue them from other parts of your template

Keywords with expressions

Some keywords take an expression. Expressions can be composed of an string and path or an number.

Path Syntax

With the use of dots you can navigate down the tree of objects and print whatever is at that location.

Example

{{root.sub.sub.sub.sub.sub}}

by default, everything is HTML encoded that means if you print a string that is containing HTML chars like <, > or & they will be replaced. To change this behavior use the ParserOptions.DisableContentEscaping flag.

If

To emit or omit parts of your template you can check if they are matching a DefinitionOfFalse. The DefaultDefinitionOfFalse is currently as folloring:

  • value is not null
  • If value is bool Then value is not false
  • If value is double Then value is not 0
  • If value is int Then value is not 0
  • If value is string Then value is not String.Empty
  • If value is IEnumerable Then value is not Empty

The DefaultDefinitionOfFalse is stored in a static variable of the ContextObject but can be overwritten by setting the DefinitionOfFalse.

To apply this to your template you can ether use the Scopeing keyword (see below) or use the #if keyword.
Example

{{#IF root.subA.subB}}
   {{root.subA.subB}}
{{/IF}}

if you want to omit something that does not match the DefinitionOfFalse you can use the ^IF keyword.
Example

{{^IF root.subA.subB}}
   {{root.subA.subB}} 
{{/IF}}

Else

You can add an else block to any #If and ^If expression just by appending it the the next element
Example

{{#IF root.subA.subB}}
   {{root.subA.subB}} 
{{/IF}}
There can be any CONTENT but no other expression
{{#ELSE}}
   {{root.subA.subC}} 
{{/ELSE}}

An else must be a direct neighbour to an if with the exception for content so that this is would be invalid:

Invalid Example

{{#IF root.subA.subB}}
   {{root.subA.subB}} 
{{/IF}}
There can be any CONTENT but no other expression
{{root.Test}} <--This is not allowed!
{{#ELSE}}
   {{root.subA.subC}} 
{{/ELSE}}

You can also combine the If&Else together and just write {{#ifelse}}

Example

{{#IF root.subA.subB}}
   {{root.subA.subB}} 
{{#IFELSE}}
   {{root.subA.subC}} 
{{/ELSE}}

Switch Case,Default

The #switch & #case keyword allows to create one or more branches that will be executed when a certain value or Expression equals one specific value

An #switch must always enclose all #case and #default keywords, if you use the #case or #default keyword outside an #switch, it will print only its children. All #case blocks will be checked from top to bottom and the first matching block will be executed.

Example

{{#SWITCH data.data}}
   {{#CASE 'Test'}}
Value is Test
   {{/CASE}}
   {{#CASE 'Untest'}}
Value is Untest
   {{/CASE}}
   {{#DEFAULT}}
Value is neither Test nor Untest
   {{/DEFAULT}}
{{/SWITCH}}

If no #case blocks expression is matching the input from its enclosing #switch, the #default block is executed and if not #default block is present nothing will be printed.

A #switch keyword does usually not scope its children to the checked value but you can enable this by adding the #scope keyword
Example

{{#SWITCH data.data #SCOPE}}
   {{#CASE 'Test'}}
Value is {{.}}
   {{/CASE}}
   {{#CASE 'Untest'}}
Value is {{.}}
   {{/CASE}}
   {{#DEFAULT}}
Value is neither Test nor Untest but {{.}}
   {{/DEFAULT}}
{{/SWITCH}}

Scopeing

To change the scope of your current data you can use the # keyword. If you scope to a path in your data, it has the same effect as the #if keyword but also allows you to shorten certain very long paths.

Example

{{#root.subA.subB}}
   {{.}} <-This emits "yourself"
{{/root.subA.subB}}

while in a scope you can navigate up 1 or more levels with ../
Example

{{#root.subA.subB}}
   {{../subB}}
{{/root.subA.subB}}

or go directly back to the root with ~
Example

{{#root.subA.subB}}
   {{~root.subA}}
{{/root.subA.subB}}

apart from that the # keyword does also fit the need for an "if exists" statement. Any value that does not fit the DefinitionOfFalse will be rendered within a scope. If you want to render a scope if the DefinitionOfFalse does not match you can use the ^ keyword.
Example

{{^root.subA.subB}} <--this will only scope if the path `root.subA.subB` does _not_ match the DefinitionOfFalse
   {{~root.subA}}
{{/root.subA.subB}}

Scopes can be aliased so that you only need to write the path once if you want to access the data.
Example

{{#root.subA.subB AS sub}} <--this will scope to root.subA.subB if it matches the DefinitionOfFalse and put the value of it into "sub"
   {{sub.Data}} <--this and the next statement are the same
   {{Data}}
{{/sub}} <--when using an alias it also can be used to close the block

Each / Every

you can iterate collections with {{#each path}} or enumerate objects with {{#each path.?}}
Example Each

{{#EACH root.collection}}
   {{prop.a.b}}
{{/EACH}}

Example Every

{{#EACH root.object.?}}
   {{Key}} <-Emits the name/key of the property or Key of IDictionary<string,object> respectively
   {{Value}} <-Emits the value of the property or item in a Dictionary
{{/EACH}}

Both Each and Every can be aliased.
Example Each

{{#EACH root.collection AS item}} <-- this creates a new variable that contains the element of root.collection
   {{item.prop.a.b}}
{{/EACH}}

Do / While / Repeat

There are both Do, While and Repeat loops in Morestachio. All accept an expression and Do & While will iterate their children as long as the expression does return true where Repeat will iterate a fix number of times.

Example Do

{{#DO $index.SmallerAs(5)}}
    {{$index}},
{{/DO}}

This example will output the numbers from 1-4 and then stops.

Example Do

{{#WHILE $index.SmallerAs(5)}}
    {{$index}},
{{/WHILE}}

This example will output the numbers from 1-5 and then stops.

Example Repeat

{{#REPEAT 10}} <-- this will print {{$index}} exactly 10 times
    {{$index}},
{{/REPEAT}}

this example will output the number from 0-9.

Special Variables for Each, Do, While & Repeat

When using one of the keywords for enumerating a list, you have also access to special variables related to that loop. All special variables are prefixed with $ to indicate an generated value:


Name Description
$first Returns a boolean value that indicates whether this is the first step in the loop
$last Returns a boolean value that indicates whether this is the last step in the loop
$middel Returns a boolean value that indicates whether this is nether the first step nor the last step in the loop
$index Returns a the current index step in the loop
$odd Returns a boolean value that indicates that $index is and odd value
$even Returns a boolean value that indicates that $index is and even value

Note that nevertheless you might have access to $last within an #while and #do loop but as the template cannot predict when the condition will possibly end this might never be true

Alias

You can declare an alias for both # and #each keywords. By using the alias you can both close the tag with the alias and use the alias as first element in any expression to access the value like this:

Example Alias

{{#root.data AS rootInData}} {{rootInData.Property}} {{/rootInData}}
{{#each root.data AS rootInData}} {{rootInData.Property}} {{/rootInData}}

By suffixing the Path with "AS Name" you declare the Alias.

If you use an alias that name is also existing in the current object and you want do access the current object instead of the alias you can simply use the "." as first part of you expression.

Partials

Partials are reusable templates within your template. You must declare a Partial before you can use it.
Example Partial

{{#DECLARE NAME}}
Any Template {{code}} that will be executed in another scope
{{/DECLARE}}

Within an partial you also have access to the $recursion variable that indicates the current depth of all nested partials.

#Include and #Import

You have two options in how to invoke a partial. #import and #include. #include should be seen as obsolete but will stay functional for now. You should opt to use the newer #import keyword.

The difference between #import and #include is that while #include does only accept a static name for its partial whoever it does validate whenever that partial does exist at parse-time. #import on the other hand does allow you to set the name dynamically by using an expression but does validate whenever a partial exists only at render-time. Example Partial Include vs Import

{{#INCLUDE PartialName}} <-- PartialName is evaluated at parse-time and cannot be changed from within the template
{{#IMPORT 'PartialName'}} <-- the string 'PartialName' is evaluated at render-time
{{#IMPORT Path.To.PartialName.Or.Variable}} <-- also valid as evaluated at render-time and can be dynamicly changed by using an variable for example

It is also possible to set the context the partial should be rendered in by utilizing the #with inline keyword

Example Partial Include vs Import

{{#IMPORT 'PartialName' #with context.data}} <-- the string 'PartialName' is evaluated at render-time
{{#IMPORT Path.To.PartialName.Or.Variable #with context.data}} <-- also valid as evaluated at render-time and can be dynamicly changed by using an variable for example
{{#IMPORT Path.To.PartialName.Or.Variable #with GetContextForPartialFormatter($name)}} <-- you also have access to the $name variable and could get dynmaic context for your partial

You can also declare templates that imports them self.

Example Hierarchical Partial

{{#DECLARE NAME}}
Any Template {{code}} that will be executed in another scope and includes
itself
   {{#IMPORT $name}}
{{/DECLARE}}

Note the usage of the special keyword $name that is available within any partial to access the string name of the currently executing partial.

You should always define a condition that stops the execution sometimes as the default level (can be modified) of maximum depth is 255.

Example Hierarchical Partial with Condition

{{#DECLARE NAME}}
Any Template {{code}} that will be executed in another scope and includes
itself and stops when a object is not present
   {{#IF child}}
      {{#IMPORT $name #with child}}
   {{/IF}}
{{/DECLARE}}

Variables

Please see the dedicated Variables page: https://github.com/JPVenson/morestachio/wiki/Variables

Text Operations

There are 3 Text operations that can modify static content

  • #NL (NewLine) Adds a linebreak at the position of the tag
  • #TNL (TrimNewLine) Trims one following linebreak in the next content regardless of following Tags
  • #TNLS(TrimNewLines) Trims all following linebreak in the next content regardless of following Tags

Whitespace control

You can also use the {{-| prefix and the |-}} suffix to removes any whitespace and newlines ether before and/or after a keyword like:

This is a <       
{{-| name}}> text

or

This is a <{{name |-}}
> text

or both

This is a <
{{-| name |-}} 
> text:   

will all output the same text without whitespaces.

Set Option

The keyword #SET OPTION name = Expression allows you to set ParseTime options. This kinds of tokens are not emited to the list of Tokens. List of possible Options:


Name Description Example
TokenPrefix Changes the start of an Token from '{{' to an custom string #SET OPTION TokenPrefix = "<&"
TokenSuffix Changes the end of an Token from '}}' to an custom string #SET OPTION TokenPrefix = "&>"
TrimLeading WIP Removes all leading whitespace and newlines from every keyword following #SET OPTION TrimLeading = true
TrimTailing WIP Removes all tailing whitespace and newlines from every keyword following #SET OPTION TrimTailing = true