Designing your own filter

The output of the cross reference stage of the pipeline, XRefFilter, is available for the user to write their own filters. First it is important to understand the format of the output of the XRefFilter. For those that are happy with the syntax there is an associated schema for the output format here which define the syntax.

The basic outline of a simple transform is discussed below, and a reasonable knowledge of XSL is required. The following is a walkthrough of the default tangle filter (tangle.xsl). First the stylesheet and the namespaces are declared:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:aw="http://www.hsfr.org.uk/Schema/AntWeave" version="1.0">

The output declaration determines reflects whether the output is plain text (as in tangle or weave) or XML (as in HTML output). If outputting text you probably want to preserve the space in all that is being output:

<xsl:output method="text" version="1.0" omit-xml-declaration="yes" encoding="UTF-8" indent="no"/> <xsl:preserve-space elements="*"/>

There are three key parameters that are passed from the Java class:

<xsl:param name="nowebFileName"/> <xsl:param name="delay"/> <xsl:param name="rootChunk"/> <xsl:param name="startCommentString" select="'//'"/> <xsl:param name="endCommentString" select="''"/>

All fairly self-explanatory. The root template matches the root of the input XML that starts everything off:

<xsl:template match="/"> <!-- Find the first code chunk to process (and all others with same name) --> <xsl:for-each select="//aw:chunk[(@name = '*' or @name = $rootChunk) and @type = 'code']"> <!-- Set the root name of the chunk we start with --> <xsl:variable name="rootName"> <xsl:choose> <xsl:when test="string-length($rootChunk) = 0"> <xsl:value-of select="'*'"/> </xsl:when> <xsl:otherwise> <xsl:value-of select="$rootChunk"/> </xsl:otherwise> </xsl:choose> </xsl:variable> <!-- Call the actual template for processing the code lines --> <xsl:call-template name="process-code"> <xsl:with-param name="chunkNode" select="."/> <xsl:with-param name="currentFile" select="@file"/> <xsl:with-param name="lineNumber" select="@line"/> <xsl:with-param name="name" select="$rootName"/> </xsl:call-template> </xsl:for-each> </xsl:template>

The process-code template is where the work is done. It is a recursive template to cater with all the include chunk statements. The parameters are

  1. chunkNode the current node chunk being processed (a list of code lines)
  2. currentFile the current file being processed (used for comments)
  3. lineNumber the current line being processed as string (used for comments)
  4. name the name of the current code chunk

It returns the raw code line to be output. It also adds a comment line to help debugging. It is important that the attribute disable-output-escaping is set to true for all output.

<xsl:template name="process-code"> <xsl:param name="chunkNode"/> <xsl:param name="currentFile"/> <xsl:param name="lineNumber"/> <xsl:param name="name"/> <xsl:for-each select="$chunkNode"> <xsl:value-of select="$startCommentString"/> <xsl:text> File:</xsl:text> <xsl:value-of select="$currentFile"/> <xsl:text> Line:</xsl:text> <xsl:value-of select="$lineNumber"/> <xsl:text> chunk:</xsl:text> <xsl:value-of select="$name"/> <xsl:value-of select="$endCommentString"/> <xsl:text> </xsl:text> <!-- Select each code line in turn --> <xsl:for-each select="aw:code"> <!-- Select enclosed element: include, variable or separator --> <xsl:for-each select="*"> <xsl:choose> <!-- Include tag forces a recurse to pick up included chunks --> <xsl:when test="name() = 'aw:include'"> <xsl:variable name="chunkRef" select="@ref"/> <xsl:call-template name="process-code"> <xsl:with-param name="chunkNode" select="//aw:chunk[@name = $chunkRef]"/> <xsl:with-param name="currentFile" select="//aw:chunk[@name = $chunkRef]/@file"/> <xsl:with-param name="lineNumber" select="//aw:chunk[@name = $chunkRef]/@line"/> <xsl:with-param name="name" select="//aw:chunk[@name = $chunkRef]/@name"/> </xsl:call-template> </xsl:when> <xsl:otherwise> <!-- All other tag content passed through --> <xsl:value-of disable-output-escaping="yes" select="."/> </xsl:otherwise> </xsl:choose> </xsl:for-each> <!-- newline --> <xsl:text> </xsl:text> </xsl:for-each> </xsl:for-each> </xsl:template>

Soak up any remaining elements not processed by the above.

<xsl:template match="node() | @*" priority="-1"> </xsl:template> </xsl:stylesheet>

And that is all you need to generate the code output (the weave transform is a lot more complicated, as in much more complicated).

Adding the filter

Once your filter is designed you need to tell the plugin about it. The instructions are put in a filters file, so typically if we have a filter myFilter.xsl you would need a file

<?xml version="1.0" encoding="UTF-8"?> <antweave> <pipeline name="myFilter"> <filter name="myFilter.xsl"/> </pipeline> <pipeline name="tangle"> <filter name="tangle.xsl"/> </pipeline> <pipeline name="weave"> <filter name="index.xsl"/> <filter name="weave.xsl"/> </pipeline> </antweave>

Currently you would have to copy the tangle and weave pipelines from the default filters definition file to the same folder as the user's filters definition file. This will be changed in future version and you would assume that the default tangle and weave filters would always be available unless overridden in the user defined file. You can write as may filters as you like and they will be run in the order that they are declared in the filters file. The output of each will be the input of the next.

Typical use would be (assuming that the user's filter is in a folder filters in the current folder:

<?xml version="1.0"?> <project name="test" basedir="."> <taskdef name="antweave" classname="uk.org.hsfr.antweave.AntWeave"/> <property name="sourceDir" value="."/> <target name="tangle"> <antweave srcdir="${sourceDir}" filters="filters/filters-user.xml" ext="m"/> </target> </project>
Copyright 2015 Hugh Field-Richards. All Rights Reserved.