Tuesday, April 18, 2006

Wrapping long strings with XSL 1.0

For my recent work with XML and XSL, I needed to wrap strings when generating a text report. I found examples online to do it, but they depended on XSL 2.0 functions such as the string tokenizer. I needed a way to do it with plain old XSL 1.0, so the solution I came up with is below. This solution is a bit more complex than necessary since I needed the ability to add markup around the wrapped lines (the prefix and suffix). Additionally, I needed the ability to add different markup before and after wrapped lines as well (the hangingPrefix and hangingSuffix). I could have taken it out to simplify this example, however, I thought some people might find it useful. Additionally, it's designed for all the prefixes and suffixes to be optional anyway. Here's my solution (the second template called splitString is the entry point):


<!-- recursive section of splitString... don't call directly -->
<xsl:template name="firstString">
<xsl:param name="string" />
<xsl:param name="length" />
<xsl:param name="currentpos" />
<xsl:param name="prefix" />
<xsl:param name="hangingPrefix" />
<xsl:param name="suffix" />
<xsl:param name="hangingSuffix" />
<xsl:param name="padding" />

<xsl:choose>
<xsl:when test="substring($string, $currentpos, 1) = ' '">
<xsl:value-of select="substring($string, 1, $currentpos - 1)" />
<xsl:value-of select="$suffix" />
<xsl:value-of select="$newline" />

<xsl:call-template name="splitString">
<xsl:with-param name="string" select="substring($string, $currentpos + 1)" />
<xsl:with-param name="length" select="$length" />
<xsl:with-param name="prefix" select="$hangingPrefix" />
<xsl:with-param name="hangingPrefix" select="$hangingPrefix" />
<xsl:with-param name="suffix" select="$hangingSuffix" />
<xsl:with-param name="hangingSuffix" select="$hangingSuffix" />
<xsl:with-param name="padding" select="$padding" />
</xsl:call-template>
</xsl:when>
<xsl:when test="substring($string, $currentpos, 1) = '/'">
<xsl:value-of select="substring($string, 1, $currentpos)" />
<xsl:value-of select="$suffix" />
<xsl:value-of select="$newline" />

<xsl:call-template name="splitString">
<xsl:with-param name="string" select="substring($string, $currentpos + 1)" />
<xsl:with-param name="length" select="$length" />
<xsl:with-param name="prefix" select="$hangingPrefix" />
<xsl:with-param name="hangingPrefix" select="$hangingPrefix" />
<xsl:with-param name="suffix" select="$hangingSuffix" />
<xsl:with-param name="hangingSuffix" select="$hangingSuffix" />
<xsl:with-param name="padding" select="$padding" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="firstString">
<xsl:with-param name="string" select="$string" />
<xsl:with-param name="length" select="$length" />
<xsl:with-param name="currentpos" select="$currentpos - 1" />
<xsl:with-param name="prefix" select="$prefix" />
<xsl:with-param name="hangingPrefix" select="$hangingPrefix" />
<xsl:with-param name="suffix" select="$suffix" />
<xsl:with-param name="hangingSuffix" select="$hangingSuffix" />
<xsl:with-param name="padding" select="$padding" />
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>

<!-- split a string to a specified size.
Note: there is no newline at the end of the last line to allow additions to the end of the line

string: string to split
length: max length string can be per line
prefix: optional string to prepend to the first line
hangingPrefix: optional string to prepend subsequent lines with
suffix: optional string to append to the end of the first line
hangingSuffix: optional string to append to subsequent lines
padding: optional string to pad the last line of a splt string with to allow extra text to be added after it
-->
<xsl:template name="splitString">
<xsl:param name="string" />
<xsl:param name="length" select="$maxLineLength" />
<xsl:param name="prefix" />
<xsl:param name="hangingPrefix" select="$prefix" />
<xsl:param name="suffix" />
<xsl:param name="hangingSuffix" select="$suffix" />
<xsl:param name="padding" />

<xsl:choose>
<xsl:when test="(string-length($prefix) +
string-length($string) +
string-length($suffix)) > $length">
<xsl:variable name="currentpos" select="$length - string-length($suffix)" />
<xsl:choose>
<xsl:when test="contains(substring($string, 1, $currentpos), ' ') or
contains(substring($string, 1, $currentpos), '/')">
<xsl:call-template name="firstString">
<xsl:with-param name="string" select="concat($prefix, $string)" />
<xsl:with-param name="length" select="$length" />
<xsl:with-param name="currentpos" select="$currentpos" />
<xsl:with-param name="prefix" select="$prefix" />
<xsl:with-param name="hangingPrefix" select="$hangingPrefix" />
<xsl:with-param name="suffix" select="$suffix" />
<xsl:with-param name="hangingSuffix" select="$hangingSuffix" />
<xsl:with-param name="padding" select="$padding" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<!-- no line split character within maximum line length -->
<xsl:value-of select="$prefix" />
<xsl:value-of select="substring($string, 1, $currentpos - string-length($prefix))" />
<xsl:value-of select="$suffix" />
<xsl:value-of select="$newline" />

<xsl:call-template name="splitString">
<xsl:with-param name="string" select="substring($string, $currentpos + 1 - string-length($prefix))" />
<xsl:with-param name="length" select="$length" />
<xsl:with-param name="prefix" select="$hangingPrefix" />
<xsl:with-param name="hangingPrefix" select="$hangingPrefix" />
<xsl:with-param name="suffix" select="$hangingSuffix" />
<xsl:with-param name="hangingSuffix" select="$hangingSuffix" />
<xsl:with-param name="padding" select="$padding" />
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:when>

<!-- less than $length chars left, just write them -->
<xsl:otherwise>
<xsl:value-of select="$prefix" />
<xsl:value-of select="$string" />
<xsl:value-of select="$suffix" />
<xsl:value-of select="substring($padding, 1, ($length - string-length($prefix) - string-length($string) - string-length($suffix))) " />
</xsl:otherwise>
</xsl:choose>
</xsl:template>

No comments: