Recently, I ran into a problem in an XSLT script that was preventing me from writing a file from within a template processing an attribute node. I am writing a script that takes a single XML file that references other XML files with relative paths as input (a DITA map file to be exact) and crawls the “file reference tree”, processing all files along the way. Since the majority of the files are XML this is a classic single input multiple output XSLT problem….for the most part. Some of the referenced files are graphics such as PNGs, JPGs, and GIFs. XSLT and XPath do not provide any capability to copy these file types so I chose to write an external Java class and call it from the XSLT script. XSLT purists might even say I shouldn’t be doing such a thing but I’m trying to accomplish this whole process with a single script to avoid having to track things in any intermediate XML. The simple Java class has one static method copyFile() that takes two arguments for the source location and destination location.
<xsl:stylesheet version="2.0" ...
xmlns:cf="com.mypackage.CopyFile">
...
<xsl:template match="@someattribute">
<xsl:value-of select="cf:copyFile(string($src),string($dst))"/>
</xsl:template>
...
</xsl:stylesheet>
The main issue with what I was doing centered around the fact that the template where I needed to perform the file write was in the XSLT context of an attribute. Saxon complained with the following error:
XTDE0410: An attribute node (audience) cannot be created after the children of the
containing element
I quickly realized that because I was using to run the custom function that I was doing something illegal in terms of what I was appending to the output tree. So I got tricky and wrapped the copyFile() call in an tag. To my surprise this actually worked. Since does not affect the output tree it allows you to do pretty much anything and continue the XSLT thread.
<xsl:message>
<xsl:value-of select="cf:copyFile(string($src),string($dst))"/>
</xsl:message>
As a corollary to the recent post I made about trying to recursively moving or copying directories with Ant, this post shows you how to do so without the use of the third-party ant-contrib library. The trick is understanding Ant’s wildcard syntax used in the type, which can be rather confusing.
Previously, I was trying to move a directory and all of its contents recursively to another directory using the following target
<target name="move.dirs">
<move todir="backups">
<fileset dir="." includes="*incremental*"/>
</move>
</target>
The result was a non-recursive copy of the first level of directories with ‘incremental’ in the directory name. The only way I could accomplish a deep copy or move at the time was to use ant-contrib’s task to iterate over all files recursively. As I discovered after playing with my original script (above) a bit more, this is unnecessary.
By modifying the wildcard syntax in my original target I am able get what I want.
<target name="move.dir">
<move todir="backups">
<fileset dir="." includes="**/*incremental*/"/>
</move>
</target>
So, the additional ‘**/’ at the beginning and a trailing ‘/’ at the end of the includes attribute value is all that is necessary. All directories (and their contents) with ‘incremental’ in the name will be moved to a directory called ‘backups/’. The same will work for the task. I won’t go into the details about how this syntax works but you can read about it here.
Related Post: Move or copy a set of directories using Ant
I’m often baffled by the way some tasks work in Ant. Today, I needed to move a set of directories to another directory based on a <fileset> wildcard expression. My first, seemingly intuitive attempt was to use the <move> task and specify a <fileset> with an includes expression. Let’s suppose I want to move a set of incremental backup directories that all have the keyword “incremental” in the name:
backup-incremental1
backup-incremental2
backup-incremental3
It seems intuitive to use the following Ant move task to move these directories into another directory called backups/
<target name="move.dirs">
<mkdir dir="backups"/>
<move todir="backups">
<fileset dir="." includes="*incremental*"/>
</move>
</target>
For some reason this only copies the root directory without recursively copying its contents. Ant gives a message that looks like the following:
[move] Moved 3 empty directories to 3 empty directories under Sandbox/ant/backups
After going through several iterations (emphatically gesturing at the monitor along the way) I finally realized that the core <move> and <copy> Ant tasks cannot do this. At least, not that I could figure out. Instead, you have to use the ant-contrib library’s <foreach> element to iterate over each directory and call a separate target that will actually move the directory:
<target name="move.dirs">
<mkdir dir="backups"/>
<foreach param="dir" target="move.inc.backups">
<path>
<fileset dir=".">
<include name="*incremental*"/>
</fileset>
</path>
</foreach>
</target>
<target name="move.inc.backups">
<move todir="backups" file="$dir"/>
</target>
Why is this so? I’m still trying to figure out if there are any vanilla Ant solutions to this problem. I will be sure to post what I find in the future. For now I will just accept this as another one of many quirks in Ant. I love Ant for its simplicity and the ability to cobble together scripts quickly to automate tasks. But on the other hand, sometimes Ant lacks the intuitive nature of scripting languages like python or ruby.
If you have any alternatives to the method I presented here, please feel free to post them in the comments.
Update: To move or copy directories recursively without ant-contrib see this more recent post Ant copy and move directories recursively without ant-contrib