Tech Is Hard

Credibility = Talent x Years of experience + Proven hardcore accomplishment

Tag Archives: xsl

XSL 1.0 str-tolower Template


We’ll add a couple variables and a template to the strings.xsl stylesheet.

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

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

And here’s a new UT to add to strings.test.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>
Advertisements

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.

Self-Contained Data Tables in XSL Stylesheets


I’m going to share a trick I’ve been using in my stylesheets for years, because every time I do it someone gets amazed.

Lookups often need to be performed during transformation of a document. Let’s use the example of taking month numbers and turning them into the standard 3-character abbreviations. Instead of hard-coding the values in a choose, or reading a separate XML document, add your own working data to the stylesheet with a namespace.

<grant:stuff>
    <grant:month-name month="01">Jan</grant:month-name>
    <grant:month-name month="02">Feb</grant:month-name>
    <grant:month-name month="03">Mar</grant:month-name>
    <grant:month-name month="04">Apr</grant:month-name>
    <grant:month-name month="05">May</grant:month-name>
    <grant:month-name month="06">Jun</grant:month-name>
    <grant:month-name month="07">Jul</grant:month-name>
    <grant:month-name month="08">Aug</grant:month-name>
    <grant:month-name month="09">Sep</grant:month-name>
    <grant:month-name month="10">Oct</grant:month-name>
    <grant:month-name month="11">Nov</grant:month-name>
    <grant:month-name month="12">Dec</grant:month-name>
</grant:stuff>

For this to work inside your XSLT, you have to add a namespace prefix declaration for it:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:grant="https://techishard.wordpress.com"

Then you use the document() function without a document, to get this stylesheet:

<xsl:value-of select="document('')/xsl:stylesheet/grant:stuff/grant:month-name[@month = 10]"/>

Will get you
Oct

%d bloggers like this: