Skip to content

Latest commit

 

History

History
648 lines (519 loc) · 14 KB

statements.md

File metadata and controls

648 lines (519 loc) · 14 KB

Sempare Template Engine

Copyright (c) 2019-2024 Sempare Limited

Statements

flow control

break

The for and while loops can include break statements to assist with flow control.

An example using break:

<% 
    i := 0; 
    while true;
      if i = 3;
         break;
      end;
    print(str(i) + ' ');
    i:=i+1;
    end;
%>

This will produce

0 1 2

The loop will terminate when a break statement is reached.

continue

The for and while loops can include continue statements to assist with flow control.

An example using continue:

<% 
    for i := 0 to 10;
       if i mod 2 = 1;
           continue;
       end;
       print(str(i) + ' ');
    end 
%>

This will produce:

0 2 4 6 8 10 

The loop will jump to start another enumeration without evaluating anything else within the block.

for

for range

The for to/downto loop comes in two forms as it does in Object Pascal:

increasing integers from 1 to 10
<% for i := 1 to 10 %>
    number <% i %>
<% end %>

decreasing integers from 10 down to 1
<% for i := 10 downto 1 %>
    number <% i %>
<% end %>

NOTE Loop variables can be updated within the scope of the block without the loop integrity being compromised.

You can change the step interval as follows:

<% for i := 1 to 10 step 2 %>
    number <% i %>
<% end %>

for in

The other for in variation is as follows:

Attendees:
<%for i in _ %> 
   <% i.name %> <% i.age %>
<%end%> 

You may also use offset and limit on for in statements:

Attendees:
<%for i in _ offset 20 limit 10 %> 
   <% i.name %> <% i.age %>
<%end%> 

for in event

Further, there are onbegin, onend, betweenitems and onempty events that can be used.

Attendees:
<%for i in _ %> 
		<li><% i.name %> <% i.age %></li>
   <% onbegin %>
		<ul>
   <% onend %>
		</ul>
   <% onempty %>
		<i>There are no entries</i>   
<%end%>

With the following Delphi:

type
  TInfo = record
    Name: string;
    age: integer;
    constructor create(const n: string; const a: integer);
  end;
var
  info: TList<TInfo>;
begin
  info := TList<TInfo>.create;
  info.AddRange([TInfo.create('conrad', 10), TInfo.create('christa', 20)]);
  writeln(Template.Eval(template, info));
  info.Free;
end;

The above produces the output:

Attendees:
   conrad 10
   christa 20

When using for-in on arrays, the loop variable enumerates all the index values of the array.

var
  a : array[10..20] of integer;
  b: TArray<integer>;
  

begin
   setlength(b, 10);
   // ...
   writeln(Template.Eval('<% for idx in _ %> <% idx %> <% end %>', a)); // 10..20
   writeln(Template.Eval('<% for idx in _ %> <% idx %> <% end %>', b)); //  0..9
   // ...
end;

When using for-of on arrays, the loop variable enumerates all the values of the array.

var
  a : array[10..20] of integer;
  b: TArray<integer>;
  i : integer;
begin
   for i := low(a) to high(a) do a[i] := i-9;
   setlength(b, 10);
   for i := low(b) to high(b) do b[i] := i * 5;
   // ...
   writeln(Template.Eval('<% for val of _ %> <% val %> <% end %>', a)); // 1..11
   writeln(Template.Eval('<% for val of _ %> <% val %> <% end %>', b)); // 0, 5, 10, 15 .. 45
   // ...
end;

Using for-in on TDataSet, the loop variable enumerates rows. The loop variable can be dereferenced with the name of the field in the given row.

if

if

You may want to conditionally include content:

Some text
<% if condition %>
some conditional text
<% end %>
Some more text

In the above scenario, you can conditionally include some text by referencing the condition. This could be a variable or a complex expression. If the condition expression is a non boolean value, such as a string or a number, the expression evaluates to true for a non empty string and a non zero number.

More complicated condition blocks can be created using elif and else statements.

some text
<% if condition %>
    block 1
<% elif condition2 %>
    block 2
<% elif condition3 %>
    block 3
<% else %>
    last block
<% end %>
    the end

When an expression references a collection (dataset, list, tarray, stack, queue), the expression evaluates to true only if the collection contains items. This means you can easily check for content:

<% if lst %>
the list as at least one item
<% end %>

while

while

While blocks are very flexibe looping constructs based on a boolean condition being true.

<% i := 1 %>
<% while i <= 3 %>
   <% i %>
   <% i := i + 1%>
<% end %>

This produces the following:

   1
   2
   3

NOTE you must ensure the terminating condition is eventually true so that the template can be rendered.

While loops may also have offset and limits defined:

<% i := 1 %>
<% while i <= 10 offset 5 limit 5 %>
   <% i %>
   <% i := i + 1%>
<% end %>

while events

While loops may also have onbegin, onend, betweenitems and onempty events defined:

<% i := 1 %>
<% while i <= 10 offset 5 limit 5 %>
			<% i %>
			<% i := i + 1%>
		<% onbegin %>
			<ul>
		<% onend %>
			</ul>
		<% onempty %>
			<i>no values</i>
<% end %>

misc

assignment

assign

Within a script block, you can create temporary variables for use within the template. Basic types such a string, boolean and numbers can be used.

<% num := 123 %>
<% str := 'abc' %>
<% bool := false %>

with

with

The with() statement is used to localise variables from a nested structure.

To illustrate, the following

type
	TInfo = record
		level1 : record
			level2 : record
				value : string;
				level3 : record
					level4 : record
						value : string;
					end;
				end;
			end;
		end;
	end;
begin
	var info : TInfo;
	info.level1.level2.value := 'test';
	info.level1.level2.level3.level4.value := 'hello';	
	Template.Eval('<% level1.level2.level3.level4.value %> <% level1.level2.level3.level4.value %> <% level1.level2.level3.level4.value %>', info)

	// can be replaced with
	Template.Eval('<% with level1.level2.level3.level4 %><% value %> <% value %> <% value %><% end %>', info)

The with() statement will push all fields/properties in a record/class into a new scope/stack frame.

output

cycle

cycle

This allows for values to cycle when rendering content. e.g.

<% for person in people %>
          <tr class="<% cycle ('odd-row','even-row') %>">
              <td><% person.firstname %></td>
              <td><% person.lastname %></td>
          </tr>
    <% onbegin %>
        <table>
            <tr>
                <th>First Name</th>
                <th>Last Name</th>
            </tr>
    <% onend %>
        </table>
    <% onempty %>
       There are no entries
</% end%>

Given a 'people' structure:

type
    TPerson = class
    public:
        firstname:string;
        lastname: string;
        constructor Create(const AFirstName, ALastName:string);
    end;
    
var
    LPeople := TObjectList<TPerson>.Create;
    // ...
    LPeople.Add(TPerson.Create('Mary', 'Smith'));
    LPeople.Add(TPerson.Create('Peter', 'Pan'));
    LPeople.Add(TPerson.Create('Joe', 'Bloggs'));
    // ...

would yield something like:

	<table>
        <tr>
            <th>First Name</th>
            <th>Last Name</th>
        </tr>
        <tr class="odd-row">
            <td>Mary</td>
            <td>Smith</td>
        </tr>
        <tr class="even-row">
            <td>Peter</td>
            <td>Pan</td>
        </tr>
        <tr class="odd-row">
            <td>Joe</td>
            <td>Bloggs</td>
        </tr>
	</table>

ignorenl

ignorenl

The purpose of an ignorenl block is to allow for template designers to space out content for easy maintenance, but to allow for the output to be more compact when required.

<table>
<% ignorenl %>
  <tr>
    <td>Col1</td>
    <td>Col2</td>
  </tr>
<% end %>
</table>

This would yield something like

<table>
  <tr>  <td>Col1</td>   <td>Col2</td>  </tr>
</table>

ignorews

ignorews

The purpose of an ignorews block is to allow for template designers to space out content for easy maintenance, but to allow for the output to be more compact when required.

<table>
<% ignorews *%>
  <tr>
    <td>Col1</td>
    <td>Col2</td>
  </tr>
<% end %>
</table>

This would yield something like

<table>
<tr>
<td>Col1</td>
<td>Col2</td>
</tr>
</table>

Mixing ignorenl and ignorews:

<table>
<% ignorenl ; ignorews %>
  <tr>
    <td>Col1</td>
    <td>Col2</td>
  </tr>
<% end; end %>
</table>

yields

<table>
<tr><td>Col1</td><td>Col2</td></tr>
</table>

print

print expr

print

Within a script, all text outside of the script '<%' start and '%>' end tokens is output.

hello world

This outputs 'hello world' as expected.

<% stmt := 'is a' %>
This <% stmt %> test.

The above example results in 'this is a test.' being output.

You may also rely on the print() statement.

<% print('this is a test') %>

template management

block

block

You may want to decompose templates into reusable parts. You register templates on a Template context.

<% block 'content' %>
Some content
<% end %>

The block statment defines a placeholder in a template that can be replaced. When a block is contained within an extends block, it will be a replacement in the referenced template.

extends

extends

extends() allows you to replace blocks within another template.

<% template 'header_footer' %>
<html>
    <head>
        <title>My Page</title>
    </head>
    <body>
        <% block 'content'%>the content<% end %>
    </body>
</html>
<% end %>

Without the extends statement, include('header_footer') would resolve to:

<html>
    <head>
        <title>My Page</title>
    </head>
    <body>
        the content
    </body>
</html>

Using the extends statement...

<% extends ('header_footer') %>
   // content here will be ignored
   <% block 'content' %><p>Welcome to my page</p><% end %>
<% end %>

would resolve to:

<html>
    <head>
        <title>My Page</title>
    </head>
    <body>
        <p>Welcome to my page</p>
    </body>
</html>

extends() can also take a second parameter, allowing for improved scoping of variables, similar to the with statement.

include

include

You may want to decompose templates into reusable parts. You register templates on a Template context.

type
    TInfo = record
        year : integer;
        company : string;
        email : string;
    end;
begin
   var ctx := Template.context();
   ctx['year'] := 2019;
   ctx['company'] := 'Sempare Limited';
   ctx['email'] := '[email protected]';
   ctx.RegisterTemplate('header', Template.Parse('Copyright (c) <% year %> <% company %> '));
   ctx.RegisterTemplate('footer', Template.Parse('Contact us <% email %> '));
   var tpl := Template.parse('<% include (''header'') %> some content <% include (''footer'') %>');

include() can also take a second parameter, allowing for improved scoping of variables, similar to the with statement.

We have a 'functional' include syntax:

    <% template 'button' %>
      <button text="<% text %>">
    <% end %>
    
    <% button { text="add" } %>
    
              or 
    
    <% button text="remove" %>

Above we can see we don't need to use the explict 'include' statement.

require

require

The require() statement is used to validate that the input variable is of a particular type.

e.g.

type
	TInfo = record
		Name : string;
	end;
	
begin
	var info : TInfo;
	info.name := 'Jane';
	writeln(Template.Eval('<% require(''TInfo'') %>', info));
end;

An exeption is thrown when the

require can take multiple parameters - in which case the input must match one of the types listed.

template

template

Localised templates can also be defined locally within a template.

The include() statement is used to render a local template as is the normal behaviour. Note that local templates take precedence over templates defined in a context when they are being resolved.

<% template 'mytemplate' %>
	<% value %>
<% end %>
<% include ('mytemplate', level1.level2) %>	
<% include ('mytemplate', level1.level2.level3.level4) %>