Word wrapping in XSLT

Sunday, 2 Jan 2005 [Thursday, 15 Jul 2010]

Update: The code in this entry is broken; do not use. Instead, take a look at the corrected version.

The scraped feed generator I just wrote for the QDB Latest 50 Quotes contains an interesting excercise in XSLT: a word wrapper for strings.

XSLT’s string parsing capabilities are extremly limited, and even EXSLT was no help – I would have reached for it in a heartbeat otherwise. In order to make do with what I had, I wrote a recursive template. It recurses down as it breaks the string into words, preprending break marks to those words which cross wrap boundaries.

<!--

THIS CODE IS BUGGY. DO NOT USE.
SEE http://plasmasturm.org/log/xslwordwrap/
FOR A CORRECT VERSION

-->

<xsl:template name="wrap-string">
  <xsl:param name="str" />
  <xsl:param name="wrap-col" />
  <xsl:param name="break-mark" />
  <xsl:param name="pos" select="0" />
  <xsl:choose>
    <xsl:when test="contains( $str, ' ' )">
      <xsl:variable name="first-word" select="substring-before( $str, ' ' )" />
      <xsl:variable name="pos-now" select="$pos + string-length( $first-word )" />

      <xsl:choose>
        <xsl:when test="$pos = 0" />
        <xsl:when test="floor( $pos div $wrap-col ) != floor( $pos-now div $wrap-col )">
          <xsl:copy-of select="$break-mark" />
        </xsl:when>
        <xsl:otherwise>
          <xsl:text> </xsl:text>
        </xsl:otherwise>
      </xsl:choose>

      <!--

      THIS CODE IS BUGGY. DO NOT USE.
      SEE http://plasmasturm.org/log/xslwordwrap/
      FOR A CORRECT VERSION

      -->

      <xsl:value-of select="$first-word" />

      <xsl:call-template name="wrap-string">
        <xsl:with-param name="str" select="substring-after( $str, ' ' )" />
        <xsl:with-param name="wrap-col" select="$wrap-col" />
        <xsl:with-param name="break-mark" select="$break-mark" />
        <xsl:with-param name="pos" select="$pos-now" />
      </xsl:call-template>
    </xsl:when>
    <xsl:otherwise>
      <xsl:if test="$pos &gt; 0"><xsl:text> </xsl:text></xsl:if>
      <xsl:value-of select="$str" />
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

<!--

THIS CODE IS BUGGY. DO NOT USE.
SEE http://plasmasturm.org/log/xslwordwrap/
FOR A CORRECT VERSION

-->

Curiously, XSLT is such a restrictive environment for some tasks that I actually enjoy using it purely for the challenge it presents. The reason it doesn’t simply get frustrating is that unlike in some other restrictive environments, such as shell, nothing is really impossible – just sometimes very difficult.