Skip to content
JP edited this page Feb 19, 2021 · 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 or if you want to print only particular paths without content escaping prefix them with an &.

Example

ParserOptions.DisableContentEscaping = false;   
{{root.HtmlCode}} <- Prints html code escaped
{{&root.HtmlCode}} <- Prints html code as-is
ParserOptions.DisableContentEscaping = true;   
{{root.HtmlCode}} <- Prints html code as-is
{{&root.HtmlCode}} <- Prints html code as-is

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

An else is a fallback block that will be executed when the if blocks expression does not evaluate to the DefinitionOfTrue Example

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

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

Invalid Example

{{#IF root.subA.subB}}
   {{root.subA.subB}} 
   {{#SCOPE root}}
      {{#ELSE}} <-- INVALID as not directly descending the #if
         {{root.subA.subC}} 
      {{/ELSE}}
   {{/SCOPE}}
{{/IF}}

You can also combine the Else & if together and just write {{#ELSEIF expression}}

Example

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

Please note that both #ELSE and #ELSEIF are optional in the #IF context. You can add multiple #ELSEIF expression but only one #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 #SCOPE 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.

Deprication note:
The {{#Path}} and {{^Path}} syntax for scoping is set to be deprecated in future versions. Please use the tags from the section below and migrate.

Example

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

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

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

or go directly back to the root with ~
Example

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

apart from that the #SCOPE 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 ^SCOPE keyword.
Example

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

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

{{#SCOPE root.subA.subB AS sub}} <--this will scope to root.subA.subB if it not 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 Note that the all special variables are only available on the scope they are created and you must ether be in the scope of the (e.g) #EACH or access the Alias it was set to.

Alias

You can declare an alias for both #SCOPE and #EACH keywords. An Alias behaves like an #LET variable and is only set to the value for its children. when the Block is closed, the Alias cease to exist.

Example Alias

{{#SCOPE 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 at some time 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

Comments

You can comment a single tag by putting an exclamation mark at the first char like This is {{!Not Printed}} nothing or you can use block comments by using the {{!}} tag to start an block comment. To end a block comment you must write an {{/!}} tag. Comment blocks can be nested like this

Text
{{!}}
Comment
{{Anything that will not be printed}}
{{ even invalid tags can be printed '"
{{!}}
This is a nested comment
{{/!}}
anything goes!
{{/!}}

Escaping Blocks

If you want to print morestachio like text you have to enclose it with an escaping block like this:

Text
{{Data.Path.Executed}}
{{!?}}
anything between these tags will be treated as content {{Even.Expressions}} {{#Or other Tags}}
{{/!?}}

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 one whitespace 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 as this is a {{name}} text. The pre/suffix -| and |- will only remove all whitespaces and the first Linebreak it will find. To remove all whitespaces and linebreaks before the first non whitespace char you must use --| and |-- pre/suffix accordingly.

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
TrimLeading Removes all leading whitespace and the first newlines from every keyword before every token #SET OPTION TrimLeading = true
TrimTailing Removes all tailing whitespace and the first newlines from every keyword following #SET OPTION TrimTailing = true
TrimAllLeading Removes all leading whitespace and newlines from every keyword before every token #SET OPTION TrimAllLeading = true
TrimAllTailing Removes all tailing whitespace and newlines from every keyword following #SET OPTION TrimAllTailing = true