3. Template statements

There are five different type statements. A tag statement is surrounded by @@.

3.1. Comments

Every line starting with @@– are comments and are completely ignored by the parser. The resulting page will have the exact same format and number of lines with or without the comments:

@@-- This template is used to display the client's data
@@-- It uses the following tags:
@@--
@@--    @_CID_@       Client ID
@@--    @_ITEMS_V_@   List of items (vector tag)

<P>Client @_CID_@

...

3.2. INCLUDE statement

This statement is used to include another template file. This is useful if you have the same header and/or footer in all your HTML pages. For example:

@@INCLUDE@@ header.tmplt

<P>This is by Web page

@@INCLUDE@@ footer.tmplt

It is also possible to pass arguments to the include file. These parameters are given after the include file name. It is possible to reference these parameters into the included file with the special variable names @_$<n>_@, where n is the include’s parameter index (0 is the include file name, 1 the first parameter and so on):

@@INCLUDE@@ another.tmplt @_VAR_@ azerty

In file another.tmplt:

@_$0_@

is another.tmplt

@_$1_@

is the variable @_VAR_@

@_$2_@

is the string “azerty”

If an include variable references a non existing include parameter the tag is kept as-is.

Note that it is possible to pass the include parameters using names, a set of positional parameters can be pass first, so all following include commands are identical:

@@INCLUDE@@ another.tmplt one two three four "a text"
@@INCLUDE@@ another.tmplt (one, two, 3 => three, 4 => four, 5 => "a text")
@@INCLUDE@@ another.tmplt (one, 5 => "a text", 3 => three, 2 => two, 4 => four)

The file name can also be a tag. In this case the file loading is deferred at the parsing time.

For security reasons the filename can’t be a full pathname. If a full pathname is passed then the leading directory separator is removed.

3.3. IF statement

This is the conditional statement. The complete form is:

@@IF@@ <expression1>
  part1
@@ELSIF@@ <expression2
  part2
@@ELSE@@
  part3
@@END_IF@

<expression> is TRUE if it evaluates to one of “TRUE”, “T” or “1” and FALSE otherwise. Note that the test is not case sensitive.

The part1 one will be parsed if expression1 evaluate to TRUE, part2 will be parsed if expression2 evaluate to TRUE and the part3 will be parse in any other case. The ELSIF and ELSE parts are optional.

The expression here is composed of Boolean variables and/or Boolean expression. Recognized operators are:

A = B

Returns TRUE if A equal B

A /= B

Returns TRUE if A is not equal B

A > B

Returns TRUE if A greater than B. If A and B are numbers it returns the the number comparison (5 > 003 = TRUE) otherwise it returns the string comparison (‘5’ > ‘003’ = FALSE).

A >= B

Returns TRUE if A greater than or equal to B. See above for rule about numbers.

A < B

Returns TRUE if A lesser than B. See above for rule about numbers.

A <= B

Returns TRUE if A lesser than or equal to B. See above for rule about numbers.

A and B

Returns TRUE if A and B is TRUE and FALSE otherwise.

A or B

Returns TRUE if A or B is TRUE and FALSE otherwise.

A xor B

Returns TRUE if either A or B (but not both) is TRUE and FALSE otherwise.

A in B

Returns TRUE if A is found into the composite tag B and FALSE otherwise. B must be a tag. If B contains a single value then this expression is equivalent to (A = B).

not A

Returns TRUE if either A is FALSE and FALSE otherwise.

A & B

Returns the catenation of A and B. A and B can be either strings or variables.

The default evaluation order is done from left to right, all operators having the same precedence. To build an expression it is possible to use parenthesis to change the evaluation order. A value with spaces must be quoted as a string. So valid expressions could be:

@@IF@@ (@_VAR1_@ > 3) or (@_COND1_@ and @_COND2_@)

@@IF@@ not (@_VAR1_@ > 3) or (@_COND1_@ and @_COND2_@)

@@IF@@ (@_VAR1_@ > 3) and not @_COND1_@

@@IF@@ @_VAR1_@ = "a value"

@@IF@@ "/" & @_FILE_@ = "/filename"

Note also that variables and values can be surrounded by quotes if needed. Quotes are needed if a value contain spaces.

Let’s see an example using an IF tag statement. With the following template:

@@IF@@ @_USER_@
   <p>As a user you have a restricted access to this server.
@@ELSE@@
   <p>As an administrator you have full access to this server.
@@END_IF@@

The following program:

with Ada.Text_IO;
with Templates_Parser;

procedure User1 is
   Translations : constant Templates_Parser.Translate_Table :=
                    (1 => Templates_Parser.Assoc ("USER", True));
begin
   Ada.Text_IO.Put_Line
     (Templates_Parser.Parse ("user.tmplt", Translations));
end User1;

Will display:

   <p>As a user you have a restricted access to this server.

But the following program:

with Ada.Text_IO;
with Templates_Parser;

procedure User2 is
   Translations : constant Templates_Parser.Translate_Table :=
                    (1 => Templates_Parser.Assoc ("USER", False));
begin
   Ada.Text_IO.Put_Line
     (Templates_Parser.Parse ("user.tmplt", Translations));
end User2;

Will display:

   <p>As an administrator you have full access to this server.

3.4. TABLE statement

Table tags are useful to generate HTML tables for example. Basically the code between the @@TABLE@@ and @@END_TABLE@@ will be repeated as many times as the vector tag has values. If many vector tags are specified in a table statement, the code between the table will be repeated a number of times equal to the maximum length of all vector tags in the TABLE tag statement.

A TABLE tag statement is a kind of implicit iterator. This is a very important concept to build HTML tables. Using a composite tag variable in a @@TABLE@@ tag statement it is possible to build very complex Web pages.

Syntax:

@@TABLE['REVERSE]['TERMINATE_SECTIONS]['TERSE]['ALIGN_ON("sep")]@@
...
[@@BEGIN@@]
...
[@@SECTION@@]
...
[@@END@@]
...
@@END_TABLE@

Let’s have an example. With the following template:

<p>Here is the ages of some peoples:

<table>
@@TABLE@@
   <tr>
   <td>@_NAME_@
   <td>@_AGE_@
@@END_TABLE@@
</table>

And the following program:

with Ada.Text_IO;
with Templates_Parser;

procedure Table is

   use type Templates_Parser.Vector_Tag;

   Names : constant Templates_Parser.Vector_Tag :=
             +"Bob" & "Bill" & "Toto";
   Ages  : constant Templates_Parser.Vector_Tag :=
             +"10" & "30" & "5";

   Translations : constant Templates_Parser.Translate_Table :=
                    (1 => Templates_Parser.Assoc ("NAME", Names),
                     2 => Templates_Parser.Assoc ("AGE", Ages));
begin
   Ada.Text_IO.Put_Line
     (Templates_Parser.Parse ("table.tmplt", Translations));
end Table;

The following output will be generated:

<p>Here is the ages of some peoples:

<table>
   <tr>
   <td>Bob
   <td>10
   <tr>
   <td>Bill
   <td>30
   <tr>
   <td>Toto
   <td>5
</table>

Note that we use vector tag variables here. A discrete variable tag in a table will be replaced by the same (the only one) value for each row. A vector tag outside a table will be displayed as a list of values, each value being separated by a specified separator. The default is a comma and a space “, “.

The complete prototype for the Tag Assoc function is:

function Assoc
  (Variable  : in String;
   Value     : in Tag;
   Separator : in String := Default_Separator) return Association;
--  Build an Association (Variable = Value) to be added to Translate_Table.
--  This is a tag association. Separator will be used when outputting the
--  a flat representation of the Tag (outside a table statement).

A table can contain many sections. The section to use will be selected depending on the current line. For example, a table with two sections will use different data on even and odd lines. This is useful when you want to alternate the line background color for a better readability when working on HTML pages.

A table with sections can have attributes:

REVERSE

The items will be displayed in the reverse order.

TERMINATE_SECTIONS

This ensure that the table output will end with the last section. If the number of data in the vector variable tag is not a multiple of the number of sections then the remaining section will be complete with empty tag value.

TERSE

Empty lines won’t be output. If the composite tag used into the table has an empty value then the corresponding line won’t be output. This is especially important to avoid empty ending lines for table containing vector of different size.

ALIGN_ON

The content of table will be aligned on the given separators. Multiple separators may be specified as coma separated strings, for example ALIGN_ON(“:”,”:=”). Each line will have the corresponding separator aligned in the specified order. That is, on the example above we first align on “:” and then “:=”, if another “:” is found on the line it is not taken into account.

For the following template:

<p>Here are some available computer devices:

<table>
@@TABLE@@
   <tr bgcolor=#FF0000>
   <td>@_DEVICES_@
   <td>@_PRICES_@
@@SECTION@@
   <tr bgcolor=#00000F>
   <td>@_DEVICES_@
   <td>@_PRICES_@
@@END_TABLE@@
</table>

<table>
@@TABLE'TERMINATE_SECTIONS@@
   <tr>
   <td bgcolor=#00000F width=10>
   <td width=150>@_DEVICES_@
@@SECTION@@
   <td width=150>@_DEVICES_@
@@SECTION@@
   <td width=150>@_DEVICES_@
   <td bgcolor=#00000F width=10>
@@END_TABLE@@
</table>

And the following program:

with Ada.Text_IO;
with Templates_Parser;

procedure Table_Section is

   use type Templates_Parser.Vector_Tag;

   Devices : constant Templates_Parser.Vector_Tag :=
               +"Screen" & "Keyboard" & "Mouse" & "Hard Drive";
   Prices  : constant Templates_Parser.Vector_Tag :=
               +"$500" & "$20" & "$15" & "$140";

   Translations : constant Templates_Parser.Translate_Table :=
                    (1 => Templates_Parser.Assoc ("DEVICES", Devices),
                     2 => Templates_Parser.Assoc ("PRICES", Prices));
begin
   Ada.Text_IO.Put_Line
     (Templates_Parser.Parse ("table_section.tmplt", Translations));
end Table_Section;

The following output will be generated:

<p>Here are some available computer devices:

<table>
   <tr bgcolor=#FF0000>
   <td>Screen
   <td>$500
   <tr bgcolor=#00000F>
   <td>Keyboard
   <td>$20
   <tr bgcolor=#FF0000>
   <td>Mouse
   <td>$15
   <tr bgcolor=#00000F>
   <td>Hard Drive
   <td>$140
</table>

<table>
   <tr>
   <td bgcolor=#00000F width=10>
   <td width=150>Screen
   <td width=150>Keyboard
   <td width=150>Mouse
   <td bgcolor=#00000F width=10>
   <tr>
   <td bgcolor=#00000F width=10>
   <td width=150>Hard Drive
   <td width=150>
   <td width=150>
   <td bgcolor=#00000F width=10>
</table>

It is important to note that it is possible to avoid code duplication by using the @@BEGIN@@ and @@END@@ block statements. In this case only the code inside the block is part of the section, the code outside is common to all sections. Here is an example to generate an HTML table with different colors for each line:

The template file above can be written this way:

<p>Here are some available computer devices:

<table>
@@TABLE@@
   <tr bgcolor=
   @@BEGIN@@
      "#FF0000"
   @@SECTION@@
      "#000000F"
   @@END@@
   >
   <td>@_DEVICES_@
   <td>@_PRICES_@
@@END_TABLE@@
</table>

Another example to for the ALIGN_ON table attribute:

procedure Call is
@@TABLE'ALIGN_ON(":",":=")@@
   @_DECLS_@
@@END_TABLE@@
begin
   null;
end Call;

And the following program:

with Ada.Text_IO;
with Templates_Parser;

procedure Table_Align is

   use type Templates_Parser.Vector_Tag;

   Decls : constant Templates_Parser.Vector_Tag :=
             +"Count : constant Integer := 8;"
            & "Name : String := ""A Name"";"
            & "I : Integer;";

   Translations : constant Templates_Parser.Translate_Table :=
                    (1 => Templates_Parser.Assoc ("DECLS", Decls));
begin
   Ada.Text_IO.Put_Line
     (Templates_Parser.Parse ("table_align.tmplt", Translations));
end Table_Align;

The following output will be generated:

procedure Call is
   Count : constant Integer := 8;
   Name  : String           := "A Name";
   I     : Integer;
begin
   null;
end Call;

Into a table construct there are some additional variable tags available:

@_UP_TABLE_LINE_@

This tag will be replaced by the table line number of the upper table statement. It will be set to 0 outside a table statement or inside a single table statement.

@_TABLE_LINE_@

This tag will be replaced by the current table line number. It will be replaced by 0 outside a table statement.

@_NUMBER_LINE_@

This is the number of line displayed in the table. It will be replaced by 0 outside a table statement.

@_TABLE_LEVEL_@

This is the table level number. A table construct declared in a table has a level value of 2. It will be replaced by 0 outside a table statement.

Let’s have a look at a more complex example with mixed IF and TABLE statements.

Here is the template:

Hello here is a list of devices:

<table>
<tr>
<th>Device Name
<th>Price
<th>Order

@@TABLE@@
<tr>
<td>@_DEVICES_@
<td>@_PRICES_@

<td>
@@IF@@ @_AVAILABLE_@
<a href="/order?DEVICE=@_DEVICES_@">Order</a>
@@ELSE@@
Sorry, not available
@@END_IF@@

@@END_TABLE@@
</table>

And the following program:

with Ada.Text_IO;
with Templates_Parser;

procedure Table_If is

   use type Templates_Parser.Vector_Tag;

   function In_Stock (Device : in String) return Boolean;
   --  Complex function. Does a SQL access to the right database to know if
   --  the Device is available and thus can be ordered.

   procedure Add (Device, Price : in String);
   --  Add the device into the list to be displayed

   Devices   : Templates_Parser.Tag;
   Prices    : Templates_Parser.Tag;
   Available : Templates_Parser.Tag;

   ---------
   -- Add --
   ---------

   procedure Add (Device, Price : in String) is
   begin
      Devices   := Devices & Device;
      Prices    := Prices & Price;
      Available := Available & In_Stock (Device);
   end Add;

   --------------
   -- In_Stock --
   --------------

   function In_Stock (Device : in String) return Boolean is
   begin
      if Device = "Keyboard" then
         return True;
      else
         return False;
      end if;
   end In_Stock;

   Translations : Templates_Parser.Translate_Table (1 .. 3);

begin
   Add ("Screen", "$500");
   Add ("Keyboard", "$15");
   Add ("Mouse", "$15");
   Add ("Hard Drive", "$140");

   Translations := (Templates_Parser.Assoc ("DEVICES", Devices),
                    Templates_Parser.Assoc ("PRICES", Prices),
                    Templates_Parser.Assoc ("AVAILABLE", Available));

   Ada.Text_IO.Put_Line
     (Templates_Parser.Parse ("table_if.tmplt", Translations));
end Table_If;

The following output will be generated:

Hello here is a list of devices:

<table>
<tr>
<th>Device Name
<th>Price
<th>Order

<tr>
<td>Screen
<td>$500

<td>
Sorry, not available

<tr>
<td>Keyboard
<td>$15

<td>
<a href="/order?DEVICE=Keyboard">Order</a>

<tr>
<td>Mouse
<td>$15

<td>
Sorry, not available

<tr>
<td>Hard Drive
<td>$140

<td>
Sorry, not available

</table>

Table tag statements can also be used with matrix tag or more nested tag variables. In this case, for a tag variable with N nested levels, the Nth closest enclosing TABLE tag statement will be used for the corresponding index. If there are not enough indexes, the last axis are just streamed as a single text value.

Let’s see what happens for a matrix tag:

  • Inside a table of level 2 (a TABLE statement inside a TABLE statement).

    In this case the first TABLE iterates through the matrix lines. First iteration will use the first matrix’s vector, second iteration will use the second matrix’s vector and so on. And the second TABLE will be used to iterate through the vector’s values.

  • Inside a table of level 1.

    In this case the TABLE iterates through the matrix lines. First iteration will use the first matrix’s vector, second iteration will use the second matrix’s vector and so on. Each vector is then converted to a string by concatenating all values using the specified separator (see Assoc constructor for Tag or Set_Separator routine).

  • Outside a table statement.

    In this case the matrix is converted to a string. Each line represents a vector converted to a string using the supplied separator (see point 2 above), and each vector is separated by an ASCII.LF character. The separators to use for each level can be specified using Set_Separator.

Let’s look at an example, with the following template:

A matrix inside a table of level 2:

@@TABLE@@
<tr>
@@TABLE@@
<td>
@_MAT_@
</td>
@@END_TABLE@@
</tr>

@@END_TABLE@@

The same matrix inside a single table:

@@TABLE@@
<tr>
<td>
@_MAT_@
</tr>

@@END_TABLE@@

The same matrix outside a table:

@_MAT_@

Using the program:

with Ada.Text_IO;
with Templates_Parser;

procedure Matrix is

   package TP renames Templates_Parser;

   use type TP.Tag;

   V1 : constant TP.Vector_Tag := +"A1.1" & "A1.2";
   V2 : constant TP.Vector_Tag := +"A2.1" & "A2.2";
   V3 : constant TP.Vector_Tag := +"A3.1" & "A3.2";

   M  : constant TP.Matrix_Tag := +V1 & V2 & V3;

begin
   Ada.Text_IO.Put_Line
     (TP.Parse ("matrix.tmplt",
                TP.Translate_Table'(1 => TP.Assoc ("MAT", M))));
end Matrix;

We get the following result:

A matrix inside a table of level 2:

<tr>
<td>
A1.1
</td>
<td>
A1.2
</td>
</tr>

<tr>
<td>
A2.1
</td>
<td>
A2.2
</td>
</tr>

<tr>
<td>
A3.1
</td>
<td>
A3.2
</td>
</tr>


The same matrix inside a single table:

<tr>
<td>
A1.1, A1.2
</tr>

<tr>
<td>
A2.1, A2.2
</tr>

<tr>
<td>
A3.1, A3.2
</tr>


The same matrix outside a table:

A1.1, A1.2
A2.1, A2.2
A3.1, A3.2

3.5. SET statement

The SET command tag can be used to define a constant or an alias for an include file parameter. This is especially important in the context of reusable template files. For example, instead of having many references to the red color in an HTML document, it is better to define a constant COLOR with the value red and use COLOR everywhere. It is then easier to change the color afterward.

The first form, to define a simple constant that can be used as any other variable in a template file, is:

@@SET@@ <name> = <value>

The second form, to define an alias for a template file parameter, is:

@@SET@@ <name> = $n [| <default_value>]

In this case <name> is an alias for the Nth include parameter. In this form it is also possible to define a default value that would be used if the Nth include parameter is not specified.

Some examples:

@@SET@@ COLOR = red

@@SET@@ SIZE = $1

@@SET@@ COLOR = $4 | green

It is important to note that a variable is set global to a template file. It means that constants set into an include file are visible into the parent template. This is an important feature to be able to have a “theme” like include template file for example.

3.6. INLINE statement

The INLINE statement can be used to better control the result’s layout. For example it is not possible to have the results of a vector tag on the same line, also it is not possible to have a conditional output in the middle of a line. The INLINE block tag statement can be used to achieve that.

Elements in an inlined block are separated by a single space by default. It is possible to specify any string as the separator. The text layout on an INLINE block has no meaning (the lines are trimmed on both side). As part of the inline command it is possible to specify texts to output before and after the block.

Syntax:

@@INLINE[(<before>)(<separator>)(<after>)]@@
...
@@END_INLINE@

There are three supported uses:

@@INLINE@@

In this case there is no text before and after the block and the separator is a single space.

@@INLINE(<separator>)@@

In this case there is no text before and after the block and the separator is the string given as parameter <separator>.

@@INLINE(<before>)(<separator>)(<after>)@@

In this case all three values are explicitly given.

<before>, <separator> and <after> may contain control characters:

\n

To insert a new-line (CR+LF or LF depending on the Operation System)

\r

To insert a line-feed

\\

To insert a single backslash

Let’s look at an example, with the following template:

@@INLINE(colors=")(, )(")@@
   @@TABLE@@
      @_COLORS_@
   @@END_TABLE@@
@@END_INLINE@@

Using the program:

with Ada.Text_IO;
with Templates_Parser;

procedure Table_Inline is

   use type Templates_Parser.Vector_Tag;

   Colors : constant Templates_Parser.Vector_Tag :=
              +"Red" & "Green" & "Blue";

   Translations : constant Templates_Parser.Translate_Table :=
                    (1 => Templates_Parser.Assoc ("COLORS", Colors));
begin
   Ada.Text_IO.Put_Line
     (Templates_Parser.Parse ("table_inline.tmplt", Translations));
end Table_Inline;

We get the following result:

colors="Red, Green, Blue"

Another example with an IF tag statement:

@@INLINE@@
A
@@IF@@ @_COND_@
   big
@@ELSE@@
   small
@@END_IF@@
car.
@@END_INLINE@@

Using the program:

with Ada.Text_IO;
with Templates_Parser;

procedure If_Inline is

   use type Templates_Parser.Vector_Tag;

   Translations : constant Templates_Parser.Translate_Table :=
                    (1 => Templates_Parser.Assoc ("COND", True));
begin
   Ada.Text_IO.Put_Line
     (Templates_Parser.Parse ("if_inline.tmplt", Translations));
end If_Inline;

We get the following result:

A big car.

3.7. MACRO statement

The MACRO statement is used to defined macros that can be used in other places in the template. The macro statement takes a single parameter which is the name of the macro.

Syntax:

@@MACRO(NAME)@@
...
@@END_MACRO@

The code inside the macro can be anything supported by the templates engine. There is no restriction. The parameters inside the macro are referenced as @_$N_@ (where N is a number and corresponds to the Nth parameter passed to the macro). There is no maximum number of parameters. A reference to a parameter that has no corresponding formal parameter at the call point is ignored (the value will be the empty string).

For example:

@@MACRO(SOMETEXT)@@
Some text, first parameter is @_$1_@ and the second is @_$2_@.
@@END_MACRO@@
@@--
@_SOMETEXT(12,@_VAR_@)_@
@_UPPER:SOMETEXT(Ada,GNAT)_@

For using macros see Macros.

3.8. EXTENDS and BLOCK statements

The EXTENDS statement is similar to INCLUDE. However, it is possible to replace parts of the included file. These parts are defined with the BLOCK statement.

Syntax:

@@EXTENDS@@ filename [variables]
  @@BLOCK(name1)@@
  ...
  @@END_BLOCK@@

  @@BLOCK(name2)@@
  ...
  @@END_BLOCK@@
@@END_EXTENDS@

And in the included file (filename in the above example):

...

@@BLOCK(name1)@@
  default contents1
@@END_BLOCK@@

@@BLOCK(name3)@@
default contents3
@@END_BLOCK@@
...

When parsing the first file, it will automatically include the contents of the second file. However, the various BLOCK will be replaced by the value given in the EXTENDS statement, if a value is provided. If no value is provided, the default value given in the included file will be used.