I have one XML with an address like this:
<ADDRESS>SHIPPING ADDRESS 1
SHIPPING ADDRESS 2
SHIPPING ADDRESS 3</ADDRESS>
and I need to convert it to:
<ADDRESS1>SHIPPING ADDRESS 1</ADDRESS1>
<ADDRESS2>SHIPPING ADDRESS 2</ADDRESS2>
(ignore third line).
NOTE: I am using XSLT 1.0
If you only want the first two lines, you could do:
<xsl:template match="ADDRESS">
<ADDRESS1>
<xsl:value-of select="substring-before(., '
')" />
</ADDRESS1>
<ADDRESS2>
<xsl:value-of select="substring-before(substring-after(., '
'), '
')" />
</ADDRESS2>
</xsl:template>
This assumes there are at least three lines. Otherwise it gets a bit more complicated. For example, you could use:
<xsl:template match="ADDRESS">
<xsl:variable name="address" select="concat(., '
')" />
<ADDRESS1>
<xsl:value-of select="substring-before($address, '
')" />
</ADDRESS1>
<ADDRESS2>
<xsl:value-of select="substring-before(substring-after($address, '
'), '
')" />
</ADDRESS2>
</xsl:template>
to create two address lines no matter how many lines the source address has.
Here's another answer that works for any number of lines:
<xsl:template match="ADDRESS">
<xsl:call-template name="splitAddress">
<xsl:with-param name="string" select="."/>
</xsl:call-template>
</xsl:template>
<xsl:template name="splitAddress">
<xsl:param name="string"/>
<xsl:param name="AddrNo" select="1"/>
<xsl:choose>
<xsl:when test="$AddrNo = 2">
<xsl:element name="ADDRESS{$AddrNo}">
<xsl:value-of select="substring-before($string, '
')"/>
</xsl:element>
</xsl:when>
<xsl:when test="contains($string, '
')">
<xsl:element name="ADDRESS{$AddrNo}">
<xsl:value-of select="substring-before($string, '
')"/>
</xsl:element>
<xsl:call-template name="splitAddress">
<xsl:with-param name="string" select="substring-after($string, '
')"/>
<xsl:with-param name="AddrNo" select="$AddrNo + 1"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:element name="ADDRESS{$AddrNo}">
<xsl:value-of select="$string"/>
</xsl:element>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
--Edit--
I missed the requirement to stop after the second address line. I've inserted the xsl:when above to end the processing as required: the condition here can be edited as required.
Related
I want to split a string at the double quotation. Input string is as follow,
<S>Test Example "{test1}" is "{equal}" "{test2}"</S>
The xslt code that I'm using is,
<xsl:template name="SplitString">
<xsl:param name="text" select="''" />
<xsl:variable name="tag" select="substring-before(substring-after($text, '"'), '"')" />
<xsl:variable name="Remainder" select="substring-after($text, '"')" />
<xsl:choose>
<xsl:when test="$tag != ''">
<xsl:element name = "NP">
<xsl:value-of select = "$tag"/>
</xsl:element>
<!--recursive loop -->
<xsl:call-template name="SplitString">
<xsl:with-param name="text" select="$Remainder" />
</xsl:call-template>
</xsl:when>
</xsl:choose>
</xsl:template>
The output that I'm getting is as follow,
<NP>{test}</NP>
<NP>is</NP>
<NP>{equal}</NP>
<NP> </NP>
<NP>{test2}</NP>
How can avoid the creation of empty element?
the desired output would be,
<NP>{test}</NP>
<NP>is</NP>
<NP>{equal}</NP>
<NP>{test2}</NP>
Just add a condition:
<xsl:if test="normalize-space($tag)">
<xsl:element name = "NP">
<xsl:value-of select = "$tag"/>
</xsl:element>
</xsl:if>
I am doing a upper case to first character content in the xml data using the delimiters (space and hyphen) and am able to get the output correctly however this template is removing the break line tag in the table td area of xml. The output should be an xml.
eg:<text>
<table>
<tbody>
<tr>
<td>test<br />testing<br />tested</td>
</tr>
I see the code transforming as continous line without break tag in the output as below:I need to see the same br tag in xml output as in input xml however by capitalizing first letter of the word.
<text>
<table>
<tbody>
<tr>
<td>Testtestingtested</td>
</tr>
I am expecting to preserve and display the same break line tag to be in output xml even after the xslt tranformation so that the output will look correct instead of continous line
I am using the below xslt transformation:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl"
xmlns:n1="urn:hl7-org:v3" xmlns:n2="urn:hl7-org:sdtc">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match='n1:ClinicalDocument/n1:component/n1:structuredBody/n1:component/n1:section/n1:text/n1:table/n1:tbody/n1:tr'>
<xsl:copy>
<xsl:apply-templates select='n1:td'/>
</xsl:copy>
</xsl:template>
<xsl:template match="n1:td">
<xsl:copy>
<xsl:if test="./#ID">
<xsl:attribute name="ID" xml:space="default">
<xsl:value-of select="./#ID"/>
</xsl:attribute>
</xsl:if>
<xsl:call-template name="capitalize">
<xsl:with-param name="text" select="string(.)"/>
</xsl:call-template>
</xsl:copy>
<xsl:text>
</xsl:text>
</xsl:template>
<xsl:template name="capitalize">
<xsl:param name="text" />
<xsl:param name="delimiter" select = "' '"/>
<xsl:variable name="upper-case" select="'ABCDEFGHIJKLMNOPQRSTUVWXYZ'"/>
<xsl:variable name="lower-case" select="'abcdefghijklmnopqrstuvwxyz'"/>
<xsl:variable name="word" select="substring-before(concat($text, $delimiter), $delimiter)" />
<xsl:choose>
<xsl:when test="$delimiter=' '">
<!-- tokenize word by 2nd delimiter -->
<xsl:call-template name="capitalize">
<xsl:with-param name="text" select="$word"/>
<xsl:with-param name="delimiter" select="'-'"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<!-- capitalize word -->
<xsl:value-of select="translate(substring($word, 1, 1), $lower-case, $upper-case) " />
<xsl:value-of select="translate(substring($word, 2), $upper-case, $lower-case)" />
</xsl:otherwise>
</xsl:choose>
<xsl:if test="contains($text, $delimiter)">
<xsl:value-of select="$delimiter"/>
<!-- recursive call -->
<xsl:call-template name="capitalize">
<xsl:with-param name="text" select="substring-after($text, $delimiter)" />
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Can anyone check and answer?
UPDATE: Both XSLT 1.0 and 2.0 solutions
All solutions preserve your line breaks and also work if you have inline markup of the text content.
First I make the lc and uc parameters global for more minified templates:
<xsl:param name="lc" select="'abcdefghijklmnopqrstuvwxyzàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿžšœ'"/>
<xsl:param name="uc" select="'ABCDEFGHIJKLMNOPQRSTUVWXYZÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞŸŽŠŒ'"/>
XSLT 1.0 or 2.0: You need to rewrite your first template to this regardless of the following XSLT solutions:
<xsl:template match="n1:td">
<xsl:copy>
<xsl:if test="./#ID">
<xsl:attribute name="ID" xml:space="default">
<xsl:value-of select="./#ID"/>
</xsl:attribute>
</xsl:if>
<xsl:apply-templates select="node()" />
</xsl:copy>
<xsl:text>
</xsl:text>
</xsl:template>
XSLT 1.0
With XSLT 1.0 you need to combine a match template with a name template.
Match template: n1:td//text()
With this match template it becomes precisely what you request, make initial letter uppercase, and make letters following space and - uppercase:
<xsl:template match="n1:td//text()" >
<xsl:param name="text" select="." />
<xsl:param name="currentTextBlock" select="ancestor::n1:td[1]" />
<xsl:param name="isFirstTextNode">
<xsl:choose>
<xsl:when test="preceding::text()[ancestor::n1:td[generate-id(.) = generate-id($currentTextBlock)]]">false</xsl:when>
<xsl:otherwise>true</xsl:otherwise>
</xsl:choose>
</xsl:param>
<xsl:choose>
<xsl:when test="$isFirstTextNode = 'true'">
<xsl:value-of select="translate(substring($text, 1, 1), $lc, $uc)" />
<xsl:call-template name="capitalize">
<xsl:with-param name="text" select="$text" />
<xsl:with-param name="remainingText" select="substring($text,2)" />
<xsl:with-param name="index" select="2" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="capitalize">
<xsl:with-param name="text" select="$text" />
<xsl:with-param name="remainingText" select="$text" />
<xsl:with-param name="index" select="1" />
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
If you also want initial letter for each text node to be uppercase without adding space or hyphen, you can use this:
<xsl:template match="n1:td//text()" >
<xsl:param name="text" select="." />
<xsl:value-of select="translate(substring($text, 1, 1), $lc, $uc)" />
<xsl:call-template name="capitalize">
<xsl:with-param name="text" select="$text" />
<xsl:with-param name="remainingText" select="substring($text,2)" />
<xsl:with-param name="index" select="2" />
</xsl:call-template>
</xsl:template>
Name template: capitalize
This name template is can be used "as is" with both of the above match templates.
<xsl:template name="capitalize">
<xsl:param name="text" select="''" />
<xsl:param name="remainingText" select="''" />
<xsl:param name="index" select="1" />
<xsl:if test="$remainingText != ''">
<xsl:variable name="currentChar" select="substring($remainingText, 1, 1)" />
<xsl:choose>
<xsl:when test="$index = 1">
<xsl:value-of select="translate($currentChar, $uc, $lc)" />
</xsl:when>
<xsl:otherwise>
<xsl:variable name="previousChar" select="substring($text, $index - 1, 1)" />
<xsl:choose>
<xsl:when test="$previousChar = ' ' or $previousChar = '-'">
<xsl:value-of select="translate($currentChar, $lc, $uc)" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="translate($currentChar, $uc, $lc)" />
</xsl:otherwise>
</xsl:choose>
</xsl:otherwise>
</xsl:choose>
<xsl:call-template name="capitalize">
<xsl:with-param name="text" select="$text" />
<xsl:with-param name="remainingText" select="substring($remainingText, 2)" />
<xsl:with-param name="index" select="$index + 1" />
</xsl:call-template>
</xsl:if>
</xsl:template>
XSLT 2.0 (same as before):
This solution does precisely what you request, make initial letter uppercase, and make letters following space and - uppercase:
<xsl:template match="n1:td//text()" >
<xsl:param name="text" select="." />
<xsl:param name="currentTextBlock" select="ancestor::n1:td[1]" />
<xsl:param name="isFirstTextNode">
<xsl:choose>
<xsl:when test="preceding::text()[ancestor::n1:td[generate-id(.) = generate-id($currentTextBlock)]]">false</xsl:when>
<xsl:otherwise>true</xsl:otherwise>
</xsl:choose>
</xsl:param>
<xsl:for-each select="tokenize(replace(replace($text,'(.)','$1\\n'),'\\n$',''),'\\n')">
<xsl:variable name="pos" select="position()" />
<xsl:variable name="char" select="." />
<xsl:choose>
<xsl:when test="$isFirstTextNode = 'true' and $pos = 1">
<xsl:value-of select="translate($char, $lc, $uc) " />
</xsl:when>
<xsl:when test="substring($text, $pos - 1, 1) = ' ' or substring($text, $pos - 1, 1) = '-'">
<xsl:value-of select="translate($char, $lc, $uc) " />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="translate($char, $uc, $lc)" />
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:template>
If you also want initial letter for each text node to be uppercase without adding space or hyphen, you can use this:
<xsl:template match="n1:td//text()" >
<xsl:param name="text" select="." />
<xsl:param name="currentTextBlock" select="ancestor::n1:td[1]" />
<xsl:for-each select="tokenize(replace(replace($text,'(.)','$1\\n'),'\\n$',''),'\\n')">
<xsl:variable name="pos" select="position()" />
<xsl:variable name="char" select="." />
<xsl:choose>
<xsl:when test="$pos = 1">
<xsl:value-of select="translate($char, $lc, $uc) " />
</xsl:when>
<xsl:when test="substring($text, $pos - 1, 1) = ' ' or substring($text, $pos - 1, 1) = '-'">
<xsl:value-of select="translate($char, $lc, $uc) " />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="translate($char, $uc, $lc)" />
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:template>
The problem is your call on string(.), which takes the string value of the element: this strips out all markup.
You should be doing a recursive descent using xsl:apply-templates at each level to apply template rules to child nodes, all the way down to the leaf text nodes, and then your template rule for text nodes should be doing the case conversion.
Except that your capitalize logic seems to be trying to do something smart (I'm not sure what) which means it might need to look at something larger than a single text node. You might need a different rule for the first text node and for subsequent text nodes: it's hard to be sure without seeing a spec.
Another approach to this kind of problem is to do multiple passes: the first pass replaces the <br/> elements with some textual marker, e.g. "§br§", in the next pass you process the text as a string, and then finally you convert the markers back to element nodes.
With XSLT 3.0 you could do the first pass using fn:serialize() and the final pass using fn:parse-xml-fragment(); the "textual marker" would then be the actual lexical markup "<br/>" as a string of five characters. (But take care not to capitalise it!)
Below XSLT converts First character of each word to upper case.But Fails for special case (example:for input-> the 'lion ,king output is-> The 'Lion ,King). Need solution for the special case also.
<xsl:template name="Split">
<xsl:param name="pText"/>
<xsl:if test="string-length($pText)">
<xsl:variable name="vConvertedWord" select="concat(translate(substring(substring-before(concat($pText,$vSeparator),$vSeparator),1,1), $smallcase,$uppercase),substring(substring-before(concat($pText,$vSeparator),$vSeparator),2))"/>
<xsl:value-of select="$vConvertedWord"/>
<xsl:value-of select="$vSeparator"/>
<xsl:call-template name="Split">
<xsl:with-param name="pText" select="substring-after($pText,$vSeparator)"/>
</xsl:call-template>
</xsl:template>
I think what you are asking is that you may have multiple separators, not just a space, but a comma too? And also maybe you want some 'small' words not to be capitalised?
Just for my own personal challenge, the solution I came up with is to iterate over the text one character at a time, building up a 'word' until you reach a 'separator'. You can then capitalise the word you have built up, unless it happens to be a 'small' word. You then continue iterating with a new empty word to be built up.
Try this XSLT
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:variable name="smallcase">abcdefghijklmnopqrstuvwxyz</xsl:variable>
<xsl:variable name="uppercase">ABCDEFGHIJKLMNOPQRSTUVWXYZ</xsl:variable>
<xsl:variable name="vSeparator"> ,.</xsl:variable>
<xsl:variable name="vProp"> a an the and </xsl:variable>
<xsl:template match="text()">
<xsl:call-template name="Split">
<xsl:with-param name="pText" select="." />
</xsl:call-template>
</xsl:template>
<xsl:template name="Split">
<xsl:param name="pText"/>
<xsl:param name="letterPos" select="1" />
<xsl:param name="currentWord" select="''" />
<xsl:variable name="nextChar" select="translate(substring($pText, $letterPos, 1), $uppercase ,$smallcase)" />
<xsl:variable name="separatorFound" select="($nextChar = '' or contains($vSeparator, $nextChar))" />
<xsl:if test="$separatorFound">
<xsl:choose>
<xsl:when test="contains($vProp, concat(' ', $currentWord, ' ')) and $letterPos > string-length($currentWord) + 1">
<xsl:value-of select="$currentWord" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="translate(substring($currentWord, 1, 1), $smallcase ,$uppercase)" />
<xsl:value-of select="substring($currentWord, 2, string-length($currentWord) - 1)" />
</xsl:otherwise>
</xsl:choose>
<xsl:value-of select="$nextChar" />
</xsl:if>
<xsl:variable name="newWord">
<xsl:if test="not($separatorFound)"><xsl:value-of select="concat($currentWord, $nextChar)"/></xsl:if>
</xsl:variable>
<xsl:if test="$letterPos <= string-length($pText)">
<xsl:call-template name="Split">
<xsl:with-param name="pText" select="$pText" />
<xsl:with-param name="letterPos" select="$letterPos + 1" />
<xsl:with-param name="currentWord" select="$newWord" />
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
When applied to this XML
<text>The Lion king, the tiger queen and the hippo prince</text>
The following is output
The Lion King, the Tiger Queen and the Hippo Prince
EDIT - Alternate solution
If you want to capitalise letters that occur after , or ., then try this XSLT:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:variable name="smallcase">abcdefghijklmnopqrstuvwxyz</xsl:variable>
<xsl:variable name="uppercase">ABCDEFGHIJKLMNOPQRSTUVWXYZ</xsl:variable>
<xsl:variable name="vSeparator">,.</xsl:variable>
<xsl:variable name="vProp"> a an the and </xsl:variable>
<xsl:template match="text()">
<xsl:call-template name="Split">
<xsl:with-param name="pText" select="." />
</xsl:call-template>
</xsl:template>
<xsl:template name="Split">
<xsl:param name="pText"/>
<xsl:param name="letterPos" select="1" />
<xsl:param name="currentWord" select="''" />
<xsl:param name="startOfBlock" select="1" />
<xsl:variable name="nextChar" select="translate(substring($pText, $letterPos, 1), $uppercase ,$smallcase)" />
<xsl:variable name="separatorFound" select="($nextChar = '' or $nextChar = ' ' or contains($vSeparator, $nextChar))" />
<xsl:if test="$separatorFound">
<xsl:choose>
<xsl:when test="contains($vProp, concat(' ', $currentWord, ' ')) and $startOfBlock = 0">
<xsl:value-of select="$currentWord" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="translate(substring($currentWord, 1, 1), $smallcase ,$uppercase)" />
<xsl:value-of select="substring($currentWord, 2, string-length($currentWord) - 1)" />
</xsl:otherwise>
</xsl:choose>
<xsl:value-of select="$nextChar" />
</xsl:if>
<xsl:variable name="newWord">
<xsl:if test="not($separatorFound)"><xsl:value-of select="concat($currentWord, $nextChar)"/></xsl:if>
</xsl:variable>
<xsl:variable name="newStartOfBlock">
<xsl:choose>
<xsl:when test="contains($vSeparator, $nextChar)"><xsl:value-of select="1" /></xsl:when>
<xsl:when test="$nextChar = ' ' and $currentWord != ''"><xsl:value-of select="0" /></xsl:when>
<xsl:otherwise><xsl:value-of select="$startOfBlock" /></xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:if test="$letterPos <= string-length($pText)">
<xsl:call-template name="Split">
<xsl:with-param name="pText" select="$pText" />
<xsl:with-param name="letterPos" select="$letterPos + 1" />
<xsl:with-param name="currentWord" select="$newWord" />
<xsl:with-param name="startOfBlock" select="$newStartOfBlock" />
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
When applied to this XML
<text>The Lion king, the tiger queen and the hippo prince.... And cats, bats and rats.</text>
The following is output:
The Lion King, The Tiger Queen and the Hippo Prince.... And Cats, Bats and Rats.
I am creating a left trim template and I have this below template:
<xsl:template name="str:left-trim">
<xsl:param name="string" select="''"/>
<xsl:variable name="tmp" select="substring($string, 1, 1)"/>
<xsl:if test="$tmp = ' '">
<xsl:variable name="tmp2" select="substring-after($string, $tmp)"/>
<xsl:choose>
<xsl:when test="$tmp2 != ''">
<xsl:call-template name="str:left-trim">
<xsl:with-param name="string" select="$tmp2"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$tmp2"/>
</xsl:otherwise>
</xsl:choose>
</xsl:if>
<xsl:if test="$tmp != ' '">
<xsl:value-of select="$string"/>
</xsl:if>
</xsl:template>
if I pass an argument like this:
<xsl:variable name="str-test2">this is a america</xsl:variable>
then my template will work just fine but if I pass an argument like this below then, my template will fail. I think there is something wrong with the break(newline)
<xsl:variable name="str-test2">
this is a america
</xsl:variable>
do you have any suggestion?
This works for me. Note I didn't use the str: namespace plus I'm checking for leading newlines.
<xsl:template name="left-trim">
<xsl:param name="string" select="''"/>
<xsl:variable name="tmp" select="substring($string, 1, 1)"/>
<xsl:choose>
<xsl:when test="$tmp = ' ' or $tmp = '
'">
<xsl:call-template name="left-trim">
<xsl:with-param name="string" select="substring-after($string, $tmp)"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$string"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
HTH, Axel.
Here's a solution to left-trim a string in XSLT 1.0 without using a recursive template:
<xsl:value-of select="substring($str-test2, string-length(substring-before($str-test2, substring(normalize-space($str-test2), 1, 1))) + 1)"/>
I had data in XML that had line feeds, spaces, and tabs that I wanted to preserve in the output HTML (so I couldn't use <p>) but I also wanted the lines to wrap when the side of the screen was reached (so I couldn't use <pre>).
Another way of putting this is that you want to turn all pairs of spaces into two non-breaking spaces, tabs into four non-breaking spaces and all line breaks into <br> elements. In XSLT 1.0, I'd do:
<xsl:template name="replace-spaces">
<xsl:param name="text" />
<xsl:choose>
<xsl:when test="contains($text, ' ')">
<xsl:call-template name="replace-spaces">
<xsl:with-param name="text" select="substring-before($text, ' ')"/>
</xsl:call-template>
<xsl:text> </xsl:text>
<xsl:call-template name="replace-spaces">
<xsl:with-param name="text" select="substring-before($text, ' ')" />
</xsl:call-template>
</xsl:when>
<xsl:when test="contains($text, ' ')">
<xsl:call-template name="replace-spaces">
<xsl:with-param name="text" select="substring-before($text, ' ')"/>
</xsl:call-template>
<xsl:text> </xsl:text>
<xsl:call-template name="replace-spaces">
<xsl:with-param name="text" select="substring-before($text, ' ')" />
</xsl:call-template>
</xsl:when>
<xsl:when test="contains($text, '
')">
<xsl:call-template name="replace-spaces">
<xsl:with-param name="text" select="substring-before($text, '
')" />
</xsl:call-template>
<br />
<xsl:call-template name="replace-spaces">
<xsl:with-param name="text" select="substring-after($text, '
')" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$text" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
Not being able to use tail recursion is a bit of a pain, but it shouldn't be a real problem unless the text is very long.
An XSLT 2.0 solution would use <xsl:analyze-string>.
Really, I'd choose an editor which supports this correctly, rather than wrangling it through more XML.
I and a co-worker (Patricia Eromosele) came up with the following solution: (Is there a better solution?)
<p> <xsl:call-template name="prewrap"> <xsl:with-param name="text" select="text"/> </xsl:call-template> </p> <xsl:template name="prewrap"> <xsl:param name="text" select="."/> <xsl:variable name="spaceIndex" select="string-length(substring-before($text, ' '))"/> <xsl:variable name="tabIndex" select="string-length(substring-before($text, ' '))"/> <xsl:variable name="lineFeedIndex" select="string-length(substring-before($text, '
'))"/> <xsl:choose> <xsl:when test="$spaceIndex = 0 and $tabIndex = 0 and $lineFeedIndex = 0"><!-- no special characters left --> <xsl:value-of select="$text"/> </xsl:when> <xsl:when test="$spaceIndex > $tabIndex and $lineFeedIndex > $tabIndex"><!-- tab --> <xsl:value-of select="substring-before($text, ' ')"/> <xsl:text disable-output-escaping="yes"> </xsl:text> <xsl:text disable-output-escaping="yes"> </xsl:text> <xsl:text disable-output-escaping="yes"> </xsl:text> <xsl:text disable-output-escaping="yes"> </xsl:text> <xsl:call-template name="prewrap"> <xsl:with-param name="text" select="substring-after($text,' ')"/> </xsl:call-template> </xsl:when> <xsl:when test="$spaceIndex > $lineFeedIndex and $tabIndex > $lineFeedIndex"><!-- line feed --> <xsl:value-of select="substring-before($text, '
')"/> <br/> <xsl:call-template name="prewrap"> <xsl:with-param name="text" select="substring-after($text,'
')"/> </xsl:call-template> </xsl:when> <xsl:when test="$lineFeedIndex > $spaceIndex and $tabIndex > $spaceIndex"><!-- two spaces --> <xsl:value-of select="substring-before($text, ' ')"/> <xsl:text disable-output-escaping="yes"> </xsl:text> <xsl:text disable-output-escaping="yes"> </xsl:text> <xsl:call-template name="prewrap"> <xsl:with-param name="text" select="substring-after($text,' ')"/> </xsl:call-template> </xsl:when> <xsl:otherwise><!-- should never happen --> <xsl:value-of select="$text"/> </xsl:otherwise> </xsl:choose> </xsl:template>
Source: http://jamesjava.blogspot.com/2008/06/xsl-preserving-line-feeds-tabs-and.html
Not sure if this relevant, but isn't there a preservespace attribute and whatnot for xml?