Split string function causes a stack overflow
I have a function to split a sting into lines that are 76 characters in length or less. The input to this function is binary data, and I suppose due to the length of the binary data I often get a "stack overflow" error. Any way to prevent this, or is 开发者_运维知识库there a better way to split a string? This needs to be done using XSL 1.0.
<xsl:template name="splitBinaryData">
<xsl:param name="txt"/>
<xsl:param name="width"/>
<xsl:choose>
<xsl:when test="string-length($txt) > 76 ">
<xsl:value-of select="substring($txt, 1, 76)"/><xsl:text> </xsl:text>
<xsl:call-template name="splitBinaryData">
<xsl:with-param select="substring($txt, 77)" name="txt"/>
<xsl:with-param select="$width" name="width"/>
</xsl:call-template>
</xsl:when>
<xsl:when test="string-length($txt) < 76 or string-length($txt) = 76">
<xsl:value-of select="$txt"/>
</xsl:when>
</xsl:choose>
</xsl:template>
Thanks in advance.
One possibility might be to translate the algorithm to be tail recursive and hope your xslt processor recognizes the pattern and turns it into a loop. Except for saxon, I could not find any information which other xslt processors support tail recursion. The transformation works by introducing an accumulator variable that contains the splitted text. The call-template
instruction will then be the last operation the template has to do and can be turned into the equivalent of a goto without consuming any stack.
<xsl:template name="splitBinaryData">
<xsl:param name="txt"/>
<xsl:param name="width"/>
<xsl:param name="accum"/>
<xsl:choose>
<xsl:when test="string-length($txt) > 76 ">
<xsl:call-template name="splitBinaryData">
<xsl:with-param select="substring($txt, 77)" name="txt"/>
<xsl:with-param select="$width" name="width"/>
<xsl:with-param select="concat($accum, substring($txt, 1, 76), ' ')" name="accum"/>
</xsl:call-template>
</xsl:when>
<xsl:when test="string-length($txt) < 76 or string-length($txt) = 76">
<xsl:value-of select="concat($accum, $txt)"/>
</xsl:when>
</xsl:choose>
</xsl:template>
Edit: Another option would be to apply a divide and conquer type of algorithm. This splits the problem into two sub-problems of about equal size and then combines their solutions. The required stack depth is greatly reduced and grows logarithmically instead of linearly regarding to the input size. The trick here is to make the first sub-string size be a multiple of 76 characters to avoid inserting additional newlines.
<xsl:template name="splitBinaryData">
<xsl:param name="txt"/>
<xsl:param name="width"/>
<xsl:variable name="len" select="string-length($txt)" />
<xsl:choose>
<xsl:when test="$len > 76 ">
<!-- process the text in two parts of about the same size,
the length of the first part should be a multiple of
the needed width -->
<xsl:variable name="idx" select="ceiling($len div 76 div 2) * 76" />
<xsl:call-template name="splitBinaryData">
<xsl:with-param select="substring($txt, 1, $idx)" name="txt"/>
<xsl:with-param select="$width" name="width"/>
</xsl:call-template>
<xsl:text> </xsl:text>
<xsl:call-template name="splitBinaryData">
<xsl:with-param select="substring($txt, $idx+1)" name="txt"/>
<xsl:with-param select="$width" name="width"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$txt" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
Use "Divide and Conquer" pattern like:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/" name="splitBinaryData">
<xsl:param name="txt" select="string()"/>
<xsl:param name="width" select="5"/>
<xsl:param name="length" select="string-length()"/>
<xsl:choose>
<xsl:when test="$length > $width">
<xsl:variable name="split"
select="ceiling($length div $width div 2) * $width"/>
<xsl:call-template name="splitBinaryData">
<xsl:with-param name="txt"
select="substring($txt, 1, $split)"/>
<xsl:with-param name="width" select="$width"/>
<xsl:with-param name="length" select="$split"/>
</xsl:call-template>
<xsl:call-template name="splitBinaryData">
<xsl:with-param name="txt"
select="substring($txt, $split + 1)"/>
<xsl:with-param name="width" select="$width"/>
<xsl:with-param name="length" select="$length - $split"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="concat($txt, '
')"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Note: MSXSL 4 splits XSLT 2.0 spec in XHTML format (1.4 MB) in 2 seconds on my machine.
精彩评论