Reputation: 51
I'm using XSLT to parse the textual content of an XML element. This text contains newlines, but I can't seem to parse them correctly. I'm using code I found online to chop up the text. Here's the relevant part of the code.
<xsl:variable name="first">
<xsl:value-of select="substring-before($source, $newline)"/>
</xsl:variable>
<xsl:variable name="rest">
<xsl:value-of select="substring-after($source, $newline)"/>
</xsl:variable>
This is part of a recusrive template that pushes $rest into itself.
The problem is that the code sample doesn't define $newline.
If I set $newline to a letter, like 's', the text gets split up just fine (e.g. it will turn the input "resounding" into "re" and "ounding"). But when I try to set $newline to the newline character, that is 

or  
, it recurses forever and gives me a stack overflow. I also tried to define an ENTITY for newline but it makes no difference.
The input has ordinary CR/LF at the end of each line (I'm on a Windows box).
What am I doing wrong?
Upvotes: 3
Views: 11199
Reputation: 1796
I have used this template once. It is a named template so you can call it where ever you need it. The text here is split up in 70 character pieces:
<xsl:template name="Texts">
<xsl:param name="string" select="TEXTITEM" />
<xsl:param name="line-length" select="70"/>
<xsl:variable name="line" select="substring($string,1,$line-length)"/>
<xsl:variable name="rest" select="substring($string, $line-length+1)"/>
<xsl:if test="$line">
<MYTEXT>
<xsl:value-of select="$line"/>
</MYTEXT>
</xsl:if>
<xsl:if test="$rest">
<xsl:call-template name="Texts">
<xsl:with-param name="string" select="$rest"/>
<xsl:with-param name="line-length" select="$line-length"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
Upvotes: 2
Reputation: 1614
Thought I'd add a line splitting code that adds newlines after white space.
<xsl:function name="kode:splitLongLine">
<xsl:param name="string"/>
<xsl:variable name="regex">
<xsl:text>(((.){1,55})( |$))</xsl:text>
</xsl:variable>
<xsl:variable name="result">
<xsl:analyze-string select="$string" regex="{$regex}">
<xsl:matching-substring>
<xsl:value-of select="concat(regex-group(1),' ')"/>
</xsl:matching-substring>
<xsl:non-matching-substring>
<xsl:value-of select="concat('REPORT ERROR: ', .)"/>
</xsl:non-matching-substring>
</xsl:analyze-string>
</xsl:variable>
<xsl:sequence select="$result"/>
</xsl:function>
Upvotes: 1
Reputation: 51
Maestro13's answer brought me closest, and I ended up merging the template I had with his, to produce this, which I share here for future generations. It's a template that returns the length of the longest line in the string you pass to it.
<xsl:template name="longestCodeLine">
<xsl:param name="str"/>
<xsl:choose>
<!-- Is this the last line? -->
<xsl:when test="contains($str, '
')">
<!-- No. First isolate all remaining lines, and recurse to find its longest line. -->
<xsl:variable name="bestOfTheRest">
<xsl:call-template name="longestCodeLine">
<xsl:with-param name="str" select="substring-after($str, '
')"/>
</xsl:call-template>
</xsl:variable>
<xsl:choose>
<!-- Compare the longest of the remaining lines to this one. Which one's longer? -->
<!-- If the longest of the remaining lines is longer, return that line. -->
<xsl:when test="string-length($bestOfTheRest) > string-length(substring-before($str, '
'))">
<xsl:value-of select="$bestOfTheRest"/>
</xsl:when>
<!-- If this line longer, return this line. -->
<xsl:otherwise>
<xsl:value-of select="substring-before($str, '
')"/>
</xsl:otherwise>
</xsl:choose>
</xsl:when>
<!-- If there are no \n's left, this is your last string. So it is by definition the longest one left. -->
<xsl:otherwise>
<xsl:value-of select="$str"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
Upvotes: 2
Reputation: 3696
You may be able to use the below.
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
<root>
<xsl:for-each select="root/str">
<str>
<xsl:call-template name="strSplit">
<xsl:with-param name="str" select="."/>
<xsl:with-param name="seqno" select="1"/>
</xsl:call-template>
</str>
</xsl:for-each>
</root>
</xsl:template>
<xsl:template name="strSplit">
<xsl:param name="str"/>
<xsl:param name="seqno"/>
<xsl:variable name="afterLeadingWS"
select="substring-after($str, substring-before($str,substring-before(normalize-space($str), ' ')))"/>
<xsl:choose>
<xsl:when test="contains($afterLeadingWS, '
')">
<line>
<xsl:attribute name="seqno"><xsl:value-of select="$seqno"/></xsl:attribute>
<xsl:attribute name="length"><xsl:value-of select="string-length(substring-before($afterLeadingWS, '
'))"/></xsl:attribute>
<xsl:value-of select="substring-before($afterLeadingWS, '
')"/>
</line>
<xsl:call-template name="strSplit">
<xsl:with-param name="str" select="substring-after($afterLeadingWS, '
')"/>
<xsl:with-param name="seqno" select="$seqno + 1"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<line>
<xsl:attribute name="seqno"><xsl:value-of select="$seqno"/></xsl:attribute>
<xsl:value-of select="$afterLeadingWS"/>
</line>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Applied to
<?xml version="1.0" encoding="UTF-8"?>
<root>
<str>
yigifgniuq h
eukwgf kuew hgk.uhgku
,/v.,silghouihhg
</str>
<str>
09734ymmnyr n.0808
o149013483ymr7rg
738924m c0
</str>
</root>
the output result is
<?xml version="1.0" encoding="UTF-8"?>
<root>
<str>
<line seqno="1" length="13">yigifgniuq h </line>
<line seqno="2" length="21">eukwgf kuew hgk.uhgku</line>
<line seqno="3" length="18"> ,/v.,silghouihhg</line>
<line seqno="4"> </line>
</str>
<str>
<line seqno="1" length="18">09734ymmnyr n.0808</line>
<line seqno="2" length="16">o149013483ymr7rg</line>
<line seqno="3" length="11">738924m c0 </line>
<line seqno="4" length="2"> </line>
<line seqno="5"> </line>
</str>
</root>
Note that leading tabs (or blanks) are seen as part of lines.
Upvotes: 2
Reputation: 4809
If you can use EXSLT try with str:tokenize
<xsl:for-each select="str:tokenize($source, $newline)">
<xsl:value-of select="."/>
<xsl:text>
</xsl:text>
</xsl:for-each>
Or similarly with XSLT 2.0:
<xsl:for-each select="tokenize($source, $newline)">
<xsl:sequence select="."/>
<xsl:text>
</xsl:text>
</xsl:for-each>
Upvotes: 4