Tech Is Hard

Credibility = Talent x Years of experience + Proven hardcore accomplishment

Simple XSL 1.0 String Templates and Very Simple XSL Unit Testing


In order to reference external examples that evolve, I’m going to start with a stylesheet containing simple string functions and build on it.  There’s also a simple, but useful methodology for unit testing the XSL templates.

Dealing with strings is notoriously annoying in XSL, so in my xsl directory I have strings.xsl, a stylesheet to do repetitive and recursive stuff.  The first function we’ll need is str-replace.  I recently updated this template to be pretty short and sweet.

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:template name="str-replace">
  <xsl:param name="haystack"/>
  <xsl:param name="needle"/>
  <xsl:param name="repl" select="''"/>
  <xsl:choose>
    <xsl:when test="contains ($haystack, $needle)">
      <xsl:text><xsl:value-of select="substring-before ($haystack, $needle)"/></xsl:text>
      <xsl:text><xsl:value-of select="$repl"/></xsl:text>
      <xsl:call-template name="str-replace">
        <xsl:with-param name="haystack" select="substring-after ($haystack, $needle)"/>
        <xsl:with-param name="needle" select="$needle"/>
        <xsl:with-param name="repl" select="$repl"/>
      </xsl:call-template>
    </xsl:when>
    <xsl:otherwise>
      <xsl:text><xsl:value-of select="$haystack"/></xsl:text>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

<!-- translation for lowercase-->
<xsl:variable name="lcletters">abcdefghijklmnopqrstuvwxyz</xsl:variable>
<xsl:variable name="ucletters">ABCDEFGHIJKLMNOPQRSTUVWXYZ</xsl:variable>

<xsl:template name="str-tolower">
  <xsl:param name="str"/>
  <xsl:value-of select="translate($str, $ucletters, $lcletters)"/>
</xsl:template>

</xsl:stylesheet>

Hopefully it’s a fairly clear template: as long as the $haystack contains $needle, concatenate the string before $needle, the replacement value to use, and the result of calling the template with what comes after the first $needle occurrence.  When there’s no $needle, then it just results in $haystack.

I want to use what I’ve learned about unit testing to prevent regression in the future. It can be extremely hard to figure out which change caused regression in XSL; you may not notice the one scenario that triggers it for a while. I wrote strings.test.xsl to call templates in strings.xsl.

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:import href="strings.xsl" />

<xsl:call-template name="assert">
  <xsl:with-param name="expected" select="'@apple1'"/>
  <xsl:with-param name="actual">
    <xsl:call-template name="str-tolower">
      <xsl:with-param name="str" select="'@ApPlE1'"/>
    </xsl:call-template>
  </xsl:with-param>
</xsl:call-template>

<xsl:template match="/">
  <xsl:call-template name="assert">
    <xsl:with-param name="expected" select="'abcBBB-BBBxyz'"/>
    <xsl:with-param name="actual">
      <xsl:call-template name="str-replace">
        <xsl:with-param name="haystack" select="'abcA-Axyz'"/>
        <xsl:with-param name="needle" select="'A'"/>
        <xsl:with-param name="repl" select="'BBB'"/>
      </xsl:call-template>
    </xsl:with-param>
  </xsl:call-template>

  <xsl:call-template name="assert">
    <xsl:with-param name="expected" select="'abc-xyz'"/>
      <xsl:with-param name="actual">
      <xsl:call-template name="str-replace">
        <xsl:with-param name="haystack" select="'aAbcA-AxyzA'"/>
        <xsl:with-param name="needle" select="'A'"/>
      </xsl:call-template>
    </xsl:with-param>
  </xsl:call-template>

  <xsl:call-template name="assert">
    <xsl:with-param name="expected" select="'eleven1'"/>
      <xsl:with-param name="actual">
        <xsl:call-template name="str-replace">
          <xsl:with-param name="haystack" select="'111'"/>
          <xsl:with-param name="needle" select="'11'"/>
        <xsl:with-param name="repl" select="'eleven'"/>
      </xsl:call-template>
    </xsl:with-param>
  </xsl:call-template>
</xsl:template>

<xsl:template name="assert">
  <xsl:param name="expected" select="'missing expected param'"/>
  <xsl:param name="actual" select="'missing actual param'"/>
  <xsl:if test="not ($actual = $expected)">
    <xsl:message terminate="yes">Expected <xsl:value-of select="$expected"/>; got <xsl:value-of select="$actual"/></xsl:message>
  </xsl:if>
</xsl:template>

</xsl:stylesheet>

The way I use strings.test.xsl is with any XML input document, because it doesn’t actually use the input XML.  It might be interesting to come up with a unit testing stylesheet that took the stylesheet to be tested as its input document.  And use the document() function to also do something introspective.

I admit there’s nothing really descriptive explaining what the test is for, but repeating the assert calls is really easy and I just update the template I’m calling or use a new set of parameters for a new condition. It’s a brute force method that works for now.

Advertisements

One response to “Simple XSL 1.0 String Templates and Very Simple XSL Unit Testing

  1. Pingback: XSL 1.0 str-tolower Template « Tech Is Hard

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: