-
Notifications
You must be signed in to change notification settings - Fork 11
Keywords
All morestachio instructions are enclosed by a leading pair of {{
and must end by a pair of }}
. Morestachio has 4 different kinds of instructions.
Note: All keywords are case-insensitive but I recommend writing keywords upper-cased to easily distingue them from other parts of your template
Tags are instructions in the Morestachio syntax. All Tags start with a hashtag {{#
or an Circumflex accent {{^
, followed by the keyword and end with }}
. Most tags can accept one or more arguments.
For example: {{#INCLUDE nameExpression}}
is a tag as the #INCLUDE
tag does not encase other children.
A block is two tags encasing content or other instructions. Special for block is that the closing tag always start with a slash /
followed by the opening keyword.
For example: {{#SCOPE dataExpression}} ... {{/SCOPE}}
is a Block with the #SCOPE
keyword enclosing other instructions.
Paths are tags, that do not start with a hashtag #
. They are used to print a expression into the output.
For example: {{path.to.data}}
will evaluate the expression path.to.data
and print whatever is at that location.
Morestachio has build-in HTML escaping so everything printed via the Path tag, is HTML escaped. If you want to print html code from an path, you have to either prefix your path with &
to disable the html-escaping like this:
{{root.HtmlCode}} <- Prints html code escaped
{{&root.HtmlCode}} <- Prints html code as-is
or you can disable the HTML enencoding via the ParserOptionsBuilder.WithDisableContentEscaping(bool)
. If disabled everything is printed as-is.
The path is always tested against the scope it is written in (see #Scope section below). To navigate up from that scope for a path, you can chain an leading ../
or ../../
to print something one or two scopes up respectively or use the ~.
(approximation) sign to write something from the root scope.
In some cases you must prefix your path with an $
(doller) character to access special variables. Examples for that are the collection variables for #EACH
,#FOREACH
,#REPEAT
,#DO
,#WHILE
allowing access to for example the $index
variable inside a collection, the $Name
and $recusion
variables in partials, the $services
variable or the $null
variable allowing you to set a per-template null representation.
Content is everything else that is not enclosed by {{
}}
and printed as-is as long as not in a #NOPRINT
block.
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 notfalse
-
If value is
double
Then value is not0
-
If value is
int
Then value is not0
-
If value is
string
Then value is notString.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}}
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
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}}
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
you can iterate collections with {{#EACH path}}
or {{#FOREACH variableName IN path}}
and enumerate objects with {{#EACH path.?}}
The difference between #EACH
and #FOREACH
is that #EACH
will scope to the item but #FOREACH
will not. For that reason you have to declare a variable with the #FOREACH
syntax.
Example Each
{{#EACH root.collection}}
{{prop.a.b}}
{{/EACH}}
Example ForEach
{{#FOREACH item IN root.collection}}
{{item.prop.a.b}}
{{/FOREACH}}
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}}
//or you can use FOREACH
{{#FOREACH item IN root.object.?}}
{{item.Key}} <-Emits the name/key of the property or Key of IDictionary<string,object> respectively
{{item.Value}} <-Emits the value of the property or item in a Dictionary
{{/FOREACH}}
Both #Each
and Every
can be aliased and #FOREACH
must declare an alias.
Example Each
{{#EACH root.collection AS item}} <-- this creates a new variable that contains the element of root.collection and is essentailly the same as {{#FOREACH item IN root.Collection}}
{{item.prop.a.b}}
{{/EACH}}
There are 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.
A #DO
loop, executes its contents and then checks for the condition to be met before executing its contents again.
Example Do
{{#DO $index.SmallerAs(5)}}
{{$index}},
{{/DO}}
This example will output the numbers from 1-4 and then stops.
A #WHILE
loop, checks for its condition to be met before executing its children.
Example While
{{#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.
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 even though you have access to
$last
within an#while
and#do
loop, it is never true, because the engine cannot predict when the condition will possibly false before executing its content. ** Example: **
{{#DO $index < 10}}
IsLast == {{$last}} <-- will always print false
{{/DO}}
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. ** Example: **
{{#EACH Data.List AS item}}
{{#SCOPE item.Name}}
{{$index}} <-- this is not guarantied to print the correct index.
{{item.$index}} <-- this is
{{/SCOPE}}
{{/EACH}}
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 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.
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}}
Please see the dedicated Variables page: https://github.com/JPVenson/morestachio/wiki/Variables
With the use of the {{#ISOLATE options}} ... {{/ISOLATE}}
block you can create scopes that enforces specific isolation behavior.
With the #ISOLATE #VARIABLES
block all usages of #VAR
and #LET
do not overwrite any variable outside the scope.
Example:
{{#VAR globalVariable = "test"}} <-- as #var declares a global variable it is never reset when used
{{globalVariable}} <-- prints test
{{#ISOLATE #VARIABLES}} <-- this enforces the same behavior onto #var as with #let in its children
{{#VAR globalVariable = "123"}}
{{globalVariable}} <-- prints 123
{{/ISOLATE}}
{{globalVariable}} <-- prints test
When used, the #ISOLATE #SCOPE expression
will scope its children to the expression but also cuts off the access to any other object so it acts as if expression
is the root of the document
Example:
{{#VAR $null = "NULL"}}
{{root.User.Name}} <-- prints JohnDoe
{{#ISOLATE #SCOPE root.User.Name}}
{{this}} <-- prints JohnDoe
{{~}} <-- prints JohnDoe
{{../Name}} <-- should go one level up and access Name but as we are isolated the current scope does not have a parent so prints NULL
{{/ISOLATE}}
The NoPrint
block allows for execution of all children such as {{#VAR name = ...}}
document items, but prohibits the output of content into the output stream. All content and path items will be omitted from output. This is helpful if you want to have a block of multiple variable setups or partial declarations.
{{#NOPRINT}}
It does not matter what we try to output here.
{{DateTime.Now.ToString("D")}} this will be evaluated but not printed to output
{{#VAR x = "test"}}
{{/NOPRINT}}
{{x}} //this will be printed again
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!
{{/!}}
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}}
{{/!?}}
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
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.
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 |