10 Oct 2022

expandfile Tutorial and examples

This page contains an expandfile tutorial consisting of a sequence of examples.

Each example shows input file contents, the command line invocation of expandfile, and the resulting output. You can read the input files in light green, and the command invocation in pink, and see if you understand why the output in blue came out that way.

Refer to the expandfile reference document for an explanation of the language and the builtin functions.

More examples will be supplied in the future. Suggestions are welcome.

Basic expansion

expandfile reads an input file and writes an output file.

Example 1: copy input to output

Input characters not inside %[...]% are just copied to the output.

test1.htmx
Hello world, this is test1.
Shell Command
expandfile test1.htmx
Shell Output
Hello world, this is test1.

Example 2: backslash

The backslash character \ makes the next character "unspecial" and is removed. To output a single backslash use \\.

test2.htmx
This is test2. To output a backslash \\, use \\\\.
Shell Command
expandfile test2.htmx
Shell Output
This is test2. To output a backslash \, use \\.

Example 3: variables

expandfile keeps a list of variables and their values. Variables have names and values that are strings of characters. (Upper and lower case letters in variable names are different.)

expandfile replaces a variable name enclosed in %[...]% by its value.

The builtin variable "date" returns the date. (There are currently 16 builtin variables.)

test3.htmx
This is test3. The date is %[date]%. Unspecial-ing the percent sign makes it ordinary text: \%[date]\%.
Shell Command
expandfile test3.htmx
Shell Output
This is test3. The date is 10 Oct 2022. Unspecial-ing the percent sign makes it ordinary text: %[date]%.

A construct like %[*name...]% invokes a builtin function provided by expandfile. Builtin functions have names that begin with *. The invocation is replaced by the result of the function (some builtin functions return no value).

Variable names are case sensitive. Upper and lower case letters in variable names are different.

test3a.htmx
This is test3a, showing that variable names are case sensitive. %[*set,&foo,="1234"]% %[*set,&FOO,="5678"]% %[*if,eq,foo,FOO,*expand,="case does not matter in if,= test"]% %[*if,ne,foo,FOO,*expand,="case does matter in if,= test"]% foo %[foo]% FOO %[FOO]%
Shell Command
expandfile test3a.htmx
Shell Output
This is test3a, showing that variable names are case sensitive. case does matter in if,= test foo 1234 FOO 5678

Expanding Variables

Example 4: setting a variable

Expanding the builtin function *set,&varname,=constant sets the value of variable varname to constant. Literal constant values in an argument list begin with =. Builtin function arguments are separated by commas; to specify a constant value that contains a comma, use double quotes, like ="dogs, cats".

test4.htmx
This is test4. %[*set,&fred,=3]% The value of "fred" is %[fred]%.
Shell Command
expandfile test4.htmx
Shell Output
This is test4. The value of "fred" is 3.

If you accidentally left out the =, you would be trying to access the content of the variable named "3". expandfile warns missing = before numeric argument '3'. You could use double quotes and write ="3" instead of =3 above, to make it clear that the 3 is a constant.

Example 5: Quoting, Constants and Escaping

(In builtin argument lists, percent-bracket characters inside double-quoted strings are not expanded.)

Within the expansion of %[...]%, backslash (\) removes the special meaning of the following character. The example .htmx file below creates the constants '<a href="' and '">' by using a backslash in front of the double quote.

test5.htmx
This is test5. %[*set,&link_ref,="<a href=\"",url,="\">",anchor,="</a>"]% The value of "link_ref" is %[link_ref]%.
Shell Command
expandfile test5.htmx
Shell Output
This is test5. The value of "link_ref" is <a href="https://multicians.org/index.html">Multics</a>.

The *set builtin above is invoked with six arguments:

  1. The variable to be set: link_ref
  2. A constant string, <a href="
  3. A variable, url, whose value is https://multicians.org/index.html
  4. A constant string, ">
  5. A variable, anchor, whose value is Multics
  6. A constant string, </a>

The *set builtin concatenates the values of the last five arguments and stores the result in link_ref.

Example 6: variables that were never set

Expanding a variable that was never set expands to an empty value.

test6.htmx
This is test6. The value of "fred" is '%[fred]%'.
Shell Command
expandfile test6.htmx
Shell Output
This is test6. The value of "fred" is ''.

Notice that the single and double quote characters are not special.. they are just copied. (Inside an argument list, double quoted strings are special: they are replaced by their content. Single quotes in an argument list are not special.)

Example 7: each run of expandfile has its own list of variable values

Each invocation of expandfile has its own list of variables and values.

test7a.htmx
This is test7a. %[*set,&fred,=3]% The value of "fred" is '%[fred]%'.
test7b.htmx
This is test7b. The value of "fred" is '%[fred]%'.
Shell Command
expandfile test7a.htmx expandfile test7b.htmx
Shell Output
This is test7a. The value of "fred" is '3'. This is test7b. The value of "fred" is ''.

Example 8: setting variables with command line arguments to expandfile

You can set variables set in the command line invocation of expandfile with arguments of the form varname=value.

test8.htmx
This is test8. The value of "fred" is %[fred]%.
Shell Command
expandfile fred=777 test8.htmx
Shell Output
This is test8. The value of "fred" is 777.

Example 9: getting variable values from the command environment

You can set variables in the shell command environment with the Unix export command. If expandfile does not find a value for a variable in its list of values, it will search the command processor's environment variables.

test9.htmx
This is test9. The value of "fred" is %[fred]%. %[*set,&fred,=42]% The value of "fred" is %[fred]%.
Shell Command
export fred=888 expandfile test9.htmx
Shell Output
This is test9. The value of "fred" is 888. The value of "fred" is 42.

Example 10: setting an expandfile value does not affect the command environment

Setting a variable in expandfile's list of values does not affect the command processor's environment variables.

test10.htmx
This is test10. %[*set,&fred,=888]% expandfile set "fred" to %[fred]%.
Shell Command
echo shell says fred is $fred expandfile test10.htmx echo shell says fred is $fred
Shell Output
shell says "fred" is This is test10. expandfile set "fred" to 888. shell says "fred" is

Example 11: setting variables in one input file and using them in another file

expandfile will expand each input file named in its arguments in order. Values set by one file can be accessed in the expansion of subsequent files in the same invocation of expandfile. (For web site building, I often create a file config.htmi with settings for the whole site, and specify the file as the first argument to every expandfile invocation.)

settings11.htmi
%[*set,&fred,=6180]%
test11.htmx
This is test11. The value of "fred" is %[fred]%.
Shell Command
expandfile settings11.htmi test11.htmx
Shell Output
This is test11. The value of "fred" is 6180.

Example 12: multiple value arguments to *set, *shell, *fwrite, *fappend, and *htmlescape

*set, *shell, *fwrite, *fappend, and *htmlescape concatenate all their value arguments (with no separator).

test12.htmx
This is test12. %[*set,&nancy,=4]% %[*set,&fred,=3,=" xxx ",nancy]% The value of "fred" is %[fred]%.
Shell Command
expandfile test12.htmx
Shell Output
This is test12. The value of "fred" is 3 xxx 4.

Example 13: comments

Comments are enclosed inside %[**....]%. Comments expand to nothing.

test13.htmx
This is test13. %[** this is a comment]% This is plain text.
Shell Command
expandfile test13.htmx
Shell Output
This is test13. This is plain text.

In the example output, why wasn't there a blank line? The comment expands to nothing, and is followed by a newline character. There is a special rule for lines with only expansions: if a %[...]% construct is the only thing on a line, the ending newline is not output. This rule applies to any expansion, not just comments.

Example 14: quote marks

Single and double quote marks not inside %[...]% are just regular characters.

test14.htmx
This is test14. %[*set,&fred,=3]% The value of "fred" is '%[fred]%'.
Shell Command
expandfile test14.htmx
Shell Output
This is test14. The value of "fred" is '3'.

Example 15: quote marks inside expansions

Double quote marks inside %[...]% cause the quoted characters to be un-special. Single quotes don't.

test15.htmx
This is test15. %[*set,&fred,="%["]% The value of "fred" is '%[fred]%'.
Shell Command
expandfile test15.htmx
Shell Output
This is test15. The value of "fred" is '%['.

Example 16: backslashes inside expansions

Inside %[...]%, a backslash makes a quote mark un-special. (Why would you need to do this? One common case is building up HTML strings that need to contain quote marks, like href="fred.html".)

test16.htmx
This is test16. %[*set,&fred,="\""]% The value of "fred" is '%[fred]%'.
Shell Command
expandfile test16.htmx
Shell Output
This is test16. The value of "fred" is '"'.

Example 17: *include reads in a file

%[*include,=filename]% reads in the entire contents of filename and expands its contents, replacing variable references with their values, and executing built-in functions.

test17.htmx
This is test17. %[*set,&fred,="set-in-outer"]% %[*include,=myinclude.htmi]% end of test17.htmx
myinclude.htmi
Hello from myinclude.htmi The value of "fred" is %[fred]%. end of myinclude.htmi
Shell Command
expandfile test17.htmx
Shell Output
This is test17. Hello from myinclude.htmi The value of "fred" is set-in-outer. end of myinclude.htmi end of test17.htmx

If you want to *include a file but not expand its contents, use the *includeraw builtin.

Example 18: *block saves text for later expansion

%[*block,&blockname,regexp]% starts saving text into a variable without expanding it, until a line matches regular expression "regexp." No quote or backslash processing or %[...]% expansion is done while saving the text. The contents of the variable can be expanded later, and variable references in the text will be evaluated at expansion time. When a block is expanded, it can produce output and set variables' values.

As we shall see, blocks are used as iterators with the *..loop builtins, and as macros invoked by *callv. The other major use of blocks is with expandfile wrappers .

test18.htmx
This is test18. %[*block,&body,^END]% Body says the value of "x" is "%[x]%". END The block "body" is defined. %[*set,&x,=swordfish]% %[*expand,body]% %[*set,&x,=hippopotamus]% %[*expand,body]%
Shell Command
expandfile test18.htmx
Shell Output
This is test18. The block "body" is defined. Body says the value of "x" is "swordfish". Body says the value of "x" is "hippopotamus".

Builtin Functions

Above we have seen the *set builtin function. It is one of 37 builtin functions. There is a summary table in the expandfile documentation. Here are examples and discussion of some of the builtins.

Set

Example 19: Using variable names in *set

%[*set,&varname,value]% sets the value of variable "varname" to a value, either a literal or a variable's value. If you forget the & you get a warning, but the program still executes.

test19.htmx
This is test19. %[*set,x,=1234]% %[*set,&y,x]% The value of "x" is "%[x]%". The value of "y" is "%[y]%".
Shell Command
expandfile test19.htmx
Shell Output
expandfile: test19.htmx 'x' should begin with & in *set This is test19. The value of "x" is "1234". The value of "y" is "1234".

Expand and Expandv

Example 20: *expand and *expandv

%[*expand,varname]% takes the text in varname, expands it, and outputs the result. Variables and builtin functions in varname are expanded. If builtin functions in varname set variables' values, the changes will persist after expansion. *expandv is like *expand, but stores its result in a variable instead of writing it to the output.

test20.htmx
This is test20. .. setting x and argument %[*set,x,="Hello %[argument]%"]% %[*set,argument,="world"]% The value of "x" is "%[x]%". The value of "argument" is "%[argument]%". .. expanding x %[*expand,x]% .. expanding x into y %[*expandv,&y,x]% The value of "y" is "%[y]%".
Shell Command
expandfile test20.htmx
Shell Output
This is test20. .. setting x and argument The value of "x" is "Hello %[argument]%". The value of "argument" is "world" .. expanding x Hello world .. expanding x into y The value of "y" is "Hello world"

Conditional Execution

Example 21: *if

%[*if,relop,value1,value2,restofline]% expands restofline if value1 relop value2.
For instance, when relational operator relop is gt then it checks if value1 > value2.
restofline can be any builtin function invocation, including another *if.
The 14 relational operators are listed in the expandfile documentation for *if.

test21.htmx
This is test21. %[*if,gt,=3,=2,*expand,=hi there]% %[*if,lt,=3,=2,*expand,=lo there]%
Shell Command
expandfile test21.htmx
Shell Output
This is test21. hi there

External Commands

Example 22: *shell

%[*shell,&result,commandline...]% sends a command line to the OS shell and captures its output in result. All the arguments to *shell after the &result parameter are concatenated with no separator to make the command line. The command line is executed in the shell environment: if it outputs characters to STDOUT, they are stored in result. Newline characters in result are changed to spaces (actually to the current value of _xf_ssvsep, see below).

A number of useful helper commands are provided with expandfile, such as fmtnum and nargs. See the list of expandfile utility functions.. In addition, standard Unix commands such as sed and ls can be invoked with parameters supplied by expandfile.

test22.htmx
This is test22. %[*set,&x,=1234567]% The value of "x" is "%[x]%". %[*shell,&xfmt,="fmtnum ",x,=" num"]% The value of "xfmt" is "%[xfmt]%".
Shell Command
expandfile test22.htmx
Shell Output
This is test22. The value of "x" is "1234567". The value of "xfmt" is "1,234,567".

SSVs (Space Separated Values)

The command interpreter on Tandem systems used lists of Space Separated Values extensively. They were called SSVs. expandfile generalizes SSVs to allow HTMX programs to use some other separator character, but I still call them SSVs.

expandfile can handle values that are lists of items; For example, "a b c d" is a four-element SSV. If I change "_xf_ssvsep" to "|" then "e|f|g|h" is also a four-element SSV.

Example 23: Constructing SSVs and *popssv

Create an SSV by simple assignment. Add elements to the right end using ordinary concatenation. Pop an element off the left end using *popssv, and rewrite the SSV.

test23.htmx
This is test23. .. Setting up the SSV %[*set,&x,="a b c d"]% The value of "x" is "%[x]%". .. Adding a separator and an element to the SSV %[*if,ne,x,="",*concat,&x,_xf_ssvsep]% %[*concat,&x,="e"]% The value of "x" is "%[x]%". .. Popping an element off the SSV %[*popssv,&y,&x]% The value of "y" is "%[y]%". The value of "x" is "%[x]%".
Shell Command
expandfile test23.htmx
Shell Output
This is test23. .. Setting up the SSV The value of "x" is "a b c d". .. Adding a separator and an element to the SSV The value of "x" is "a b c d e". .. Popping an element off the SSV The value of "y" is "a". The value of "x" is "b c d e".

Example 24: Looping over an SSV with *ssvloop

An interesting use of SSVs is to expand a template variable for each element in an SSV. *ssvloop uses a copy of the SSV, so the SSV is unchanged by the loop. Each time the template is expanded, the left-most element of the SSV is popped off and _xf_ssvitem is set to it, and *ssvloop expands the iterator template and appends the expansion to the output. The iterator block can in turn expand variables and builtin functions. If the SSV is empty, nothing happens. (This example shows how to change the "space" to another SSV delimiter, and how to set up the iterator variable using a *block. It also shows the good practice of saving and restoring _xf_ssvsep.)

test24.htmx
This is test24. %[*block,&iterator,^END]% iterator: the value of "_xf_ssvitem" is "%[_xf_ssvitem]%" END .. Setting up the SSV %[*set,&oldssvsep,_xf_ssvsep]% %[*set,&_xf_ssvsep,="|"]% %[*set,x,="a|b|c|d"]% The value of "x" is "%[x]%". .. looping %[*ssvloop,&y,iterator,x]% .. done The value of "x" is "%[x]%". The value of "y" is %[y]% %[*set,&_xf_ssvsep,oldssvsep]%
Shell Command
expandfile test24.htmx
Shell Output
This is test24. .. Setting up the SSV The value of "x" is "a|b|c|d". .. looping .. done The value of "x" is "a|b|c|d". The value of "y" is iterator: the value of "_xf_ssvitem" is "a" iterator: the value of "_xf_ssvitem" is "b" iterator: the value of "_xf_ssvitem" is "c" iterator: the value of "_xf_ssvitem" is "d"

Example 25: Looping over a MySQL query with *sqlloop

If you have data in a MySQL database, you can query it and generate formatted output with *sqlloop. As each row is returned, values from the row will be bound to names like "table.name". For values that have no table name, like "COUNT(*) AS xyz", the name will be ".xyz". *sqlloop also binds _xf_nrows to the count of rows returned, and _xf_colnames to an SSV listing the column names in the query.

The variables hostname, database, username, and password must be set up to point to the database server. I set them in a file called config.htmi that I expand before I expand the template.

test25.sql
-- Example of a table for sqlloop DROP TABLE IF EXISTS testtable; CREATE TABLE testtable ( ordinal FLOAT, -- order of items if more than one selected anchor VARCHAR(255), -- text anchor target VARCHAR(255), -- link target desc VARCHAR(255) -- TITLE attribute ); INSERT INTO testtable VALUES (1.0,'apple','applepie.html','apple pie recipe'), (2.0,'cherry','cherrypie.html','cherry pie recipe'), (2.5,'peach','peachpie.html','peach pie recipe'), (3.0,'pecan','pecanpie.html','pecan pie recipe');
test25.htmx
This is test25. %[*block,&iterator,^END]% <a href="%[testtable.target]%" title="%[testtable.desc]%">%[testtable.anchor]%</a> END %[*set,hostname,=localhost]% %[*set,database,=mydata]% %[*set,username,=george]% %[*set,password,=omyokesh]% .. looping %[*sqlloop,&y,iterator,="SELECT * FROM testtable ORDER BY ordinal"]% .. done The value of "_xf_nrows" is "%[_xf_nrows]%". The value of "_xf_colnames" is "%[_xf_colnames]%". The value of "y" is %[y]%
Shell Command
mysql --execute "source test25.sql" expandfile config.htmi test25.htmx
Shell Output
This is test25. .. looping .. done The value of "_xf_nrows" is "4". The value of "_xf_colnames" is "ordinal anchor target desc". The value of "y" is <a href="applepie.html" title="apple pie recipe">apple</a> <a href="cherrypie.html" title="cherry pie recipe">cherry</a> <a href="peachpie.html" title="peach pie recipe">peach</a> <a href="pecanpie.html" title="pecan pie recipe">pecan</a>

The multicians.org website has multiple pages generated by *sqlloop from SQL tables. This is especially valuable when the same information is displayed in multiple formats: for example, the HTML sitemap and the Google XML sitemap are generated from the same table; the changes table produces an update history, an RSS feed, and a recent updates panel for the main page; the bibliography database table generates seven HTML files; and the list of contributors is annotated with counts (from the bibliography table) of the documents they produced.

I also built a complex application, swt, that generates daily Web usage reports in HTML from web server logs. It makes extensive use of *sqlloop. Web server log input data is transformed into MySQL input, and then swt produces over 30 report sections by processing the data with *sqlloop, according to configuration and options also stored in SQL tables. Each report section is created by expanding a template file with expandfile. Each section obtains the queries to perform from the configuration: these queries often use multiple inner, outer, and self joins. Most report sections generate bar graphs in HTML illuminating some view of the data. swt has about 300 different queries in its basic configuration; users may tailor and extend the reports with their own queries and templates.

Example 26: Looping over a CSV file with *csvloop

Spreadsheet programs and some Web interfaces export data in a Comma Separated Values file (CSV). (RFC-4180 defines the format of CSV files.) expandfile can process CSV files. The input file may be gzipped. The first row of the CSV file should be a list of column names. *csvloop reads a CSV file and expands an iterator block for each row. As each row is returned, values from the row will be bound to column names from the first row. *csvloop also binds _xf_nrows to the count of rows returned, and _xf_colnames to an SSV listing the column names in the query.

test26.csv
anchor,target,desc apple,applepie.html,apple pie recipe cherry,cherrypie.html,cherry pie recipe peach,peachpie.html,peach pie recipe pecan,pecanpie.html,pecan pie recipe
test26.htmx
This is test26. %[*block,&iterator,^END]% <a href="%[target]%" title="%[desc]%">%[anchor]%</a> END .. looping %[*csvloop,&y,iterator,=test26.csv]% .. done The value of "_xf_nrows" is "%[_xf_nrows]%". The value of "_xf_colnames" is "%[_xf_colnames]%". The value of "y" is %[y]%
Shell Command
expandfile test26.htmx
Shell Output
This is test26. .. looping .. done The value of "_xf_nrows" is "4". The value of "_xf_colnames" is "anchor target description". The value of "y" is <a href="applepie.html" title="apple pie recipe">apple</a> <a href="cherrypie.html" title="cherry pie recipe">cherry</a> <a href="peachpie.html" title="peach pie recipe">peach</a> <a href="pecanpie.html" title="pecan pie recipe">pecan</a>

I have used *csvloop to convert CSV data exported from an online Web store application to a different format CSV file for a cash register application. This required translating fields from one encoding to another, and combining multiple records from the online store format into a single record with several variant fields for the cash register.

Example 27: Looping over an XML file with *xmlloop

expandfile can also process XML files that contain a sequence of similar items. (The XML file may be gzipped.) *xmlloop reads such a file and expands an iterator block for each item matching an XPATH. (The default XPATH is "/*/*".) For each item found by the XPATH, the loop binds the values of sub-items "./*" and binds the values of attributes "./@*". *xmlloop also binds _xf_nrows to the count of rows returned, and _xf_colnames to an SSV listing the item and attribute names found.

*xmlloop actually reads the file twice, once to build up the list of variables to bind, and again to set them. Fields that are not present in a particular item are set to the empty string, so they aren't copied from the previous item.

XML files can have very complex structure: *xmlloop doesn't handle every possible XML schema.

Some Web application interfaces export their data in an XML file containing an array of similar items. *xmlloop can loop over the array, extracting and processing characteristics of the items. I have used *xmlloop to generate reports from APIs for trouble tickets and mobile device management.

test27.xml
<?xml version='1.0'?> <!--test file for XML --> <list> <item type="recipelink" sequence="1"> <anchor>apple</anchor> <target>applepie.html</target> <desc>apple pie recipe</desc> </item> <item type="recipelink" sequence="2"> <anchor>cherry</anchor> <target>cherrypie.html</target> <desc>cherry pie recipe</desc> </item> <item type="recipelink" sequence="3"> <anchor>peach</anchor> <target>peachpie.html</target> <desc>peach pie recipe</desc> </item> <item type="recipelink" sequence="4"> <anchor>pecan</anchor> <target>pecanpie.html</target> <desc>pecan pie recipe</desc> </item> </list>
test27.htmx
This is test27. %[*block,&iterator,^END]% <a href="%[target]%" title="%[desc]%">%[anchor]%</a> END .. looping %[*xmlloop,&y,iterator,=test27.xml]% .. done The value of "_xf_nrows" is "%[_xf_nrows]%". The value of "_xf_colnames" is "%[_xf_colnames]%". The value of "y" is %[y]%
Shell Command
expandfile test27.htmx
Shell Output
This is test27. .. looping .. done The value of "_xf_nrows" is "4". The value of "_xf_colnames" is "sequence anchor target description". The value of "y" is <a href="applepie.html" title="apple pie recipe">apple</a> <a href="cherrypie.html" title="cherry pie recipe">cherry</a> <a href="peachpie.html" title="peach pie recipe">peach</a> <a href="pecanpie.html" title="pecan pie recipe">pecan</a>

(Currently there is no "*JSONloop" builtin. Some Web APIs return information in JSON format. For simple enough JSON, one can map it into XML. I have successfully used a simple Perl program called json2xml on JSON data I fetched from a web API for a training management system: I translated the data to XML, processed it with *xmlloop, and generated HTML reports.)

Example 28: Looping over a file system directory with *dirloop

*dirloop looks at a file directory and lists its contents. For each file, stat() is called and variables are set with the file's attributes, and the iterator is expanded. *dirloop also binds _xf_nrows to the count of files found.

test28.htmx
This is test28. %[*block,&iterator,^END]% <tr><td>%[file_name]%</td><td>%[file_datemod]%</td></tr> END .. looping %[*dirloop,&y,iterator,="/tmp/",="*"]% .. done The value of "x" is "%[x]%". The value of "y" is <table> %[y]% </table>
Shell Command
expandfile test28.htmx
Shell Output
This is test28. .. looping .. done The value of "_xf_nrows" is "2". The value of "y" is <table> <tr><td>alpha</td><td>03/31/17 14:51</td></tr> <tr><td>beta</td><td>02/23/05 10:26</td></tr> </table>

Macros

Example 29: Defining macros, and invoking them with with *callv

You can define HTMX macros using a *block, and invoke them with *callv. Macros can be called with any number of arguments. Macro invocations can either write output, set variables, or both.

test29.htmx
This is test29. %[*block,&mymacro,^END]% %[** this is macro 'mymacro' which outputs the sum of its two arguments **]% %[*set,&_xxx,param1]% %[*increment,&_xxx,param2]% %[_xxx]% END .. calling the macro %[*callv,mymacro,=2,=5]% .. done
Shell Command
expandfile test29.htmx
Shell Output
This is test29. .. calling the macro 7 .. done

Example 30: The title macro in this document

Each example section in this document begins with a macro that increments the example number, and outputs an H3 block with specified text.

test30.htmx
%[*block,&extitle,^END]% %[** callv,extitle,text **]% %[*increment,&exampleno,=1]% <h3 id="tag%[exampleno]%">Example %[exampleno]%: %[param1]%</h3> END

See Macros in Expandfile for more examples and discussion about macros.

MORE EXAMPLES TO ADD