Skip to content

Instantly share code, notes, and snippets.

@ColinEberhardt
Last active December 15, 2015 22:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ColinEberhardt/5334470 to your computer and use it in GitHub Desktop.
Save ColinEberhardt/5334470 to your computer and use it in GitHub Desktop.
An XSLT siteswap validator by Colin E.
The aim was to create a vaildator that takes an XML document of the following form and annotate
it to state whether each individual siteswap was valid or invalid:
<?xml version="1.0" encoding="ISO-8859-1"?>
<siteswaps>
<siteswap>34535</siteswap>
<siteswap>543</siteswap>
<siteswap>441</siteswap>
<siteswap>97531</siteswap>
<siteswap>24234</siteswap>
</siteswaps>
generating the following:
<?xml version="1.0" encoding="UTF-8"?>
<siteswaps>
<siteswap valid="true">34535</siteswap>
<siteswap valid="false">543</siteswap>
<siteswap valid="true">441</siteswap>
<siteswap valid="true">97531</siteswap>
<siteswap valid="true">24234</siteswap>
</siteswaps>
However, the strength of XSLT lies in its ability to perform operations based on the structure
of the document tree, rather than text processing. Therefore the above document is not in a very
XSLT friendly format making it very difficult to validate it using a single tranformation.
After a bit of head scratching I decided to tackle this problem by creating a chain of transformations,
each on performing a specific task. This is much closer to the spirit of what XSLT was intended for.
======= Transformation #1: "expand_throws.xsl" =======
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="siteswap">
<xsl:element name="siteswap">
<xsl:attribute name="value">
<xsl:value-of select="."/>
</xsl:attribute>
<xsl:call-template name="splitThrows">
<xsl:with-param name="siteswap" select="."/>
</xsl:call-template>
</xsl:element>
</xsl:template>
<xsl:template name="splitThrows">
<xsl:param name="siteswap"/>
<xsl:if test="string-length($siteswap)>0">
<xsl:element name="beat">
<xsl:element name="throw">
<xsl:value-of select="substring($siteswap,1,1)"/>
</xsl:element>
</xsl:element>
<xsl:call-template name="splitThrows">
<xsl:with-param name="siteswap" select="substring($siteswap,2,string-length($siteswap)-1)"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
The above transformation splits each siteswap up into a collection of beats, each on containing a throw.
for example the following XML file:
<?xml version="1.0" encoding="ISO-8859-1"?>
<siteswaps>
<siteswap>4411</siteswap>
</siteswaps>
will be transformed to become the following:
<?xml version="1.0" encoding="UTF-8"?>
<siteswaps>
<siteswap value="4411">
<beat>
<throw>4</throw>
</beat>
<beat>
<throw>4</throw>
</beat>
<beat>
<throw>1</throw>
</beat>
<beat>
<throw>1</throw>
</beat>
</siteswap>
</siteswaps>
======= Transformation #2: "locate_catches.xsl" =======
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="siteswap">
<xsl:element name="siteswap">
<xsl:apply-templates select="@*"/>
<xsl:variable name="siteswapLength" select="count(beat)"/>
<xsl:for-each select="beat">
<xsl:element name="beat">
<xsl:variable name="thisBeatPos" select="position()"/>
<xsl:apply-templates select="throw"/>
<xsl:for-each select="../beat/throw">
<xsl:if test="((number(.)+position()) mod $siteswapLength)+1 = $thisBeatPos">
<xsl:element name="catch"/>
</xsl:if>
</xsl:for-each>
</xsl:element>
</xsl:for-each>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
The above transformation considers each beat in turn and inserts a catch element if
any of the throws within the siteswap are caught there. for example, the following XML:
<?xml version="1.0" encoding="UTF-8"?>
<siteswaps>
<siteswap value="4411">
<beat>
<throw>4</throw>
</beat>
<beat>
<throw>4</throw>
</beat>
<beat>
<throw>1</throw>
</beat>
<beat>
<throw>1</throw>
</beat>
</siteswap>
</siteswaps>
Is transformed to:
<?xml version="1.0" encoding="UTF-8"?>
<siteswaps>
<siteswap value="4411">
<beat>
<throw>4</throw>
<catch/>
</beat>
<beat>
<throw>4</throw>
<catch/>
<catch/>
</beat>
<beat>
<throw>1</throw>
<catch/>
</beat>
<beat>
<throw>1</throw>
</beat>
</siteswap>
</siteswaps>
You should already be able to see that this siteswap is not valid, due to their being two
catches made on the second beat, but just a single throw.
======= Transformation #3: "validate_siteswap.xsl" =======
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="siteswap">
<xsl:element name="siteswap">
<xsl:attribute name="valid">
<xsl:variable name="invalid">
<xsl:call-template name="findMultipleCatches"/>
</xsl:variable>
<xsl:choose>
<xsl:when test="$invalid=''">
<xsl:text>true</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:text>false</xsl:text>
</xsl:otherwise>
</xsl:choose>
</xsl:attribute>
<xsl:value-of select="@value"/>
</xsl:element>
</xsl:template>
<xsl:template name="findMultipleCatches">
<xsl:for-each select="beat">
<xsl:if test="count(catch) &gt; 1">
<xsl:text>x</xsl:text>
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
The final transformation inspects all the beats within each siteswap to find multiple
catches. The document is transformed back into its original form, with the addition
of a valid attribute. for example, the following XML:
<?xml version="1.0" encoding="UTF-8"?>
<siteswaps>
<siteswap value="4411">
<beat>
<throw>4</throw>
<catch/>
</beat>
<beat>
<throw>4</throw>
<catch/>
<catch/>
</beat>
<beat>
<throw>1</throw>
<catch/>
</beat>
<beat>
<throw>1</throw>
</beat>
</siteswap>
</siteswaps>
Is transformed to:
<?xml version="1.0" encoding="UTF-8"?>
<siteswaps>
<siteswap valid="false">4411</siteswap>
</siteswaps>
======= Putting it all together =======
if you use Ant, the following build file will execute the transformations
in the desired order:
<?xml version="1.0"?>
<project name="validate cocoon" default="validate" basedir=".">
<target name="validate">
<xslt in="siteswaps.xml"
out="siteswaps_throws_expanded.xml"
style="expand_throws.xsl" />
<xslt in="siteswaps_throws_expanded.xml"
out="siteswaps_catches.xml"
style="locate_catches.xsl" />
<xslt in="siteswaps_catches.xml"
out="siteswaps_validated.xml"
style="validate_siteswap.xsl" />
</target>
</project>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment