Most of the macros follow a pattern. Firstly, the main entry point
is a macro of the same name as the module (or file). So, for
example, the
compile-schematron.xml file
contains a “top level”
<compile-schematron>
macro. This will
detect the type of arguments given to it and run either the
<compile-schematorn-file>
macro if a
single file is supplied or the
<compile-schematron-fileset>
macro if a
fileset (or folder of files) has been given. Note that in the
latter case the “-fileset” macro will call the
“-file” macro in a loop over all files in the
fileset. This pattern is typical of all macros that can take
either a file or a fileset specification.
Macros will also use other macros in the library as required. For
example, almost all macros use macros from the
attr-checks.xml module to
provide additional checks on their arguments, beyond that provided
by the Ant <macrodef>
facility itself.
Although considerable use of the Ant Contrib
<for>
macro is used (simply due to the
utility it provides) an attempt has been made to avoid other logic
constructs such as the
<if>/<then>/<else>
macros. As
Ant is generally declarative in nature rather than imperative,
extensive use has been made of its built in
“if/unless”
attributes.
As an example of macro construction consider the
<xspec-file>
macro below:
<macrodef name="xspec-file" description="Run a single XSpec file"> <attribute name="file" default="" description="XSpec file"/> <attribute name="test" default="" description="Type of test to run [xslt,xquery,schematron]"/> <attribute name="coverage" default="false" description="Output test coverage report [true,false]"/> <attribute name="junit" default="false" description="Output JUnit report [true,false]"/> <sequential> <!-- fail if required attributes not set --> <check-attr-set macro="xspec-file" name="file" value="@{file}"/> <check-attr-list macro="xspec-file" name="test" value="@{test}" list="xslt,xquery,schematron"/> <check-attr-bool macro="xspec-file" name="coverage" value="@{coverage}"/> <check-attr-bool macro="xspec-file" name="junit" value="@{junit}"/> <!-- find XSpec command --> <check-exe-path property="xspec.exe" unix="xspec.sh" windows="xspec.bat" unless:set="xspec.exe"/> <!-- get test type --> <local name="test.@{test}"/> <property name="test.@{test}" value="true"/> <!-- run tidy --> <property name="exec.failonerror" value="true"/> <exec executable="${xspec.exe}" failonerror="${exec.failonerror}"> <arg line="-t" if:set="test.xslt"/> <arg line="-q" if:set="test.xquery"/> <arg line="-s" if:set="test.schematron"/> <arg line="-c" if:true="@{coverage}"/> <arg line="-j" if:true="@{junit}"/> <arg line="@{file}"/> </exec> </sequential> </macrodef>
All <macrodef>
elements and their related
<attribute>
/<element>
children have “description” attributes—these are used
to generate documentation via the
extract-markdown.xml module. In
the first section, the attributes are checked: the file name
cannot be blank, the type of test must be one of
“xslt”, “xquery” or
“schematron” and the values of the
“coverage” and “junit” attributes must
be “true” or “false”. The various
<check-attr-...>
macros will fail the
build if these conditions are not met. (Note that Ant itself will
only check if attributes are required or optional, and will apply
their default values. It cannot check attribute types or lists of
allowed values). Next the system’s path is
checked for the presence of the XSpec executable (this is
“xspec.sh” in Unix based systems and
“xspec.bat” for Windows). Again the build will fail
if the executable cannot be found.
Next a property is set whose name is based on the test type. So,
therefore, if the “xquery” test was selected the
“test.xquery” property will be created. This is used
in the <exec>
macro to set the correct
command line arguments: the “test.xslt” and
“test.schematron” properties will not exist and so
the “-t” and “-s” arguments will not be
included in the call. This pattern is used extensively in the
XPantS library to keep the code readable (although it does require
familiarity with how Ant properties work).