- Variable and Object Substitution
- Conditionals: IF, SWITCH
- Looping: FOREACH
- Inline Variable
Definition: VARDEF, RECDEF, ARRDEF
- Reusable sub-templates:
- Including other files
- Scope and Variables
- Built-in Variables
- 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
IF DEFINED $BAR]]Hello 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.
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:
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
<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: www.jbc.org?debug=adam_says_debug&user=Josh
vs www.jbc.org?user=josh&debug=adam_says_debug). You will get
a page that looks like this:
So, if your template said [[$RESOURCE.VOL]], you now know it should have
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]]
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
[[WITH $ARTICLE]][[$.FIRSTPAGE]] [[$.LASTPAGE]] [[$.VOLUME]] [[/WITH]]
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
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
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!
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
[New, 7/10/98] Multiple variables can be tested
by using "ORDEF" between multiple values. For example,
[[IF DEFINED $ERRORONE ORDEF $ERRORTWO ORDEF $ERRORTHREE]]There is
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
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
[[IF EQUAL $ARTICLE.FIRSTPAGE $ARTICLE.LASTPAGE]][[$ARTICLE.FIRSTPAGE]]
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
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
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]]
(Important: New in 2.1) Unlike IF EQUAL, the quoted
string value may not be replaced with a variable.
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]]
will produce the text "You got this for free" for values of either
"FREEREG" or "COMPLIMENTARY."
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
[[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:
[[FOREACH $AUTHOR]][[IF DEFINED !ISLAST]][[IF NOT DEFINED !ISFIRST]]and
[[/IF]][[/IF]][[$.FNAME]] [[$.LNAME]][[IF EQUAL !TOTALCOUNT "2"]] [[ELSE]][[IF
NOT DEFINED !ISLAST]], [[/IF]][[/IF]][[/FOREACH]]
which might generate the output
Adam Elman and Edward M. Beaux
Adam Elman, Ethan Straffin, and Edward M. Beaux
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
[[FOREACH $ITEM]][[$.ITEM]], [[IF EVERY "3"]] --- [[/IF]][[/FOREACH]]
Item 1, Item 2, Item 3, --- Item 4, Item 5, Item 6, --- Item 7,
Item 8, Item 9, ---
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
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
[[VARDEF $VARNAME]]This is a substitution of [[$MYVAR]][[/VARDEF]]
if $MYVAR is defined later as "foo", then the output of [[$VARNAME]]would
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]]
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]],
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:
[[FOREACH $ARRNAME]][[$.VARNAME1]], [[/FOREACH]]
which would appear as
1st Value of the variable, 2nd Value of the variable,
ARRDEFS can be nested within RECDEFS and vice versa.
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
[[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:
[[EVAL $TMPLNAME WITH $OBJNAME]]
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.
External files or URLs can be included in a template. DTL allows four
types of inclusions:
inserts the text of a file directly into the output of the template
without any further processing.
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.
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.
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
(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,
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 /FOREACH /WITH]]
If MANUSCRIPT then has a sub-loop called REVIEW, the colon can be repeated:
[[/FOREACH /FOREACH /FOREACH /WITH]]
and so on.
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:
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]].
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
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]]]".
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
YEAR_SHORT -- the last two digits of the year, for
YEAR_LONG -- the full four-digit year, for example 1997
TIME -- the time, in PDT, with AM/PM. Currently no other way to
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:
[[EVALDEF $DATEFORMAT_LONG]][[$.MONTH_LONG]] [[$.DAY]], [[$.YEAR_LONG]][[/EVALDEF]]
This format could then be used as follows:
[[EVAL $DATEFORMAT_LONG WITH $DATE]]
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,