image image
Stanford University CourseWork

Dynamic Template Language
back to index


  1. Introduction
  2. Variable and Object Substitution
  3. Conditionals: IF, SWITCH
  4. Looping: FOREACH
  5. Inline Variable Definition: VARDEF, RECDEF, ARRDEF
  6. Reusable sub-templates: EVALDEF, EVAL
  7. Including other files
  8. Scope and Variables
  9. Built-in Variables
  10. Date Formatting


DTL is designed to simplify the development of Java-based "servlets," or server-side applications.  DTL separates the computation and database access of the Java servlet from the actual presentation of information.  This allows developers to modify many aspects of the interface to a servlet-based application without modifying the actual code.  It also allows developers to reuse servlets for multiple similar applications which may require different appearances, simply by using a different set of template files.

DTL tags begin and end with two square brackets ("[[" and "]]").  Some DTL tags are "stand-alone"; others require begin and end tags; the end tag usually has the same name as the begin tag with a slash ("/") in front of it; for example:

[[VARDEF $FOO]]This is an example of DTL Tagging.[[/VARDEF]]

(New in 2.0!) If multiple DTL tags are placed one after the other, the begin/end tags may be omitted between the tags, or may be placed on a new line, with or without whitespace. Thus,

[[VARDEF $FOO]][[IF DEFINED $BAR]]Hello there![[ELSE]]Hi there![[/IF]][[/VARDEF]]

gives an identical result to

[[VARDEF $FOO IF DEFINED $BAR]]Hello there![[ELSE]]Hi there![[/IF /VARDEF]]


  IF DEFINED $BAR]]Hello there![[
  ELSE]]Hi there![[
In the last example, the whitespace (new lines and indentations) between various DTL tags is ignored, allowing developers to write more readable DTL code without displaying additional whitespace to the user.

Variable and Object Substitution

In DTL, a variable is an identifier which corresponds to a value. An object is an identifier which corresponds to a set of values, called "fields." Both variables and objects are generally defined by the Java servlet; however, variables can also be defined "inline" in the file (see Inline Variable Definition below). There is also a third type of identifier, called an "array", which will be discussed below in Looping: FOREACH.

Usually, a variable is defined with an integer or string value, although any Java data type is legal. Variables with date values can be manipulated specially; see Date Formatting below.

To substitute a variable in DTL, include the variable name with a "$" in front of it, inside a DTL tag, for example [[$VARNAME]]. For example, a servlet might define a variable called "VOLUME" with the integer value 270. If the DTL template includes the following:

<P>This article can be found in volume [[$VOLUME]]

The output file will show

<P>This article can be found in volume 270

An object may contain multiple fields, and can also contain objects which themselves contain additional fields or objects. To substitute an object field, use the same syntax as for single variables, adding the field name with a "." after the object name, for example [[$OBJECT.FIELD]]. For example, a servlet might define an object called "ARTICLE" which contains a field called "FIRSTPAGE" which is set to the value 1498. The DTL code

<P>This article begins on page [[$ARTICLE.FIRSTPAGE]]

would give the output

<P>This article begins on page 1498

If the object called "ARTICLE" contains another object called "AUTHOR" which contains a field called "LNAME" which is set to "Smith," the DTL code

<P>This article is by somebody named [[$ARTICLE.AUTHOR.LNAME]]

will produce the output

<P>This article is by somebody named Smith

In DTL, object fields are interchangeable with variables; for the rest of this document, the term "variable" will be used to mean both.

Finding out what variables you have to work with

When something does not show up the way you think it should, it is not always obvious whether your template is wrong or the data is being loaded incorrectly. To see what variables are defined in the current environment, you can either use the [[!ALL]] tag described below, or add "&debug=adam_says_debug" to the URL. (Use ? instead of & if it is the first query parameter: vs You will get a page that looks like this:
    • SUBPAGE=tw
    • VOLUME=2000
    • TITLE_ENC=Moved+in+and+at+Work
So, if your template said [[$RESOURCE.VOL]], you now know it should have been [[$RESOURCE.VOLUME]]

 If you are comfortable reading DTL syntax, or just want to save the current variables for using later in an off-line script, you can use 'debug=vardef_style_debug' instead, which outputs the variables in DTL [[VARDEF $VARNAME]]... syntax instead. (this is the same as the [[!ALLVARDEFS]] command.


If one needs to display multiple fields from an object, it can be rather tedious to type "[[$ARTICLE.FIRSTPAGE]] [[$ARTICLE.LASTPAGE]] [[$ARTICLE.VOLUME]]." To simplify this, one can type



If one needs to have a URL-encoded version of a particular variable, it can be tedious to make the code change necessary to enable it. To fix this, one can now have


This will URL-encode the variable $ARTICLE.TITLE, allowing us to pass this value seamlessly through URLs, even if it contains spaces or other URL special characters.


Similarly to URLENCODE,


will fix the variable to change quotes and angle brackets into entities so that it can be displayed in a form field. This should be used for ALL form text fields where text like this might be displayed, including hidden fields.

Conditionals: IF, SWITCH

Frequently, one needs to display different text depending on the value or existence of a variable. In DTL, this is done with the IF and SWITCH directives.

The IF directive can be used to test the existence of, or the value of, a given variable. To test the existence of a variable, use the syntax

[[IF (NOT) DEFINED $VARNAME]]Text if true([[ELSE]]Text if false)[[/IF]]

Text between parentheses is optional. For example, the servlet might define a variable called "ERROR" if there was an error in processing the user's request. To display an error message if this variable is defined, the DTL code

[[IF DEFINED $ERROR]]There was an error![[ELSE]]All is well.[[/IF]]

will produce, if ERROR is defined to any value,

There was an error!

and otherwise,

All is well.

If the [[ELSE]] clause is omitted, nothing will appear if the variable is not defined. The truth value of the IF directive may be negated by using the "NOT" operator. For example,

[[IF NOT DEFINED $ERROR]]All is well.[[/IF]]

will produce the text "All is well." if the variable "ERROR" is not defined.

[New, 7/10/98] Multiple variables can be tested by using "ORDEF" between multiple values. For example,

will produce the text "There is an error!" if any of $ERRORONE, $ERRORTWO, or $ERRORTHREE are defined.

IF statements can also test the value of a variable, using the EQUAL operator. For example, if the ERROR variable were defined with the value "Serious Error", the DTL code

[[IF EQUAL $ERROR "Serious Error"]]There was a serious error![[/IF]]

will produce the text "There was a serious error!". If ERROR is defined to any other value, or is not defined, this text will not appear. The [[ELSE]] directive can be used here as well, as can the NOT operator.

(New in 2.1!) IF EQUAL can now be used to test if a variable has one of a set of values. For example, the DTL code

[[IF EQUAL $JOURNALCODE "jbc" "jcb" "jem"]]This is one of these journals[[/IF]]

will produce the text "This is one of these journals" if JOURNALCODE is defined as any of "jbc," "jcb," or "jem."

IF EQUAL can also be used to test whether two variables have identical values. For example, the code


will produce the text "2789" if both ARTICLE.FIRSTPAGE and ARTICLE.LASTPAGE are defined to the value 2789, but will produce the text "2789-2991" if ARTICLE.LASTPAGE is set to 2991. However, only one variable may be used -- [[IF EQUAL $ARTICLE.FIRSTPAGE $ARTICLE.LASTPAGE $OTHERARTICLE.LASTPAGE]] will not work.

(New in 2.1) Using the MULTIPLE operator, IF statements can now check whether multiple values are defined for an array. For example, the code

This article has [[IF NOT MULTIPLE $ARTICLE.AUTHOR]]an[[/IF]] author[[IF MULTIPLE $ARTICLE.AUTHOR]]s[[/IF]].

produces the text "This article has an author" if there are one or fewer authors (IF DEFINED will test whether there are any authors in the first place), and "This article has authors" if there are two or more authors.

(New in 2.11) DTL also has several comparator operators: GREATER (greater than), LESS (less than), GEQ (greater than or equal to), and LEQ (less than or equal to). These operators also require the user to specify the type of variable which is expected: DATETYPE for dates, INTTYPE for integers, and STRINGTYPE for strings. (Otherwise two dates or integers may be compared as strings, which would produce unexpected results.) Dates must be specified as (MM/DD/YYYY). For example, the code

This article was published [[IF GEQ DATETYPE $.DATE "05/08/1998"]]on or after May 8, 1998[[ELSE]]before May 8, 1998.[[/IF]]

produces "This article was published on or after May 8, 1998" if the date object $.DATE is greater than or equal to 5/8/98. Similarly,

This article has [[IF GREATER INTTYPE $AUTHOR.!COUNT "2"]]more than 2[[ELSE]]2 or fewer[[/IF]] authors

produces "This article has more than 2 authors" if the number of authors is greater than 2. Finally,

This article's title is sorted [[IF LESS STRINGTYPE $.TITLE "The"]]before[[ELSE]]after[[/IF]] an article starting with "The"

produces "before" if the title sorts lexographically before the word "The".

If a variable may have multiple values which should have different outputs, one may wish to use the SWITCH directive. SWITCH has the following syntax:

[[SWITCH $VARNAME]][[CASE "value 1"]]Text if value 1[[/CASE]][[CASE "value 2"]]Text if value 2[[/CASE]][[DEFAULT]]Text if neither applies[[/DEFAULT]][[/SWITCH]].

For example, say the variable "SUBSCRIBER.TYPE" may have the values "BASIC", "FREEREG", or "COMPLIMENTARY", or no value.

[[CASE "BASIC"]]Basic subscriber[[/CASE]]
[[CASE "FREEREG"]]Free registrant[[/CASE]]
[[CASE "COMPLIMENTARY"]]Complimentary subscriber[[/CASE]]
[[DEFAULT]]No subscription[[/DEFAULT]]

(Important: New in 2.1) Unlike IF EQUAL, the quoted string value may not be replaced with a variable. As with IF EQUAL, the quoted string value may be replaced with a variable.

(New in 2.1) If a CASE is relevant for more than one value of the variable, CASE can now take multiple values. For example,

[[CASE "BASIC"]]You paid money for this![[/CASE]]
[[CASE "FREEREG" "COMPLIMENTARY"]]You got this for free![[/CASE]]
[[DEFAULT]]No subscription[[/DEFAULT]]

will produce the text "You got this for free" for values of either "FREEREG" or "COMPLIMENTARY."

Looping: FOREACH

An array is a special type of object which actually corresponds to a list of objects. An array object has no user-defined fields which can be accessed (although it does have one built-in field, see Built-in Variables). To access it, one uses the FOREACH directive. FOREACH has the following syntax:

[[FOREACH $ARRAY]] Some text, [[$.FIELD]], [[$.ANOTHERFIELD]] [[/FOREACH]]

Fields of the "current" object in the loop may be accessed as "$.FIELD". For example, if a list of objects were defined with the name "ISSUE", and the first issue had fields VOLUME set to 270, NUMBER set to 1, the second VOLUME 270, NUMBER 2, and the third VOLUME 270, NUMBER 3, the following DTL code

[[FOREACH $ISSUE]]<P>Volume [[$.VOLUME]], Number [[$.NUMBER]][[/FOREACH]]

would generate the output

<P>Volume 270, Number 1
<P>Volume 270, Number 2
<P>Volume 270, Number 3

Four built-in variables are defined only within a loop. Built-in variables are accessed with a "!" rather than a "$", and are discussed further below. !TOTALCOUNT is the total number of items in the array. !CURRCOUNT is the current number within the array, starting from 1. !ISFIRST is defined only if the current item is the first item in the array. !ISLAST is defined only if the current item is the last item in the array. This can be useful in an example such as the following:


which might generate the output

Adam Elman and Edward M. Beaux


Adam Elman, Ethan Straffin, and Edward M. Beaux


Adam Elman

depending on the number of authors defined in the array.

Finally, there is another IF which can be accessed only within a loop. IF EVERY evaluates to true every N item. So for example, if an array ITEM were defined with 9 items (objects with field "ITEM" with "Item n", the following code:

[[FOREACH $ITEM]][[$.ITEM]], [[IF EVERY "3"]] --- [[/IF]][[/FOREACH]]

would produce:

Item 1, Item 2, Item 3, --- Item 4, Item 5, Item 6, --- Item 7, Item 8, Item 9, ---

Inline Variable Definition: VARDEF

At times, one may want to define a new variable within a template which can then be substituted elsewhere in the template. This is done with the VARDEF directive. VARDEF uses this syntax:

[[VARDEF $VARNAME]]Value of the variable[[/VARDEF]]

The value can include variable substitutions, IF, SWITCH, FOREACH, and EVAL constructs. Once a variable is defined with VARDEF, it can be used anywhere afterwards in the template. The VARDEF construct itself generates no output.

Normally, variables used within the definition of a VARDEF are not evaluated until the variable is used; for example, if a variable is defined such that

[[VARDEF $VARNAME]]This is a substitution of [[$MYVAR]][[/VARDEF]]

if $MYVAR is defined later as "foo", then the output of [[$VARNAME]]would read

This is a substitution of foo

To override this behavior, use STATICVARDEF. For instance, if the above was defined instead as

[[STATICVARDEF $VARNAME]]This is a substitution of [[$MYVAR]][[/STATICVARDEF]]

if $MYVAR was not defined when this was referenced, even if it was later defined, [[$VARNAME]] would produce:

This is a substitution of

Inline Record Definition: RECDEF and ARRDEF

At times, one may want to define a new record with its own variables within a template which can then be substituted elsewhere in the template. This is done with the RECDEF directive. RECDEF uses this syntax:

[[VARDEF $VARNAME1]]Value of the variable[[/VARDEF]]
[[VARDEF $VARNAME2]]Value of the variable[[/VARDEF]]

In this example, the fields could later be accessed as [[$RECNAME.VARNAME1]] and [[$RECNAME.VARNAME2]].

RECDEFS can be nested, so for example

[[VARDEF $VARNAME1]]Value of the variable[[/VARDEF]]
[[VARDEF $VARNAME2]]Value of the variable[[/VARDEF]]
[[VARDEF $CHILDVAR1]]Value of the child[[/VARDEF]]
[[VARDEF $CHILDVAR2]]Value of the child[[/VARDEF]]

would produce a record where fields could later be accessed as [[$RECNAME.CHILDREC.CHILDVAR1]], etc.

Similarly to RECDEF, one can define a series of records which can be accessed as an array using ARRDEF.

[[VARDEF $VARNAME1]]1st Value of the variable[[/VARDEF]]
[[VARDEF $VARNAME2]]1st Value of the variable[[/VARDEF]]
[[VARDEF $VARNAME1]]2nd Value of the variable[[/VARDEF]]
[[VARDEF $VARNAME2]]2nd Value of the variable[[/VARDEF]]

In this example, an array could be created which could be accessed as:


which would appear as

1st Value of the variable, 2nd Value of the variable,

ARRDEFS can be nested within RECDEFS and vice versa.

Reusable sub-templates: EVALDEF, EVAL

At times, one may want to define a "sub-template" which can be used multiple times within a template, but which is evaluated for a different object each time. For example, one may wish to define a standard format for the display of dates, or citations, which can then be used in multiple locations in the template. To do this, one uses the EVALDEF and EVAL directives.

To define a "sub-template," use the EVALDEF directive, which has the following syntax:

[[EVALDEF $TMPLNAME]]Some text, [[$.FIELD]], other text[[/EVALDEF]]

EVALDEF "sub-templates" can only be called with a single object; fields of that object can be referenced with the "$." syntax as in FOREACH.

To use one of these sub-templates, use the EVAL directive, which has the following syntax:


This will call the TMPLNAME sub-template with the OBJNAME object. In the EVALDEF example above, if OBJNAME has a field called FIELD with value "a field", this would generate the following output:

Some text, a field, other text

From within a FOREACH loop, the "WITH $OBJNAME" can be omitted, and the EVAL will use the current array object.

Including Other Files

External files or URLs can be included in a template. DTL allows four types of inclusions:

[[INCLUDE "/path/to/the/file"]]

inserts the text of a file directly into the output of the template without any further processing.

[[INCLUDEDTL "/path/to/the/file"]]

inserts the text of a file into the output of the template, evaluating it as DTL with the same set of variables as in the main file. The included file must be a legal DTL file as a standalone.

[[INCLUDESLP "/path/to/the/file"]]

inserts the text of a file into the output of the template. It evaluates the file as an "SLP" file. SLP is a template language similar to DTL (in fact, DTL was inspired by it) which uses more SGML-like syntax. DTL does not support most SLP features with INCLUDESLP; it only does variable substitution.

[[INCLUDEHTMLASC "/path/to/the/file"]]

inserts the text of a file into the output of the template. It translates from HTML into approximate ASCII; currently, it simply replaces <P> tags with double line breaks, <IMG> tags with the appropriate ALT text, and <A HREF> tags with footnoted URLs if the URL is fully qualified. This does _not_ do word wrapping; word wrapping should be done with org.highwire.util.WordWrapWriter. This feature is useful for generating email alerts which include HTML or HTSLP content.

(New!) You can now specify an additional directive to the [[INCLUDEHTMLASC ...]] DTL syntax that will load a configuration file with additional information about how to parse the included HTML or HTSLP file. The syntax is: [[INCLUDEHTMLASC CONF "<path_to_conf_file>" "<path_to_HTML_file_to_include>"]] both of the filenames can be DTL variables instead of strings. The old syntax for INCLUDEHTMLASC still works, too.

The config file can specify the following lists and key/value pairs: (The values listed below are just examples, of course)

omitTagSets=( "H1" "STRONG" ) # This will omit the named
                              # tags, and all text
                              # occurring between the
                              # open and close tags
omitTag=( "IMG" "BR" "HR" )   # This will eliminate the
                              # named tags and any
                              # parameters inside those
                              # tags. For example
                              # <HR WIDTH="150">
                              # would be removed entirely

strip_extra_whitespace=true   # This will strip all leading and
                              # trailing space from the
                              # ASCII file generated by
                              # the conversion

include_only="ABS"            # This will ignore (omit) ALL
                              # text/HTML that is not inside
                              # the named tag. So,
                              # in this example, only text
                              # inside a <ABS>blah</ABS>
                              # tag set would be included
                              # in the result

For "include_only" ANY tag can be specified (it does not
have to be a valid HTML tag), but for everything else only
valid HTML tags will be recognized.
In each of the INCLUDE directives, a variable which contains the path to a file may be used rather than the literal quoted value. Also, if FROMURL is put after the INCLUDE ([[INCLUDE FROMURL $FILENAME]]), the filename will be treated as a URL and included over the network; http URLs are supported, at least.

Scope and Variables

New 7/27/00
Normally, when inside an array loop, one can only access variables defined for the entire template ([[$FOO]], for example), or variables within the current object in the array (as [[$.FIELD]]). However, if arrays are nested within other arrays, it may be desirable to access variables in an intermediate scope. This can be done by referring to the variable with a colon: "$:".

For example, the record QUEUE may have two arrays defined within: ACTION and MANUSCRIPT. While looping through the MANUSCRIPT loop, one may wish to loop through the ACTION loop. The following code will do that:

[[FOREACH $:ACTION]]...text...
If MANUSCRIPT then has a sub-loop called REVIEW, the colon can be repeated:
[[FOREACH $::ACTION]]...text...
and so on.

Built-in Variables

For convenience, DTL includes several "built-in" variables and objects. As noted above, they are referenced with a "!" rather than a "$", but otherwise can be used as any other variables. These variables are:
  • TODAY -- an object including one field, "DATE", which is set to the current date/time.
  • TOTALCOUNT -- the total number of items in the current FOREACH loop. Only available within a FOREACH loop.
  • CURRCOUNT -- the number of the current object in the array in the current FOREACH loop. Only available within a FOREACH loop.
  • ISFIRST -- defined if the current item is the first item in the array. Only available within a FOREACH loop.
  • ISLAST -- defined if the current item is the last item in the array. Only available within a FOREACH loop.
  • ALL -- displays a list of all variables defined by the current DTL session in html format. ALL can also be used as a built-in field for objects, for example [[$OBJECT.!ALL]] will display all fields defined in "OBJECT".
  • ALLVARDEFS -- like ALL, except that it outputs the current variables in DTL syntax, which could then be saved and and re-input into the dtl engine. For example, if you were trying to fine-tune a page which took lots of time to produce, you could first just put [[!ALLVARDEFS]] in the output template to get all of the data, save that file in savedstuff.dtl, then just run 'rundtl savedstuff.dtl real_output_template.dtl' as you fine-tune real_output_template.dtl.
  • UPPER -- built-in field, displays the variable in all upper case.
  • LOWER -- built-in field, displays the variable in all lower case.
  • LB and RB -- Left and right brackets. Useful for when you want to print something like [Abstract] where "Abstract" is actually a variable. This could be coded as [[!LB]][[$ABSTRACT_NAME]][[!RB]] or, more simply, [[!LB $ABSTRACT_NAME !RB]]. This is necessary because while DTL can handle single square brackets in most text, it cannot parse something like "[[[$ABSTRACT_NAME]]]".
As noted above, although array objects do not have user-definable fields that can be accessed directly, array objects do have one built-in field: COUNT, which is the number of items in the array. This can be accessed as [[$ARRAY.!COUNT]].

Date Formatting

DTL allows the servlet to define a variable with a Java Date as a value. This variable can be used directly, in which case the format may vary depending on whether it is a java.sql.Date or a java.util.Date, or it can be treated as an object with several built-in fields. These fields are:
  • MONTH_LONG -- the full name of the Month, for example September
  • MONTH_MEDIUM -- the three-letter abbreviation for the Month, for example Sep
  • MONTH_SHORT -- the number of the month, for example 9
  • DAY -- the number of the date, for example 2
  • DAY_LONG -- the number of the date with a leading 0, for example 02
  • YEAR_SHORT -- the last two digits of the year, for example 97
  • YEAR_LONG -- the full four-digit year, for example 1997
  • TIME -- the time, in PDT, with AM/PM. Currently no other way to get it.
  • DAYOFWEEK -- the day of the week. (full name, eg "Monday", by default
  • DAYOFWEEK_LONG -- the day of the week - full name (eg, Monday).
  • DAYOFWEEK_MEDIUM -- the day of the week - abbrev (eg, Mon, Tue).
  • DAYOFWEEK_SHORT -- the day of the week - digits (1-7).
  • HOUR -- hour on 12-hour clock (1-12).
  • HOUR_LONG -- 2-digit hour on 12-hour clock (01-12).
  • HOUROFDAY -- 2-digit hour on 24-hour clock (01-23).
  • MINUTE -- 2-digit minute of time (00-59).
  • AMPM -- "AM" or "PM" for 12-hour clock.
  • LONG -- eg, 2 January 2002
  • LONG_ALT -- eg, January 2, 2002
  • MEDIUM -- eg, 2 Jan 2002
  • MEDIUM_ALT -- eg, Jan 2, 2002
  • SHORT -- M/D/YYYY eg, 1/2/2002
  • SHORT_ALT -- D/M/YYYY eg, 2/1/2002
  • SQL -- eg, 2002-01-02 13:37:52.674

This can be used to define a date format. For example, the format might be defined as an EVALDEF as follows:


This format could then be used as follows:


where DATE is a date set to 9/2/97, would generate the output "September 2, 1997".


DTL handles comments by ignoring any text between "[/*" and "*/]". For example, the following text would be ignored in a DTL document:
[/* This is a comment, which will be ignored!  [[$DONT_USE]]
    See, ain't it great? :)
Comments may extend over multiple lines, and may include DTL code.
As an alternative, DTL can handle comments which start with [// and end at the end of the current line. Comments delineated with [/* can comment out sections delineated with [// and vice versa; however,


Stanford University Academic Computing
A division of Stanford University Libraries and Academic Information Resources
Copyright © 2001-2002 by Board of Trustees of the Leland Stanford Junior University.