Given the following XML:
<application>
<documentation>Before text.
[This|http://google.com] is a link.
Click [here|http://google.com] for another link.
After text.
</documentation>
</application>
I want to replace new lines with <br/> tags and create links. The outcome should produce the following HTML:
Before text.<br/>
This is a link.<br/>
Click here for another link.<br/>
After text.
I have the XSLT below with the appropriate templates. My problem is that the <br/> elements added by the first function are stripped out by the second function. I've tried using an identity transform, but I can't wrap my head around it.
Any help would be appreciated.
My current XSLT:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" version="1.0"/>
<xsl:template match="application">
<p>
<xsl:variable name="with_new_lines">
<xsl:call-template name="replace-newlines">
<xsl:with-param name="text" select="documentation"/>
<xsl:with-param name="replace" select="'
'"/>
<xsl:with-param name="by" select="'new_line'"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="with_links">
<xsl:call-template name="create-links">
<xsl:with-param name="text">
<xsl:copy-of select="$with_new_lines"/>
</xsl:with-param>
</xsl:call-template>
</xsl:variable>
<xsl:copy-of select="$with_links"/>
</p>
</xsl:template>
<xsl:template name="replace-newlines">
<xsl:param name="text"/>
<xsl:choose>
<xsl:when test="contains($text, '
')">
<xsl:copy-of select="substring-before($text,'
')"/>
<br/>
<xsl:call-template name="replace-newlines">
<xsl:with-param name="text" select="substring-after($text,'
')"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:copy-of select="$text"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!-- Changing [here|http://a.com] to hereand
[http://a.com] to http://a.com-->
<xsl:template match="*" name="create-links">
<xsl:param name="text" select="."/>
<xsl:choose>
<xsl:when test="contains($text, 'http')">
<xsl:copy-of select="substring-before($text,'[')"/>
<xsl:variable name="the_link_and_after">
<xsl:copy-of select="substring-after($text,'[')"/>
</xsl:variable>
<xsl:variable name="the_link">
<xsl:copy-of select="substring-before($the_link_and_after,']')"/>
</xsl:variable>
<xsl:choose>
<xsl:when test="contains($the_link,'|')">
<xsl:variable name="the_url">
<xsl:copy-of select="substring-after($the_link,'|')"/>
</xsl:variable>
<a href="{$the_url}">
<xsl:copy-of select="substring-before($the_link,'|')"/>
</a>
</xsl:when>
<xsl:otherwise>
<a href="{$the_link}">
<xsl:copy-of select="$the_link"/>
</a>
</xsl:otherwise>
</xsl:choose>
<!--<xsl:copy-of select="substring-after($text,']')" />-->
<xsl:call-template name="create-links">
<xsl:with-param name="text" select="substring-after($text,']')"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:copy-of select="$text"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
The problem with your approach is that each of your named templates creates a result-tree-fragment, containing a mixture of text nodes and elements (either <br> or <a>). When you send the result to be processed as a string by the other template, only the text nodes survive the treatment.
I would suggest you start by tokenizing the input into lines as elements. Then send each line in turn to be processed by the other template:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
extension-element-prefixes="exsl">
<xsl:output method="html" encoding="utf-8"/>
<xsl:template match="application">
<xsl:variable name="lines">
<xsl:call-template name="tokenize-lines">
<xsl:with-param name="text" select="documentation"/>
</xsl:call-template>
</xsl:variable>
<p>
<xsl:for-each select="exsl:node-set($lines)/line">
<xsl:call-template name="create-links">
<xsl:with-param name="text" select="."/>
</xsl:call-template>
<br/>
</xsl:for-each>
</p>
</xsl:template>
<xsl:template name="tokenize-lines">
<xsl:param name="text"/>
<xsl:choose>
<xsl:when test="contains($text, '
')">
<line><xsl:copy-of select="substring-before($text,'
')"/></line>
<xsl:call-template name="tokenize-lines">
<xsl:with-param name="text" select="substring-after($text,'
')"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<line><xsl:copy-of select="$text"/></line>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="create-links">
<xsl:param name="text"/>
<xsl:choose>
<xsl:when test="contains($text, 'http')">
<xsl:copy-of select="substring-before($text,'[')"/>
<xsl:variable name="the_link_and_after">
<xsl:copy-of select="substring-after($text,'[')"/>
</xsl:variable>
<xsl:variable name="the_link">
<xsl:copy-of select="substring-before($the_link_and_after,']')"/>
</xsl:variable>
<xsl:choose>
<xsl:when test="contains($the_link,'|')">
<xsl:variable name="the_url">
<xsl:copy-of select="substring-after($the_link,'|')"/>
</xsl:variable>
<a href="{$the_url}">
<xsl:copy-of select="substring-before($the_link,'|')"/>
</a>
</xsl:when>
<xsl:otherwise>
<a href="{$the_link}">
<xsl:copy-of select="$the_link"/>
</a>
</xsl:otherwise>
</xsl:choose>
<!--<xsl:copy-of select="substring-after($text,']')" />-->
<xsl:call-template name="create-links">
<xsl:with-param name="text" select="substring-after($text,']')"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:copy-of select="$text"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Alternatively, you could call the 2nd template from within the 1st one, for each line separately before returning it:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" encoding="utf-8"/>
<xsl:template match="application">
<p>
<xsl:call-template name="create-lines">
<xsl:with-param name="text" select="documentation"/>
</xsl:call-template>
</p>
</xsl:template>
<xsl:template name="create-lines">
<xsl:param name="text"/>
<xsl:choose>
<xsl:when test="contains($text, '
')">
<xsl:call-template name="create-links">
<xsl:with-param name="text" select="substring-before($text,'
')"/>
</xsl:call-template>
<br/>
<xsl:call-template name="create-lines">
<xsl:with-param name="text" select="substring-after($text,'
')"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="create-links">
<xsl:with-param name="text" select="$text"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="create-links">
<xsl:param name="text"/>
<xsl:choose>
<xsl:when test="contains($text, 'http')">
<xsl:copy-of select="substring-before($text,'[')"/>
<xsl:variable name="the_link_and_after">
<xsl:copy-of select="substring-after($text,'[')"/>
</xsl:variable>
<xsl:variable name="the_link">
<xsl:copy-of select="substring-before($the_link_and_after,']')"/>
</xsl:variable>
<xsl:choose>
<xsl:when test="contains($the_link,'|')">
<xsl:variable name="the_url">
<xsl:copy-of select="substring-after($the_link,'|')"/>
</xsl:variable>
<a href="{$the_url}">
<xsl:copy-of select="substring-before($the_link,'|')"/>
</a>
</xsl:when>
<xsl:otherwise>
<a href="{$the_link}">
<xsl:copy-of select="$the_link"/>
</a>
</xsl:otherwise>
</xsl:choose>
<!--<xsl:copy-of select="substring-after($text,']')" />-->
<xsl:call-template name="create-links">
<xsl:with-param name="text" select="substring-after($text,']')"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:copy-of select="$text"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Related
how to split a node value in XSLT 1.0?
<mark>1,2</mark>
i need to perform some operations in the for loop with each value of the output of split.
<xsl:for-each select="">
</xsl:for-each>
How to do this?
I. XSLT 1.0 solution:
Here is one way to do this in XSLT 1.0 using only the xxx:node-set() extension function:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="http://exslt.org/common" exclude-result-prefixes="ext">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="mark">
<xsl:variable name="vrtfSplit">
<xsl:apply-templates/>
</xsl:variable>
<xsl:for-each select="ext:node-set($vrtfSplit)/*">
<processedItem>
<xsl:value-of select="10 * ."/>
</processedItem>
</xsl:for-each>
</xsl:template>
<xsl:template match="text()" name="split">
<xsl:param name="pText" select="."/>
<xsl:if test="string-length($pText) >0">
<item>
<xsl:value-of select=
"substring-before(concat($pText, ','), ',')"/>
</item>
<xsl:call-template name="split">
<xsl:with-param name="pText" select=
"substring-after($pText, ',')"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
when this transformation is applied to the following XML document:
<mark>1,2,3,4,5</mark>
The wanted, correct output (each item multiplied by 10) is produced:
<processedItem>10</processedItem>
<processedItem>20</processedItem>
<processedItem>30</processedItem>
<processedItem>40</processedItem>
<processedItem>50</processedItem>
II. XSLT 2.0 solution:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="mark">
<xsl:for-each select="tokenize(., ',')">
<processedItem>
<xsl:sequence select="10*xs:integer(.)"/>
</processedItem>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
The explaination by Dimitre Novatchev is awesome, but we can also do it in much more simpler way without using node-set() function have a look:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:variable name="delimiter">
<xsl:text>,</xsl:text>
</xsl:variable>
<xsl:template match="mark">
<xsl:variable name="dataList">
<xsl:value-of select="."/>
</xsl:variable>
<xsl:call-template name="processingTemplate">
<xsl:with-param name="datalist" select="$dataList"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="processingTemplate">
<xsl:param name="datalist"/>
<xsl:choose>
<xsl:when test="contains($datalist,$delimiter) ">
<xsl:element name="processedItem">
<xsl:value-of select="substring-before($datalist,$delimiter) * 10"/>
</xsl:element>
<xsl:call-template name="processingTemplate">
<xsl:with-param name="datalist" select="substring-after($datalist,$delimiter)"/>
</xsl:call-template>
</xsl:when>
<xsl:when test="string-length($datalist)=1">
<xsl:element name="processedItem">
<xsl:value-of select="$datalist * 10"/>
</xsl:element>
</xsl:when>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
In 1.0 you need to write a recursive template - except you don't, because it's already been written. Download the str:tokenize template from http://www.exslt.org.
If you can use exslt there's a tokenize() function that will do this nicely.
node-set str:tokenize(string, string?)
See http://www.exslt.org/str/functions/tokenize/
This code will split a delimited string in XSLT 1.0
(It will work for 2.0, but don't use the node-set.)
It will also optionally suppress empty elements in the string
or optionally upper case the elements.
<!-- Example delimited string. -->
<xsl:variable name="delimitedString" select="'a, b, c, , , d, e, f, g'"/>
<!-- Create a node set where each node contains one of the elements from the
delimited string. -->
<xsl:variable name="splitNodes">
<xsl:call-template name="getNodeListFromDelimitedList">
<xsl:with-param name="inStrList" select="$delimitedString"/>
<xsl:with-param name="delimiter" select="','"/>
<xsl:with-param name="suppressEmptyElements" select="false()"/>
<xsl:with-param name="upperCase" select="false()"/>
<xsl:with-param name="allTrim" select="false()"/>
</xsl:call-template>
</xsl:variable>
<!-- Use this for XSLT 1.0 only. -->
<xsl:variable name="splitNodesList" select="msxml:node-set($splitNodes)"/>
<!-- Use the split node list to do something. For example, create a string like
the delimited string, but without the delimiters. -->
<xsl:variable name="nonDelimitedString">
<xsl:for-each select="$splitNodesList/element">
<xsl:value-of select="."/>
</xsl:for-each>
</xsl:variable>
<!-- Do something with the nonDelimitedString. -->
<!--
*****************************************************************************************
This template converts a delimited string list to a node list as follows:
Each value in the delimited input string is extracted from the string. Then, a node is
created to contain the value. The name of the node is 'element', and it is added to the
list. To use this template, create an variable and call this template from within the variable.
If you are using XSLT version 1.0, convert the node list to a node set using the node-set
function. You can access the element as follows: $SomeVariableNodeSet/element
*****************************************************************************************
-->
<xsl:template name="getNodeListFromDelimitedList">
<!-- Delimited string with one or more delimiters. -->
<xsl:param name="inStrList"/>
<!-- The delimiter. -->
<xsl:param name="delimiter" select="'|'"/>
<!-- Set to true to suppress empty elements from being added to node list. Otherwise, set to 'false'.-->
<xsl:param name="suppressEmptyElements" select="true()"/>
<!-- Set to true to upper case the strings added to the node list. -->
<xsl:param name="upperCase" select="false()"/>
<!-- Set to true to left trim and right trim the strings added to the nodes list. -->
<xsl:param name="allTrim" select="false()"/>
<xsl:variable name="element">
<xsl:choose>
<xsl:when test="contains($inStrList,$delimiter)">
<xsl:value-of select="substring-before($inStrList,$delimiter)"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$inStrList"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<!-- Write out the element based on parameters. -->
<xsl:if test="not($suppressEmptyElements) or normalize-space($element) != ''">
<!-- Put the element in the list. -->
<xsl:element name="element">
<xsl:choose>
<xsl:when test="$allTrim">
<xsl:call-template name="all-trim">
<xsl:with-param name="inStr" select="$element"/>
<xsl:with-param name="upperCase" select="$upperCase"/>
</xsl:call-template>
</xsl:when>
<xsl:when test="$upperCase">
<xsl:value-of select="translate($element, 'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ')"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$element"/>
</xsl:otherwise>
</xsl:choose>
</xsl:element>
</xsl:if>
<xsl:if test="contains($inStrList,$delimiter)">
<!-- Call template recursively to process the next element. -->
<xsl:call-template name="getNodeListFromDelimitedList">
<xsl:with-param name="inStrList" select="substring-after($inStrList,$delimiter)"/>
<xsl:with-param name="delimiter" select="$delimiter"/>
<xsl:with-param name="suppressEmptyElements" select="$suppressEmptyElements"/>
<xsl:with-param name="upperCase" select="$upperCase"/>
<xsl:with-param name="allTrim" select="$allTrim"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
<!--
*****************************************************************************************
This template trims the blanks from the left and right sides of a string.
*****************************************************************************************
-->
<xsl:template name="all-trim">
<!-- The string that you want to all trim. -->
<xsl:param name="inStr"/>
<xsl:param name="upperCase" select="false()"/>
<xsl:variable name="leftTrimmed">
<xsl:call-template name="left-trim">
<xsl:with-param name="inStr" select="$inStr"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="rightTrimmed">
<xsl:call-template name="right-trim">
<xsl:with-param name="inStr" select="$leftTrimmed"/>
</xsl:call-template>
</xsl:variable>
<xsl:choose>
<xsl:when test="$upperCase">
<xsl:value-of select="translate($rightTrimmed, 'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ')"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$rightTrimmed"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!--
*****************************************************************************************
This template trims the blanks from the left side of a string.
*****************************************************************************************
-->
<xsl:template name="left-trim">
<!-- The string you want to left trim. -->
<xsl:param name ="inStr"/>
<xsl:choose>
<xsl:when test="$inStr!=''">
<xsl:variable name="temp" select="substring($inStr, 1, 1)"/>
<xsl:choose>
<xsl:when test="$temp=' '">
<xsl:choose>
<xsl:when test="string-length($inStr) > 1">
<xsl:call-template name="left-trim">
<xsl:with-param name="inStr" select="substring($inStr, 2, string-length($inStr)-1)"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="''"/>
</xsl:otherwise>
</xsl:choose>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$inStr"/>
</xsl:otherwise>
</xsl:choose>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="''"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!--
*****************************************************************************************
This template trims the blanks from the right side of a string.
*****************************************************************************************
-->
<xsl:template name="right-trim">
<!-- The string you want to right trim. -->
<xsl:param name ="inStr"/>
<xsl:choose>
<xsl:when test="$inStr!=''">
<xsl:variable name="temp" select="substring($inStr, string-length($inStr), 1)"/>
<xsl:choose>
<xsl:when test="$temp=' '">
<xsl:choose>
<xsl:when test="string-length($inStr) > 1">
<xsl:call-template name="right-trim">
<xsl:with-param name="inStr" select="substring($inStr, 1, string-length($inStr)-1)"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="''"/>
</xsl:otherwise>
</xsl:choose>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$inStr"/>
</xsl:otherwise>
</xsl:choose>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="''"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
Based on #Abhinav solution I just simplified recursive solution to work with general strings. My input string which I need to split is "GEN_EME2_G9_3311|A55;GEN_EME2_G9_3312|A55;foooo_3312|A42"
<xsl:variable name="delimiter">
<xsl:text>;</xsl:text>
</xsl:variable>
<xsl:template name="fooTemplate">
...
<xsl:choose>
<xsl:when test="$conditionlink != ''">
<xsl:call-template name="processconditionlinktemplate">
<xsl:with-param name="datalist" select="$conditionlink"/>
</xsl:call-template>
</xsl:when>
</xsl:choose>
...
</xsl:template>
<xsl:template name="processconditionlinktemplate">
<xsl:param name="datalist"/>
<xsl:choose>
<xsl:when test="contains($datalist,$delimiter)">
<xsl:element name="processedItem">
<xsl:value-of select="substring-before($datalist,$delimiter)"/>
</xsl:element>
<xsl:call-template name="processconditionlinktemplate">
<xsl:with-param name="datalist" select="substring-after($datalist,$delimiter)"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:element name="processedItem">
<xsl:value-of select="$datalist"/>
</xsl:element>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
Hi i'm having the below xml.
<primaryie>
<content-style font-style="bold">Administration</content-style>
</primaryie>
and when i'm applying the below xslt it is working fine.(the content-style part)
<xsl:template match="primaryie">
<div class="primaryie">
<xsl:apply-templates select="content-style"/>
<xsl:if test="contains(current()/text(), '.')">
<xsl:variable name="numberString" select="substring(current()/text(), string-length(substring-before(current()/text(),'.'))-1)"></xsl:variable>
<xsl:call-template name="numbersToLink">
<xsl:with-param name="numbersString" select="$numberString"></xsl:with-param>
</xsl:call-template>
</xsl:if>
</div>
</xsl:template>
<xsl:template name="numbersToLink">
<xsl:param name="numbersString"></xsl:param>
<xsl:choose>
<xsl:when test="contains($numbersString, ',')">
<xsl:call-template name="splitByComma">
<xsl:with-param name="numString" select="$numbersString"/>
</xsl:call-template>
</xsl:when>
<xsl:when test="contains($numbersString, '-')">
<xsl:call-template name="splitByHyphen">
<xsl:with-param name="numString" select="$numbersString"/>
</xsl:call-template>
</xsl:when>
<xsl:when test="5 >= string-length(normalize-space($numbersString))">
<xsl:variable name="x">
<xsl:value-of select="substring-after($numbersString,'.')"></xsl:value-of>
</xsl:variable>
<xsl:variable name="y">
<xsl:value-of select="normalize-space(substring-before($numbersString,'.'))"></xsl:value-of>
</xsl:variable>
<xsl:variable name="conca">
<xsl:value-of select="concat('er:#BVI_CH_0',$y,'/P',$y,'-',$x)"/>
</xsl:variable>
<a href="{$conca}">
<xsl:value-of select="$numbersString"/>
</a>
</xsl:when>
<xsl:otherwise>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="splitByComma">
<xsl:param name="numString"></xsl:param>
<xsl:choose>
<xsl:when test="contains(substring-before($numString,','), '-')">
<xsl:call-template name="splitByHyphen">
<xsl:with-param name="numString" select="$numString"/>
</xsl:call-template>
</xsl:when>
<xsl:when test="contains($numString,',')">
<xsl:variable name="abc">
<xsl:value-of select="normalize-space(substring-before($numString,'.'))"/>
</xsl:variable>
<xsl:variable name="def">
<xsl:value-of select="substring-before(substring-after($numString,'.'),',') "/>
</xsl:variable>
<xsl:variable name="conct">
<xsl:value-of select="concat('er:#BVI_CH_0',$abc,'/P',$abc,'-',$def)"/>
<!--"concat(concat('er:#BVI_CH_0',,'/P',$y,'-',$x)"/-->
</xsl:variable>
<a href="{$conct}">
<xsl:value-of select="substring-before($numString,',')"/>
</a>
<xsl:text>, </xsl:text>
<xsl:if test="contains(substring-after($numString,','), '.')">
<xsl:call-template name="numbersToLink">
<xsl:with-param name="numbersString" select="normalize-space(substring-after($numString,','))"/>
</xsl:call-template>
</xsl:if>
</xsl:when>
</xsl:choose>
</xsl:template>
<xsl:template name="splitByHyphen">
<xsl:param name="numString"></xsl:param>
<xsl:choose>
<xsl:when test="contains($numString,'-')">
<xsl:variable name="abc">
<xsl:value-of select="normalize-space(substring-before($numString,'.'))"/>
</xsl:variable>
<xsl:variable name="def">
<xsl:value-of select="substring-before(substring-after($numString,'.'),'-') "/>
</xsl:variable>
<xsl:variable name="conct">
<xsl:value-of select="concat('er:#BVI_CH_0',$abc,'/P',$abc,'-',$def)"/>
<!--"concat(concat('er:#BVI_CH_0',,'/P',$y,'-',$x)"/-->
</xsl:variable>
<a href="{$conct}">
<!--<xsl:value-of select="substring-before($numString,'-')"/>-->
<xsl:value-of select="substring-before($numString,'-')"/>
</a>
<xsl:text>–</xsl:text>
<xsl:if test="contains(substring-after($numString,'-'), '.')">
<xsl:call-template name="numbersToLink">
<xsl:with-param name="numbersString" select="normalize-space(substring-after($numString,'-'))"/>
</xsl:call-template>
</xsl:if>
</xsl:when>
</xsl:choose>
</xsl:template>
but i have a special case in the same document as below xml states.
<primaryie>
<content-style font-style="bold">VIRRGIN system</content-style> 7.204, 7.205
</primaryie>
here when i'm applying the above template it is working with content-style, but i want it to work also with numberstolink template.
the outputs are as below.
current:
<div class="primaryie"><span class="font-style-bold">Virgin Islands Special Trust Act (VISTA) 9.077</span></div>
expexted:
<div class="primaryie"><span class="font-style-bold">Virgin Islands Special Trust Act (VISTA) 9.077</span></div>
Please change this below portion in your code
<xsl:template match="primaryie">
<div class="primaryie">
<xsl:apply-templates select="content-style"/>
<xsl:if test="contains(current(), '.')">
<xsl:variable name="numberString" select="substring(current(), string-length(substring-before(current(),'.'))-1)"></xsl:variable>
<xsl:call-template name="numbersToLink">
<xsl:with-param name="numbersString" select="$numberString"></xsl:with-param>
</xsl:call-template>
</xsl:if>
</div>
</xsl:template>
Use only Current() instead of Current()/Text()
Output for give XML is
<div class="primaryie">VIRRGIN system 7.204, 7.205</div>
My question is How to un-escape xml that has already been escaped.
I tried the code provided by Tomalak in response to How to unescape XML characters with help of XSLT?, but I can't get that to do what I want.
I have SoapMsg Xml. The body contains a few elements one of which is a String. This string
contains Escaped XML. This is often done in RPC SoapMsg because they don't allow complex
types. To Get around this they embed Escaped-Xml inside a String Element, see sXmlParameters in the input below.
Example Input:
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:pan="http://wsdl.somebody.com/Stuff/">
<soap:Header />
<soap:Body>
<pan:SomeCommand>
<first>eefbb52a0fee443cbda838caffbc2654</first>
<second>f26eb2f5dabc457ca045e64585f7b185</second>
<sXmlParameters><PARAMETERS><TIMEOUTDATETIME>2011-03-15
2:09:48.997</TIMEOUTDATETIME></PARAMETERS></sXmlParameters>
</pan:SomeCommand>
</soap:Body>
</soap:Envelope>
I also see this data escaped with <![CDATA[>]]>, I need to un-escape it also.
Converted Output:
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:pan="http://wsdl.somebody.com/Stuff/">
<soap:Header />
<soap:Body>
<pan:SomeCommand>
<first>eefbb52a0fee443cbda838caffbc2654</first>
<second>f26eb2f5dabc457ca045e64585f7b185</second>
<sXmlParameters>
<PARAMETERS>
<TIMEOUTDATETIME>2011-03-15 2:09:48.997</TIMEOUTDATETIME>
</PARAMETERS>
</sXmlParameters>
</pan:SomeCommand>
</soap:Body>
</soap:Envelope>
This will already take care of half of your problem – not the CDATA part:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="//sXmlParameters">
<xsl:copy>
<xsl:call-template name="unescape">
<xsl:with-param name="escaped" select="string(.)"/>
</xsl:call-template>
</xsl:copy>
</xsl:template>
<xsl:template name="unescape">
<xsl:param name="escaped"/>
<xsl:choose>
<xsl:when test="contains($escaped,'<')">
<xsl:variable name="beforeelem" select="substring-before($escaped,'<')"/>
<xsl:variable name="elemname1" select="substring-before(substring-after($escaped,'<'),' ')"/>
<xsl:variable name="elemname2" select="substring-before(substring-after($escaped,'<'),'>')"/>
<xsl:variable name="elemname3" select="substring-before(substring-after($escaped,'<'),'/>')"/>
<xsl:variable name="hasattributes" select="string-length($elemname1) > 0 and ((string-length($elemname2)=0 or string-length($elemname1) < string-length($elemname2)) and (string-length($elemname3)=0 or string-length($elemname1) < string-length($elemname3)))"/>
<xsl:variable name="elemclosed" select="string-length($elemname3) > 0 and (string-length($elemname2)=0 or string-length($elemname3) < string-length($elemname2))"/>
<xsl:variable name="elemname">
<xsl:choose>
<xsl:when test="$hasattributes">
<xsl:value-of select="$elemname1"/>
</xsl:when>
<xsl:when test="not($elemclosed)">
<xsl:value-of select="$elemname2"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$elemname3"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable name="elemclosetag" select="concat('</',$elemname,'>')"/>
<xsl:variable name="innercontent">
<xsl:if test="not($elemclosed)">
<xsl:call-template name="skipper-before">
<xsl:with-param name="source" select="substring-after(substring-after($escaped,'<'),'>')"/>
<xsl:with-param name="delimiter" select="$elemclosetag"/>
</xsl:call-template>
</xsl:if>
</xsl:variable>
<xsl:variable name="afterelem">
<xsl:choose>
<xsl:when test="not($elemclosed)">
<xsl:call-template name="skipper-after">
<xsl:with-param name="source" select="substring-after(substring-after($escaped,'<'),'>')"/>
<xsl:with-param name="delimiter" select="$elemclosetag"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="substring-after(substring-after($escaped,'<'),'/>')"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:element name="{$elemname}">
<xsl:if test="$hasattributes">
<xsl:call-template name="unescapeattributes">
<xsl:with-param name="escapedattributes">
<xsl:choose>
<xsl:when test="not($elemclosed)">
<xsl:value-of select="normalize-space(substring-after($elemname2,' '))"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="normalize-space(substring-after($elemname3,' '))"/>
</xsl:otherwise>
</xsl:choose>
</xsl:with-param>
</xsl:call-template>
</xsl:if>
<xsl:call-template name="unescape">
<xsl:with-param name="escaped" select="$innercontent"/>
</xsl:call-template>
</xsl:element>
<xsl:call-template name="unescape">
<xsl:with-param name="escaped" select="$afterelem"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="unescapetext">
<xsl:with-param name="escapedtext" select="$escaped"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="unescapeattributes">
<xsl:param name="escapedattributes"/>
<xsl:variable name="attrname" select="substring-before($escapedattributes,'=')"/>
<xsl:variable name="attrquote" select="substring($escapedattributes,string-length($attrname)+2,1)"/>
<xsl:variable name="attrvalue" select="substring-before(substring-after($escapedattributes,$attrquote),$attrquote)"/>
<xsl:variable name="afterattr" select="substring-after(substring-after($escapedattributes,$attrquote),$attrquote)"/>
<xsl:attribute name="{$attrname}">
<xsl:call-template name="unescapetext">
<xsl:with-param name="escapedtext" select="$attrvalue"/>
</xsl:call-template>
</xsl:attribute>
<xsl:if test="contains($afterattr,'=')">
<xsl:call-template name="unescapeattributes">
<xsl:with-param name="escapedattributes" select="normalize-space($afterattr)"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template name="unescapetext">
<xsl:param name="escapedtext"/>
<xsl:call-template name="string-replace-all">
<xsl:with-param name="text">
<xsl:call-template name="string-replace-all">
<xsl:with-param name="text">
<xsl:call-template name="string-replace-all">
<xsl:with-param name="text" select="$escapedtext"/>
<xsl:with-param name="replace">></xsl:with-param>
<xsl:with-param name="by">></xsl:with-param>
</xsl:call-template>
</xsl:with-param>
<xsl:with-param name="replace"><</xsl:with-param>
<xsl:with-param name="by"><</xsl:with-param>
</xsl:call-template>
</xsl:with-param>
<xsl:with-param name="replace">&</xsl:with-param>
<xsl:with-param name="by">&</xsl:with-param>
</xsl:call-template>
</xsl:template>
<!-- replaces substrings in strings -->
<xsl:template name="string-replace-all">
<xsl:param name="text"/>
<xsl:param name="replace"/>
<xsl:param name="by"/>
<xsl:choose>
<xsl:when test="contains($text, $replace)">
<xsl:value-of select="substring-before($text,$replace)"/>
<xsl:value-of select="$by"/>
<xsl:call-template name="string-replace-all">
<xsl:with-param name="text" select="substring-after($text,$replace)"/>
<xsl:with-param name="replace" select="$replace"/>
<xsl:with-param name="by" select="$by"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$text"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!-- returns the substring after the last delimiter -->
<xsl:template name="skipper-after">
<xsl:param name="source"/>
<xsl:param name="delimiter"/>
<xsl:choose>
<xsl:when test="contains($source,$delimiter)">
<xsl:call-template name="skipper-after">
<xsl:with-param name="source" select="substring-after($source,$delimiter)"/>
<xsl:with-param name="delimiter" select="$delimiter"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$source"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!-- returns the substring before the last delimiter -->
<xsl:template name="skipper-before">
<xsl:param name="source"/>
<xsl:param name="delimiter"/>
<xsl:param name="result"/>
<xsl:choose>
<xsl:when test="contains($source,$delimiter)">
<xsl:call-template name="skipper-before">
<xsl:with-param name="source" select="substring-after($source,$delimiter)"/>
<xsl:with-param name="delimiter" select="$delimiter"/>
<xsl:with-param name="result">
<xsl:if test="result!=''">
<xsl:value-of select="concat($result,$delimiter)"/>
</xsl:if>
<xsl:value-of select="substring-before($source,$delimiter)"/>
</xsl:with-param>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$result"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
I found that I can use saxon to do this in a much simpler way using the following:
<xsl:template match="SomeCommand">
<sXmlParameters>
<xsl:apply-templates select="saxon:parse(.)" />
</sXmlParameters>
</xsl:template>
there is also saxon:seriralize() that can be used to escape the xml
thanks to all for you input
Wrote a SAX parser for xml-escaped strings in pure xsl 1.0+EXSLT
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:pxml="https://github.com/ilyakharlamov/pure-xsl/parseStringAsXML"
version="1.0">
<xsl:import href="https://raw.githubusercontent.com/ilyakharlamov/pure-xsl/master/parseStringAsXML.xsl"/>
<xsl:template match="/">
<xsl:call-template name="pxml:parseStringAsXML">
<xsl:with-param name="string"><PARAMETERS><TIMEOUTDATETIME>2011-03-152:09:48.997</TIMEOUTDATETIME></PARAMETERS></xsl:with-param>
</xsl:call-template>
</xsl:template>
</xsl:stylesheet>
Output:
<PARAMETERS>
<TIMEOUTDATETIME>2011-03-152:09:48.997</TIMEOUTDATETIME>
</PARAMETERS>
This is a followup to this question.
I have several <span> tags in a document with several semicolon separated style attributes. Right now I have 3 specific style attributes I'm looking for to translate into tags. All works well in the example above as long as the style attribute only contains one of the three style attributes. If a span has more, I get an ambiguous rule match.
The three style attributes I'm looking for are font-style:italic, font-weight:600, and text-decoration:underline which should be removed from the style attribute and transformed into <em>, <strong>, and <u>, respectively.
Here's my current XSLT:
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="span[
contains(translate(#style, ' ', ''), 'font-style:italic')
]">
<xsl:copy>
<xsl:attribute name="style">
<xsl:value-of select="substring-before(#style, ' font-style')"/>
<xsl:value-of select="substring-after(#style, 'italic;')"/>
</xsl:attribute>
<em>
<xsl:apply-templates select="node()"/>
</em>
</xsl:copy>
</xsl:template>
<xsl:template match="span[
contains(translate(#style, ' ', ''), 'font-weight:600')
]">
<xsl:copy>
<xsl:attribute name="style">
<xsl:value-of select="substring-before(#style, ' font-weight')"/>
<xsl:value-of select="substring-after(#style, '600;')"/>
</xsl:attribute>
<strong>
<xsl:apply-templates select="node()"/>
</strong>
</xsl:copy>
</xsl:template>
<xsl:template match="span[
contains(translate(#style, ' ', ''), 'text-decoration:underline')
]">
<xsl:copy>
<xsl:attribute name="style">
<xsl:value-of select="substring-before(#style, ' text-decoration')"/>
<xsl:value-of select="substring-after(#style, 'underline;')"/>
</xsl:attribute>
<u>
<xsl:apply-templates select="node()"/>
</u>
</xsl:copy>
</xsl:template>
Which will generate the ambiguous rule warning doesn't work correctly on some elements that contain more than one of the listed attributes.
An example of the input:
<span style=" text-decoration: underline; font-weight:600; color:#555555">some text</span>
gets transformed to:
<span style=" font-weight:600; color:#555555"><u>some text</u></span>
when the desired result is:
<span style="color:#555555"><b><u>some text</u></b></span>
How can I fix the ambiguous rule match for this?
Thanks in advance
Update:
If i set priorty on each of the templates to descending values and run the XSLT again on the output of the first XSLT run, everything works as expected. There has to be an easier way than running it through the transformation twice. Any ideas?
As Alejandro and Tomalak suggested, replacing the style attributes with a class attribute for CSS classes is an option, too.
EDIT: Just in case real problem gets hide, I have simplified the stylesheet:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
xmlns:s="styles"
exclude-result-prefixes="s msxsl">
<s:s prop="font-style:italic" name="em"/>
<s:s prop="font-weight:600" name="strong"/>
<s:s prop="text-decoration:underline" name="u"/>
<xsl:variable name="vStyles" select="document('')/*/s:s"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="span[#style]">
<xsl:variable name="vrtfProp">
<xsl:call-template name="parser"/>
</xsl:variable>
<xsl:variable name="vProp"
select="msxsl:node-set($vrtfProp)/*"/>
<xsl:copy>
<xsl:apply-templates select="#*[name()!='style']"/>
<xsl:attribute name="style">
<xsl:for-each select="$vProp[not(.=$vStyles/#prop)]">
<xsl:value-of select="concat(.,';')"/>
</xsl:for-each>
</xsl:attribute>
<xsl:call-template name="generate">
<xsl:with-param
name="pElements"
select="$vStyles[#prop=$vProp]/#name"/>
</xsl:call-template>
</xsl:copy>
</xsl:template>
<xsl:template name="generate">
<xsl:param name="pElements" select="/.."/>
<xsl:choose>
<xsl:when test="$pElements">
<xsl:element name="{$pElements[1]}">
<xsl:call-template name="generate">
<xsl:with-param
name="pElements"
select="$pElements[position()>1]"/>
</xsl:call-template>
</xsl:element>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="parser">
<xsl:param name="pString" select="concat(#style,';')"/>
<xsl:if test="contains($pString,';')">
<xsl:variable
name="vProp"
select="substring-before($pString,';')"/>
<prop>
<xsl:value-of
select="concat(
normalize-space(
substring-before($vProp,':')
),
':',
normalize-space(
substring-after($vProp,':')
)
)"/>
</prop>
<xsl:call-template name="parser">
<xsl:with-param
name="pString"
select="substring-after($pString,';')"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Output:
<span style="color:#555555;"><strong><u>some text</u></strong></span>
Note: Simpler parsing with space normalization to match properties in an existencial comparison. Generating content without optimization (selecting no match, selecting match). "Stateful" or "stackful" named template for output nested elements. Either way there are two rules (identity and span with #style overwriting it) and two names templates (parser/tokenizer and generator of nested content)
Original stylesheet:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
xmlns:s="styles"
xmlns:t="tokenizer"
exclude-result-prefixes="s t msxsl">
<s:s r="font-style" v="italic" e="em"/>
<s:s r="font-weight" v="600" e="strong"/>
<s:s r="text-decoration" v="underline" e="u"/>
<t:t s=";" n="p"/>
<t:t s=":" n="t"/>
<xsl:variable name="vStyles" select="document('')/*/s:s"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="span[#style]">
<xsl:variable name="vrtfStyles">
<xsl:call-template name="tokenizer"/>
</xsl:variable>
<xsl:copy>
<xsl:apply-templates select="#*[name()!='style']"/>
<xsl:call-template name="generate">
<xsl:with-param name="pStyles"
select="msxsl:node-set($vrtfStyles)/*"/>
</xsl:call-template>
</xsl:copy>
</xsl:template>
<xsl:template name="generate">
<xsl:param name="pStyles" select="/.."/>
<xsl:param name="pAttributes" select="/.."/>
<xsl:param name="pElements" select="/.."/>
<xsl:choose>
<xsl:when test="$pStyles">
<xsl:variable name="vMatch"
select="$vStyles[#r=$pStyles[1]/t[1]]
[#v=$pStyles[1]/t[2]]"/>
<xsl:call-template name="generate">
<xsl:with-param name="pStyles"
select="$pStyles[position()>1]"/>
<xsl:with-param name="pAttributes"
select="$pAttributes|
$pStyles[1][not($vMatch)]"/>
<xsl:with-param name="pElements"
select="$pElements|$vMatch"/>
</xsl:call-template>
</xsl:when>
<xsl:when test="$pAttributes">
<xsl:attribute name="style">
<xsl:for-each select="$pAttributes">
<xsl:value-of select="concat(t[1],':',t[2],';')"/>
</xsl:for-each>
</xsl:attribute>
<xsl:call-template name="generate">
<xsl:with-param name="pElements" select="$pElements"/>
</xsl:call-template>
</xsl:when>
<xsl:when test="$pElements">
<xsl:element name="{$pElements[1]/#e}">
<xsl:call-template name="generate">
<xsl:with-param name="pElements"
select="$pElements[position()>1]"/>
</xsl:call-template>
</xsl:element>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="tokenizer">
<xsl:param name="pTokenizer" select="document('')/*/t:t"/>
<xsl:param name="pString" select="#style"/>
<xsl:choose>
<xsl:when test="not($pTokenizer)">
<xsl:value-of select="normalize-space($pString)"/>
</xsl:when>
<xsl:when test="contains($pString,$pTokenizer[1]/#s)">
<xsl:call-template name="tokenizer">
<xsl:with-param name="pTokenizer" select="$pTokenizer"/>
<xsl:with-param name="pString"
select="substring-before(
$pString,
$pTokenizer[1]/#s
)"/>
</xsl:call-template>
<xsl:call-template name="tokenizer">
<xsl:with-param name="pTokenizer" select="$pTokenizer"/>
<xsl:with-param name="pString"
select="substring-after(
$pString,
$pTokenizer[1]/#s
)"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:element name="{$pTokenizer[1]/#n}">
<xsl:call-template name="tokenizer">
<xsl:with-param name="pTokenizer"
select="$pTokenizer[position()>1]"/>
<xsl:with-param name="pString" select="$pString"/>
</xsl:call-template>
</xsl:element>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Note: Recursion paradise. Nested tokenizer for parsing style properties. "Stateful" template for nested content (and performance matching properties, by the way)
Here is an XSLT 1.0 solution using the str-split-to-words template/function of FXSL:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="http://exslt.org/common"
exclude-result-prefixes="ext"
>
<xsl:import href="strSplit-to-Words.xsl"/>
<xsl:output indent="yes" omit-xml-declaration="yes"/>
<xsl:param name="pStyleReps">
<r s="font-style:italic"><em/></r>
<r s="font-weight:600"><strong/></r>
<r s="text-decoration:underline"><u/></r>
</xsl:param>
<xsl:variable name="vReps" select=
"document('')/*/xsl:param[#name='pStyleReps']/*"/>
<xsl:template match="span">
<xsl:variable name="vrtfStyles">
<xsl:call-template name="str-split-to-words">
<xsl:with-param name="pStr" select="#style"/>
<xsl:with-param name="pDelimiters" select="';'"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="vStyles" select=
"ext:node-set($vrtfStyles)/*"/>
<xsl:choose>
<xsl:when test=
"not($vReps/#s[contains(current()/#style, .)])">
<xsl:copy-of select="."/>
</xsl:when>
<xsl:otherwise>
<span>
<xsl:copy-of select="#*"/>
<xsl:attribute name="style">
<xsl:for-each select=
"$vStyles[not(translate(.,' ','')=$vReps/#s)]">
<xsl:value-of select="."/>
<xsl:if test="not(position()=last())">;</xsl:if>
</xsl:for-each>
</xsl:attribute>
<xsl:call-template name="styles2markup">
<xsl:with-param name="pStyles" select=
"$vReps/#s
[contains
(translate(current()/#style, ' ', ''),
.
)
]"/>
</xsl:call-template>
</span>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="styles2markup">
<xsl:param name="pResult" select="text()"/>
<xsl:param name="pStyles"/>
<xsl:choose>
<xsl:when test="not($pStyles)">
<xsl:copy-of select="$pResult"/>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="vrtfnewResult">
<xsl:element name="{name($pStyles[1]/../*)}">
<xsl:copy-of select="$pResult"/>
</xsl:element>
</xsl:variable>
<xsl:call-template name="styles2markup">
<xsl:with-param name="pStyles" select=
"$pStyles[position()>1]"/>
<xsl:with-param name="pResult" select=
"ext:node-set($vrtfnewResult)/*"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
when this transformation is applied on the provided XML document:
<span style=" text-decoration: underline; font-weight:600; color:#555555">some text</span>
the wanted, correct result is produced:
<span style=" color:#555555">
<u>
<strong>some text</strong>
</u>
</span>
I'm transforming my XSLT-stylesheets into documentation, and I want a rich experience within the comment nodes for each code-chunk, therefore I want to convert the following comment and output as xhtml:
String:
# This is a title with __bold__ text and *italic* #
This is just a normal line
- list point with some __bold__
- list point with a "link"[http://www.stackoverflow.com]
Wanted output:
<h1> This is a title with <strong>bold</strong> and <span>italic</span> </h1>
<p>This is a normal line</p>
<ul>
<li>list point with some <strong>bold</strong></li>
<li>list point with a link</li>
</ul>
I tried with a recursive function that uses xsl:analyze-string recursively from a ruleset, but can't find a solution that works really well.
Anyone have done this lately, or is there some frameworks out there that has functions to do this ?
thanx in advance! :)
Edit: Added one dirty example:
<!-- Output comments -->
<xsl:template match="comment()" mode="COMMENT">
<xsl:copy-of select="ips:groupReplace(normalize-space(.),
'
(.*)(\n|\r)(.*),
(.*)\*(.*)\*(.*),
(.*)\*\*(.*)\*\*(.*),
(.*)__(.*)__(.*),
(.*)#(.*)#(.*),
(.*)-(.*)
',
'
br,
span.italic,
span.bold,
strong,
h1,
li
')" />
</xsl:template>
<!-- Initializing the iterateRegex function -->
<xsl:function name="ips:groupReplace">
<xsl:param name="string" as="xs:string" />
<xsl:param name="search" />
<xsl:param name="replace" />
<xsl:variable name="regex" select="tokenize($search, ',')" />
<xsl:variable name="replacements" select="tokenize($replace, ',')" />
<xsl:copy-of select="ips:iterateRegex(count($replacements), $string, $regex, $replacements)" />
</xsl:function>
<!-- Iterate each regex -->
<xsl:function name="ips:iterateRegex">
<xsl:param name="counter" />
<xsl:param name="string" />
<xsl:param name="list_regex" />
<xsl:param name="list_replace" />
<xsl:variable name="newStr">
<xsl:analyze-string select="$string" regex="{normalize-space($list_regex[$counter])}" flags="xm">
<xsl:matching-substring>
<xsl:variable name="cc" select="contains($list_replace[$counter], '.')" />
<xsl:variable name="tag" select="normalize-space(if ($cc) then (substring-before($list_replace[$counter], '.')) else ($list_replace[$counter]))" />
<xsl:copy-of select="regex-group(1)" />
<xsl:choose>
<xsl:when test="normalize-space(regex-group(2)) = ''">
<xsl:element name="{$tag}" />
</xsl:when>
<xsl:otherwise>
<xsl:element name="{$tag}" >
<xsl:if test="$cc">
<xsl:attribute name="class" select="substring-after($list_replace[$counter],'.')" />
</xsl:if>
<xsl:copy-of select="regex-group(2)" />
</xsl:element>
</xsl:otherwise>
</xsl:choose>
<xsl:copy-of select="regex-group(3)" />
</xsl:matching-substring>
<xsl:non-matching-substring>
<xsl:copy-of select="." />
</xsl:non-matching-substring>
</xsl:analyze-string>
</xsl:variable>
<xsl:variable name="count" select="number($counter) - 1" />
<xsl:choose>
<xsl:when test="$count > 0">
<xsl:copy-of select="ips:iterateRegex($count, $newStr, $list_regex, $list_replace)" />
</xsl:when>
<xsl:otherwise>
<xsl:copy-of select="$newStr" />
</xsl:otherwise>
</xsl:choose>
</xsl:function>
I think you would need a parser. So this stylesheet implements a verbose one:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="text" name="block">
<xsl:param name="pString" select="."/>
<xsl:if test="$pString != ''">
<xsl:choose>
<xsl:when test="starts-with($pString,'#')">
<xsl:call-template name="header">
<xsl:with-param name="pString"
select="substring($pString,2)"/>
</xsl:call-template>
</xsl:when>
<xsl:when test="starts-with($pString,'
')">
<xsl:call-template name="list">
<xsl:with-param name="pString"
select="substring($pString,2)"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="paragraph">
<xsl:with-param name="pString"
select="$pString"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:if>
</xsl:template>
<xsl:template name="header">
<xsl:param name="pString"/>
<xsl:variable name="vInside"
select="substring-before($pString,'#
')"/>
<xsl:choose>
<xsl:when test="$vInside != ''">
<h1>
<xsl:call-template name="inline">
<xsl:with-param name="pString" select="$vInside"/>
</xsl:call-template>
</h1>
<xsl:call-template name="block">
<xsl:with-param name="pString"
select="substring-after($pString,'#
')"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="paragraph">
<xsl:with-param name="pString"
select="concat('#',$pString)"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="list">
<xsl:param name="pString"/>
<xsl:variable name="vCheckList" select="starts-with($pString,'- ')"/>
<xsl:choose>
<xsl:when test="$vCheckList">
<ul>
<xsl:call-template name="listItem">
<xsl:with-param name="pString" select="$pString"/>
</xsl:call-template>
</ul>
<xsl:call-template name="block">
<xsl:with-param name="pString">
<xsl:call-template name="afterlist">
<xsl:with-param name="pString" select="$pString"/>
</xsl:call-template>
</xsl:with-param>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="block">
<xsl:with-param name="pString" select="$pString"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="paragraph">
<xsl:param name="pString"/>
<xsl:choose>
<xsl:when test="contains($pString,'
')">
<p>
<xsl:value-of select="substring-before($pString,'
')"/>
</p>
</xsl:when>
<xsl:otherwise>
<p>
<xsl:value-of select="$pString"/>
</p>
</xsl:otherwise>
</xsl:choose>
<xsl:call-template name="block">
<xsl:with-param name="pString"
select="substring-after($pString,'
')"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="afterlist">
<xsl:param name="pString"/>
<xsl:choose>
<xsl:when test="starts-with($pString,'- ')">
<xsl:call-template name="afterlist">
<xsl:with-param name="pString"
select="substring-after($pString,'
')"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$pString"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="listItem">
<xsl:param name="pString"/>
<xsl:if test="starts-with($pString,'- ')">
<li>
<xsl:call-template name="inline">
<xsl:with-param name="pString"
select="substring-before(substring($pString,3),'
')"/>
</xsl:call-template>
</li>
<xsl:call-template name="listItem">
<xsl:with-param name="pString"
select="substring-after($pString,'
')"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template name="inline">
<xsl:param name="pString" select="."/>
<xsl:if test="$pString != ''">
<xsl:choose>
<xsl:when test="starts-with($pString,'__')">
<xsl:call-template name="strong">
<xsl:with-param name="pString"
select="substring($pString,3)"/>
</xsl:call-template>
</xsl:when>
<xsl:when test="starts-with($pString,'*')">
<xsl:call-template name="span">
<xsl:with-param name="pString"
select="substring($pString,2)"/>
</xsl:call-template>
</xsl:when>
<xsl:when test="starts-with($pString,'"')">
<xsl:call-template name="link">
<xsl:with-param name="pString"
select="substring($pString,2)"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="substring($pString,1,1)"/>
<xsl:call-template name="inline">
<xsl:with-param name="pString"
select="substring($pString,2)"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:if>
</xsl:template>
<xsl:template name="strong">
<xsl:param name="pString"/>
<xsl:variable name="vInside" select="substring-before($pString,'__')"/>
<xsl:choose>
<xsl:when test="$vInside != ''">
<strong>
<xsl:value-of select="$vInside"/>
</strong>
<xsl:call-template name="inline">
<xsl:with-param name="pString"
select="substring-after($pString,'__')"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="'__'"/>
<xsl:call-template name="inline">
<xsl:with-param name="pString" select="$pString"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="span">
<xsl:param name="pString"/>
<xsl:variable name="vInside" select="substring-before($pString,'*')"/>
<xsl:choose>
<xsl:when test="$vInside != ''">
<span>
<xsl:value-of select="$vInside"/>
</span>
<xsl:call-template name="inline">
<xsl:with-param name="pString"
select="substring-after($pString,'*')"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="'*'"/>
<xsl:call-template name="inline">
<xsl:with-param name="pString" select="$pString"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="link">
<xsl:param name="pString"/>
<xsl:variable name="vInside"
select="substring-before($pString,'"')"/>
<xsl:choose>
<xsl:when test="$vInside != ''">
<xsl:call-template name="href">
<xsl:with-param name="pString"
select="substring-after($pString,'"')"/>
<xsl:with-param name="pInside" select="$vInside"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="'"'"/>
<xsl:call-template name="inline">
<xsl:with-param name="pString" select="$pString"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="href">
<xsl:param name="pString"/>
<xsl:param name="pInside"/>
<xsl:variable name="vHref"
select="substring-before(substring($pString,2),']')"/>
<xsl:choose>
<xsl:when test="starts-with($pString,'[') and $vHref != ''">
<a href="{$vHref}">
<xsl:value-of select="$pInside"/>
</a>
<xsl:call-template name="inline">
<xsl:with-param name="pString"
select="substring-after($pString,']')"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="concat('"',$pInside,'"')"/>
<xsl:call-template name="inline">
<xsl:with-param name="pString" select="$pString"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
With this input:
<text>
# This is a title with __bold__ text and *italic* #
This is just a normal line
- list point with some __bold__
- list point with a "link"[http://www.stackoverflow.com]
</text>
Output:
<h1> This is a title with
<strong>bold</strong> text and
<span>italic</span>
</h1>
<p>This is just a normal line</p>
<ul>
<li>list point with some
<strong>bold</strong>
</li>
<li>list point with a
link
</li>
</ul>
Note: Look how many templates are similar (they follow a pattern), so these could be parametrized. I didn't do that in this case because there seems to be more questions which need some sort of parser, so by the end of the week I will repost an answer implementing functional parser and parser combinators pattern that make very easy to write parsers (just writing its grammar rules).
Edit: XSLT 2.0 solution. This stylesheet:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="text">
<xsl:param name="pString" select="."/>
<xsl:analyze-string select="$pString"
regex="(#(.*)#
)|((- (.*)
)+)">
<xsl:matching-substring>
<xsl:choose>
<xsl:when test="regex-group(1)">
<h1>
<xsl:call-template name="inline">
<xsl:with-param name="pString"
select="regex-group(2)"/>
</xsl:call-template>
</h1>
</xsl:when>
<xsl:when test="regex-group(3)">
<ul>
<xsl:call-template name="list">
<xsl:with-param name="pString"
select="regex-group(3)"/>
</xsl:call-template>
</ul>
</xsl:when>
</xsl:choose>
</xsl:matching-substring>
<xsl:non-matching-substring>
<xsl:if test=".!='
'">
<p>
<xsl:call-template name="inline">
<xsl:with-param name="pString"
select="normalize-space(.)"/>
</xsl:call-template>
</p>
</xsl:if>
</xsl:non-matching-substring>
</xsl:analyze-string>
</xsl:template>
<xsl:template name="list">
<xsl:param name="pString"/>
<xsl:analyze-string select="$pString" regex="- (.*)
">
<xsl:matching-substring>
<li>
<xsl:call-template name="inline">
<xsl:with-param name="pString"
select="regex-group(1)"/>
</xsl:call-template>
</li>
</xsl:matching-substring>
</xsl:analyze-string>
</xsl:template>
<xsl:template name="inline">
<xsl:param name="pString" select="."/>
<xsl:analyze-string select="$pString"
regex="(__(.*)__)|(\*(.*)\*)|("(.*)"\[(.*)\])">
<xsl:matching-substring>
<xsl:choose>
<xsl:when test="regex-group(1)">
<strong>
<xsl:value-of select="regex-group(2)"/>
</strong>
</xsl:when>
<xsl:when test="regex-group(3)">
<span>
<xsl:value-of select="regex-group(4)"/>
</span>
</xsl:when>
<xsl:when test="regex-group(5)">
<a href="{regex-group(7)}">
<xsl:value-of select="regex-group(6)"/>
</a>
</xsl:when>
</xsl:choose>
</xsl:matching-substring>
<xsl:non-matching-substring>
<xsl:value-of select="."/>
</xsl:non-matching-substring>
</xsl:analyze-string>
</xsl:template>
</xsl:stylesheet>
Output:
<h1> This is a title with
<strong>bold</strong> text and
<span>italic</span>
</h1>
<p>This is just a normal line</p>
<ul>
<li>list point with some
<strong>bold</strong>
</li>
<li>list point with a
link
</li>
</ul>
This transformation (111 lines):
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:my="my:my"
exclude-result-prefixes="xml xsl xs my">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/">
<xsl:variable name="vLines" select="tokenize(., '\n')"/>
<xsl:sequence select="my:parse-lines($vLines)"/>
</xsl:template>
<xsl:function name="my:parse-lines" as="element()*">
<xsl:param name="pLines" as="xs:string*"/>
<xsl:sequence select=
"my:parse-line($pLines, 1, count($pLines))"/>
</xsl:function>
<xsl:function name="my:parse-line" as="element()*">
<xsl:param name="pLines" as="xs:string*"/>
<xsl:param name="pLineNum" as="xs:integer"/>
<xsl:param name="pTotalLines" as="xs:integer"/>
<xsl:if test="not($pLineNum gt $pTotalLines)">
<xsl:variable name="vLine" select="$pLines[$pLineNum]"/>
<xsl:variable name="vLineLength"
select="string-length($vLine)"/>
<xsl:choose>
<xsl:when test=
"starts-with($vLine, '#')
and
ends-with($vLine, '#')
">
<xsl:variable name="vInnerString"
select="substring($vLine, 2, $vLineLength -2)"/>
<h1>
<xsl:sequence select="my:parse-string($vInnerString)"/>
</h1>
<xsl:sequence select=
"my:parse-line($pLines, $pLineNum+1, $pTotalLines)"/>
</xsl:when>
<xsl:when test=
"starts-with($vLine, '- ')
and
not(starts-with($pLines[$pLineNum -1], '- '))
">
<ul>
<li>
<xsl:sequence select="my:parse-string(substring($vLine, 2))"/>
</li>
<xsl:sequence select=
"my:parse-line($pLines, $pLineNum+1, $pTotalLines)"/>
</ul>
</xsl:when>
<xsl:when test="starts-with($vLine, '- ')">
<li>
<xsl:sequence select="my:parse-string(substring($vLine, 2))"/>
</li>
<xsl:sequence select=
"my:parse-line($pLines, $pLineNum+1, $pTotalLines)"/>
</xsl:when>
<xsl:otherwise>
<p>
<xsl:sequence select="my:parse-string($vLine)"/>
</p>
<xsl:sequence select=
"my:parse-line($pLines, $pLineNum+1, $pTotalLines)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:if>
</xsl:function>
<xsl:function name="my:parse-string" as="node()*">
<xsl:param name="pS" as="xs:string"/>
<xsl:analyze-string select="$pS" flags="x" regex=
'(__(.*?)__)
|
(\*(.*?)\*)
|
("(.*?)"\[(.*?)\])
'>
<xsl:matching-substring>
<xsl:choose>
<xsl:when test="regex-group(1)">
<strong>
<xsl:sequence select="my:parse-string(regex-group(2))"/>
</strong>
</xsl:when>
<xsl:when test="regex-group(3)">
<span>
<xsl:sequence select="my:parse-string(regex-group(4))"/>
</span>
</xsl:when>
<xsl:when test="regex-group(5)">
<a href="{regex-group(7)}">
<xsl:sequence select="regex-group(6)"/>
</a>
</xsl:when>
</xsl:choose>
</xsl:matching-substring>
<xsl:non-matching-substring>
<xsl:value-of select="."/>
</xsl:non-matching-substring>
</xsl:analyze-string>
</xsl:function>
</xsl:stylesheet>
when applied on this XML document (the provided text complicated with nested constructs and wrapped in an element):
<t># This is a title with __bold__ text and *italic* #
This is just a normal line
- list point with some __bold__
- list point with a __*"link"[http://www.stackoverflow.com]*__</t>
produces the wanted, correct output:
<h1> This is a title with <strong>bold</strong> text and <span>italic</span>
</h1>
<p>This is just a normal line</p>
<p/>
<ul>
<li> list point with some <strong>bold</strong>
</li>
<li> list point with a <strong>
<span>
link
</span>
</strong>
</li>
</ul>
Do note: The RegEx mechanism of XPath 2.0 and XSLT 2.0 is adequate for solving this problem.