I am trying to call the below template from my code . But I keep getting javax.xml.transform.TransformerException: ElemTemplateElement error: incrementValue.For a different template I still get javax.xml.transform.TransformerException: ElemTemplateElement error: templateName.Since the stylesheet is too long I am pasting the relevant code of the stylesheet. Can someone let me know what I am doing wrong ??
<xsl:stylesheet version = '2.0'
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xdt="http://www.w3.org/2005/02/xpath-datatypes"
xmlns:mngi="www.medianewsgroup.com"
exclude-result-prefixes="xs xdt mngi dirReader"
xmlns:date="http://exslt.org/dates-and-times"
xmlns:utildate="xalan://java.util.Date"
xmlns:dirReader="xalan://com.mngi.eidos.util.DirectoryReader"
extension-element-prefixes="date utildate dirReader">
<xsl:strip-space elements="*"/>
<xsl:output method="xml"
indent="yes"
encoding="utf-8"
doctype-system="/SysConfig/Classify/Dtd/MNG/classify-story.dtd"/>
<xsl:template match="/">
<xsl:processing-instruction name="EM-dtdExt"
>/SysConfig/Rules/MNG/MNG.dtx</xsl:processing-instruction>
<xsl:processing-instruction name="EM-templateName"
>/SysConfig/BaseConfiguration/MNG/Templates/MNG_story.xml</xsl:processing-instruction>
<xsl:processing-instruction name="xml-stylesheet"
>type="text/css" href="/SysConfig/BaseConfiguration/MNG/Css/MNG-story-nonechannel.css"</xsl:processing-instruction>
<!-- Added By Sachin -->
<xsl:processing-instruction name="EM-dtdExt"
>/SysConfig/Rules/MNG/MNG.dtx</xsl:processing-instruction>
<xsl:processing-instruction name="EM-templateName"
>/SysConfig/BaseConfiguration/MNG/Templates/MNG_story.xml</xsl:processing-instruction>
<xsl:processing-instruction name="xml-stylesheet"
>type="text/css" href="/SysConfig/BaseConfiguration/MNG/Css/MNG-story-nonechannel.css"</xsl:processing-instruction>
<xsl:variable name="UPPERCASE" select="'ABCDEFGHIJKLMNOPQRSTUVWXYZ '" />
<xsl:variable name="lowercase" select="'abcdefghijklmnopqrstuvwxyz'" />
<xsl:variable name="HubName" select="translate(/Article/Hub/HubName, ' ', '')" />
<xsl:variable name="lowerhubname" select="translate($HubName, $UPPERCASE, $lowercase)" />
<xsl:variable name="SiteRoot" select="'C:/TwinCitiesArticles'" />
<xsl:variable name="DatePath" select="translate(substring-before(/Article/PublishingDates/WebPublish_DTTM, 'T'), '-', '/')"/>
<xsl:variable name="PhotoDir" select="'photos/'" />
<xsl:variable name="PhotoPath" select="concat($SiteRoot, $DatePath, '/', $lowerhubname, $PhotoDir)" />
<TodaysDate>
<xsl:value-of select="utildate:new()"/>
</TodaysDate>
<imageDir>
<xsl:value-of select="$PhotoPath"/>
</imageDir>
<xsl:variable name="totalPhotos" select="dirReader:totalPhotos($PhotoPath)"/>
<xsl:variable name="photoList" select="dirReader:readDirectory($PhotoPath)"/>
<xsl:variable name="pName" select="dirReader:photoName($totalPhotos,$PhotoPath)"/>
<xsl:variable name="firstPhotoName" select="dirReader:firstPhoto($totalPhotos,$PhotoPath)"/>
<xsl:variable name="currentIdx" select="dirReader:currentIndex($firstPhotoName,$PhotoPath)"/>
<totalPhotos>
<xsl:value-of select="$totalPhotos" />
</totalPhotos>
<xsl:template name="incrementValue">
<xsl:param name="currentIdx"/>
<xsl:if test="$currentIdx < $totalPhotos">
<xsl:value-of select="$currentIdx"/>
<photoName>
<xsl:variable name="photoFromIndex"
select="dirReader:photoNameWithIndex($currentIdx,$PhotoPath)"/>
<xsl:value-of select="concat($PhotoPath,'',$photoFromIndex)"/>
</photoName>
<xsl:call-template name="incrementValue">
<xsl:with-param name="currentIdx" select="$currentIdx + 1"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:if test="$totalPhotos > 0">
<photoName>
<!--xsl:value-of select="$currentIdx"/-->
<xsl:variable name="photoFromIndex" select="dirReader:photoNameWithIndex($currentIdx,$PhotoPath)"/>
<xsl:value-of select="concat($PhotoPath,'',$photoFromIndex)"/>
</photoName>
<xsl:call-template name="incrementValue">
<xsl:with-param name="currentIdx" select="$currentIdx"/>
</xsl:call-template>
</xsl:if>
Your xsl:if, xsl:value-of and xsl:variable all need to exist inside an xsl:template, xsl:variable or xsl:param, I am not sure whether they are not.
An xsl:template must be a child of xsl:stylesheet only.
You need to remove the template definitions from inside the first <xsl:template match="/">
Define the incrementValue template seperate and put the content of the other template inside the main <xsl:template match="/">
so you have something like this:
<xsl:stylesheet version = '2.0'
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xdt="http://www.w3.org/2005/02/xpath-datatypes"
xmlns:mngi="www.medianewsgroup.com"
exclude-result-prefixes="xs xdt mngi dirReader"
xmlns:date="http://exslt.org/dates-and-times"
xmlns:utildate="xalan://java.util.Date"
xmlns:dirReader="xalan://com.mngi.eidos.util.DirectoryReader"
extension-element-prefixes="date utildate dirReader">
<xsl:strip-space elements="*"/>
<xsl:output method="xml"
indent="yes"
encoding="utf-8"
doctype-system="/SysConfig/Classify/Dtd/MNG/classify-story.dtd"/>
...
<xsl:variable name="totalPhotos" select="dirReader:totalPhotos($PhotoPath)"/>
...
<xsl:template match="/">
...
<xsl:if test="$totalPhotos > 0">
<photoName>
<!--xsl:value-of select="$currentIdx"/-->
<xsl:variable name="photoFromIndex" select="dirReader:photoNameWithIndex($currentIdx,$PhotoPath)"/>
<xsl:value-of select="concat($PhotoPath,'',$photoFromIndex)"/>
</photoName>
<xsl:call-template name="incrementValue">
<xsl:with-param name="currentIdx" select="$currentIdx"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template name="incrementValue">
<xsl:param name="currentIdx"/>
<xsl:if test="$currentIdx < $totalPhotos">
<xsl:value-of select="$currentIdx"/>
<photoName>
<xsl:variable name="photoFromIndex" select="dirReader:photoNameWithIndex($currentIdx,$PhotoPath)"/>
<xsl:value-of select="concat($PhotoPath,'',$photoFromIndex)"/>
</photoName>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
EDIT: Variables used in both templates will have to be declared globally as I have done with <xsl:variable name="totalPhotos" select="dirReader:totalPhotos($PhotoPath)"/> above so they are available to both templates because at the minute they are only scoped to the template they are in. or you can pass them as parameters as is done with <xsl:with-param name="currentIdx" select="$currentIdx"/>. If there are variables that only exist in the incrementValue template move out of the main template into that one.
WARNING: This is untested as I don't fully understand the problem due to lack of input so I am only sorting out the syntax.
Related
i have a xml like,
<DESIGN-FUNCTION-PROTOTYPE>
<SHORT-NAME>xxx</SHORT-NAME>
<TYPE-TREF TYPE="DESIGN-FUNCTION-PROTOTYPE">ABC/DEF/123</TYPE-TREF>
</DESIGN-FUNCTION-PROTOTYPE>
<DESIGN-FUNCTION-PROTOTYPE>
<SHORT-NAME>yyy</SHORT-NAME>
<TYPE-TREF TYPE="DESIGN-FUNCTION-PROTOTYPE">LMN/OPQ/123</TYPE-TREF>
</DESIGN-FUNCTION-PROTOTYPE>
<DESIGN-FUNCTION-PROTOTYPE>
<SHORT-NAME>mmm</SHORT-NAME>
<TYPE-TREF TYPE="DESIGN-FUNCTION-PROTOTYPE">XYZ/GHY/456</TYPE-TREF>
</DESIGN-FUNCTION-PROTOTYPE>
<DESIGN-FUNCTION-PROTOTYPE>
<SHORT-NAME>nnn</SHORT-NAME>
<TYPE-TREF TYPE="DESIGN-FUNCTION-PROTOTYPE">AJK/UTL/456</TYPE-TREF>
</DESIGN-FUNCTION-PROTOTYPE>
My xslt,
<xsl:template name="substring-after-last">
<xsl:param name="string" />
<xsl:param name="delimiter" />
<xsl:choose>
<xsl:when test="contains($string, $delimiter)">
<xsl:call-template name="substring-after-last">
<xsl:with-param name="string"
select="substring-after($string, $delimiter)" />
<xsl:with-param name="delimiter" select="$delimiter" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$string" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:for-each select="select="//DESIGN-FUNCTION-PROTOTYPE/ea:TYPE-TREF[#TYPE='DESIGN-FUNCTION-TYPE']">
<xsl:variable name="myVar" select="current()"/>
<xsl:variable name="taskName" select="../ea:SHORT-NAME"/>
<xsl:variable name="Var7">
<xsl:call-template name="substring-after-last">
<xsl:with-param name="string" select="$myVar" />
<xsl:with-param name="delimiter" select="'/'" />
</xsl:call-template>
</xsl:variable>
<varoutput>
<xsl:value-of select="$Var7"/>
</varoutput>
</xsl:for-each>
My intention here is to iterate all the 'DESIGN-FUNCTION-PROTOTYPE' elements and display the sub-string of 'TYPE-TREF' value, but if a sub-string of 'TYPE-TREF' value has already been read..i must skip that element.
Expected output,
123
456
And Not,
123
123
456
456
In general I should consider only the first occurrence and skip the rest.
To do this in pure XSLT 1.0, without relying on processor-specific extensions, you could do:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:key name="k1" match="DESIGN-FUNCTION-PROTOTYPE" use="substring-after(substring-after(TYPE-TREF, '/'), '/')"/>
<xsl:template match="/Root">
<root>
<xsl:for-each select="DESIGN-FUNCTION-PROTOTYPE[count(. | key('k1', substring-after(substring-after(TYPE-TREF, '/'), '/'))[1]) = 1]">
<varoutput>
<xsl:value-of select="substring-after(substring-after(TYPE-TREF, '/'), '/')" />
</varoutput>
</xsl:for-each>
</root>
</xsl:template>
</xsl:stylesheet>
Demo: https://xsltfiddle.liberty-development.net/bFN1y9s
This is of course assuming that the value you're after is always the third "token" in TYPE-TREF. Otherwise you would have to do something similar to your attempt:
XSLT 1.0 + EXSLT node-set() function
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
exclude-result-prefixes="exsl" >
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:key name="k1" match="value" use="."/>
<xsl:template match="/Root">
<!-- EXTRACT VALUES -->
<xsl:variable name="values">
<xsl:for-each select="DESIGN-FUNCTION-PROTOTYPE">
<value>
<xsl:call-template name="last-token">
<xsl:with-param name="text" select="TYPE-TREF"/>
</xsl:call-template>
</value>
</xsl:for-each>
</xsl:variable>
<!-- OUTPUT -->
<root>
<xsl:for-each select="exsl:node-set($values)/value[count(. | key('k1', .)[1]) = 1]">
<varoutput>
<xsl:value-of select="." />
</varoutput>
</xsl:for-each>
</root>
</xsl:template>
<xsl:template name="last-token">
<xsl:param name="text"/>
<xsl:param name="delimiter" select="'/'"/>
<xsl:choose>
<xsl:when test="contains($text, $delimiter)">
<!-- recursive call -->
<xsl:call-template name="last-token">
<xsl:with-param name="text" select="substring-after($text, $delimiter)"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$text"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Demo: https://xsltfiddle.liberty-development.net/bFN1y9s/1
Assuming you use Xalan you should have access to the EXSLT str:split function (http://xalan.apache.org/xalan-j/apidocs/org/apache/xalan/lib/ExsltStrings.html#split(java.lang.String,%20java.lang.String):
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:str="http://exslt.org/strings" exclude-result-prefixes="str" version="1.0">
<xsl:key name="group" match="DESIGN-FUNCTION-PROTOTYPE/TYPE-TREF"
use="str:split(., '/')[last()]"/>
<xsl:template match="Root">
<xsl:for-each select="DESIGN-FUNCTION-PROTOTYPE/TYPE-TREF[generate-id() = generate-id(key('group', str:split(., '/')[last()])[1])]">
<varoutput>
<xsl:value-of select="str:split(., '/')[last()]"/>
</varoutput>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Transforms
<?xml version="1.0" encoding="UTF-8"?>
<Root>
<DESIGN-FUNCTION-PROTOTYPE>
<SHORT-NAME>xxx</SHORT-NAME>
<TYPE-TREF TYPE="DESIGN-FUNCTION-PROTOTYPE">ABC/DEF/123</TYPE-TREF>
</DESIGN-FUNCTION-PROTOTYPE>
<DESIGN-FUNCTION-PROTOTYPE>
<SHORT-NAME>yyy</SHORT-NAME>
<TYPE-TREF TYPE="DESIGN-FUNCTION-PROTOTYPE">LMN/OPQ/123</TYPE-TREF>
</DESIGN-FUNCTION-PROTOTYPE>
<DESIGN-FUNCTION-PROTOTYPE>
<SHORT-NAME>mmm</SHORT-NAME>
<TYPE-TREF TYPE="DESIGN-FUNCTION-PROTOTYPE">XYZ/GHY/456</TYPE-TREF>
</DESIGN-FUNCTION-PROTOTYPE>
<DESIGN-FUNCTION-PROTOTYPE>
<SHORT-NAME>nnn</SHORT-NAME>
<TYPE-TREF TYPE="DESIGN-FUNCTION-PROTOTYPE">AJK/UTL/456</TYPE-TREF>
</DESIGN-FUNCTION-PROTOTYPE>
</Root>
into
<?xml version="1.0" encoding="UTF-8"?><varoutput>123</varoutput><varoutput>456</varoutput>
with Xalan Java and Xalan Java XSLTC.
Or, as suggested in a comment, if you simply want to find the distinct values you can use set:distinct e.g.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
xmlns:str="http://exslt.org/strings"
xmlns:set="http://exslt.org/sets"
exclude-result-prefixes="exsl str set"
version="1.0">
<xsl:template match="Root">
<xsl:variable name="split-values">
<xsl:for-each select="DESIGN-FUNCTION-PROTOTYPE/TYPE-TREF">
<xsl:copy-of select="str:split(., '/')[last()]"/>
</xsl:for-each>
</xsl:variable>
<xsl:copy-of select="set:distinct(exsl:node-set($split-values)/node())"/>
</xsl:template>
</xsl:stylesheet>
I have below two variables which have multiple commas separated string values in different XSLT variable
Variable_01 : 88888,777777
Variable_02 : abc,xyz
Now I am looking for below output
[{"Group":"88888", "Name":"abc"},{"Group":"777777", "Name":"xyz"}]
Could you please help me what is correct XSLT code for the above output.
Here's one way you could look at it:
XSLT 2.0
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" encoding="UTF-8"/>
<xsl:variable name="Variable_01">88888,777777</xsl:variable>
<xsl:variable name="Variable_02">abc,xyz</xsl:variable>
<xsl:template match="/">
<xsl:text>[</xsl:text>
<xsl:for-each select="tokenize($Variable_01, ',')">
<xsl:variable name="i" select="position()"/>
<xsl:text>{"Group":"</xsl:text>
<xsl:value-of select="." />
<xsl:text>", "Name":"</xsl:text>
<xsl:value-of select="tokenize($Variable_02, ',')[$i]" />
<xsl:text>"}</xsl:text>
<xsl:if test="position()!=last()">
<xsl:text>,</xsl:text>
</xsl:if>
</xsl:for-each>
<xsl:text>]</xsl:text>
</xsl:template>
</xsl:stylesheet>
Result
[{"Group":"88888", "Name":"abc"},{"Group":"777777", "Name":"xyz"}]
Demo: http://xsltransform.hikmatu.com/jyH9rLV
In XSLT 3 with higher-order function for-each-pair and JSON map and array support this would be as easy as
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:math="http://www.w3.org/2005/xpath-functions/math"
xmlns:map="http://www.w3.org/2005/xpath-functions/map"
xmlns:array="http://www.w3.org/2005/xpath-functions/array"
xmlns:mf="http://example.com/mf"
exclude-result-prefixes="#all"
version="3.0">
<xsl:output method="json" indent="yes"/>
<xsl:param name="Variable_01" as="xs:string">88888,777777</xsl:param>
<xsl:param name="Variable_02" as="xs:string">abc,xyz</xsl:param>
<xsl:template match="/" name="xsl:initial-template">
<xsl:sequence
select="array {
for-each-pair(
tokenize($Variable_01, ','),
tokenize($Variable_02, ','),
function($a, $b) { map { 'Group' : $a, 'Name' : $b } }
)
}"/>
</xsl:template>
</xsl:stylesheet>
In XSLT 3 without higher-order function support for for-each-pair you could implement your own function along the lines of the defintion:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:math="http://www.w3.org/2005/xpath-functions/math"
xmlns:map="http://www.w3.org/2005/xpath-functions/map"
xmlns:array="http://www.w3.org/2005/xpath-functions/array"
xmlns:mf="http://example.com/mf"
exclude-result-prefixes="#all"
version="3.0">
<xsl:output method="json" indent="yes"/>
<xsl:function name="mf:object-for-each-pair" as="map(xs:string, xs:string)*">
<xsl:param name="seq1"/>
<xsl:param name="seq2"/>
<xsl:param name="action"/>
<xsl:if test="exists($seq1) and exists($seq2)">
<xsl:sequence select="map { 'Group' : head($seq1), 'Name' : head($seq2) }"/>
<xsl:sequence select="mf:object-for-each-pair(tail($seq1), tail($seq2))"/>
</xsl:if>
</xsl:function>
<xsl:param name="Variable_01" as="xs:string">88888,777777</xsl:param>
<xsl:param name="Variable_02" as="xs:string">abc,xyz</xsl:param>
<xsl:template match="/" name="xsl:initial-template">
<xsl:sequence select="array { mf:object-for-each-pair(tokenize($Variable_01, ','), tokenize($Variable_02, ',')) }"/>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/6qVRKxk
Finally in XSLT 2 without JSON array and map support you could use the same approach to create text output instead:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:mf="http://example.com/mf"
exclude-result-prefixes="#all"
version="2.0">
<xsl:output method="text"/>
<xsl:function name="mf:object-for-each-pair" as="xs:string*">
<xsl:param name="seq1"/>
<xsl:param name="seq2"/>
<xsl:if test="exists($seq1) and exists($seq2)">
<xsl:sequence select="concat('{ "Group" : "', $seq1[1], '", "Name" : "', $seq2[1], '" }')"/>
<xsl:sequence select="mf:object-for-each-pair(subsequence($seq1, 2), subsequence($seq2, 2))"/>
</xsl:if>
</xsl:function>
<xsl:param name="Variable_01" as="xs:string">88888,777777</xsl:param>
<xsl:param name="Variable_02" as="xs:string">abc,xyz</xsl:param>
<xsl:template match="/">
<xsl:text>[ </xsl:text>
<xsl:value-of select="mf:object-for-each-pair(tokenize($Variable_01, ','), tokenize($Variable_02, ','))" separator=", "/>
<xsl:text> ]</xsl:text>
</xsl:template>
</xsl:stylesheet>
http://xsltransform.hikmatu.com/nc4NzPX
This is somewhat crude. But it gets the job done.
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" encoding="UTF-8"/>
<xsl:variable name="Variable_01">88888,777777</xsl:variable>
<xsl:variable name="Variable_02">abc,xyz</xsl:variable>
<xsl:template match="/">
<xsl:text>[</xsl:text>
<xsl:call-template name="tokenizeString">
<!-- store anything left in another variable -->
<xsl:with-param name="list" select="$Variable_01"/>
<xsl:with-param name="list2" select="$Variable_02"/>
<xsl:with-param name="delimiter" select="','"/>
</xsl:call-template>
<xsl:text>]</xsl:text>
</xsl:template>
<xsl:template name="tokenizeString">
<!--passed template parameter -->
<xsl:param name="list"/>
<xsl:param name="list2"/>
<xsl:param name="delimiter"/>
<xsl:choose>
<xsl:when test="contains($list, $delimiter)">
<!-- get everything in front of the first delimiter -->
<xsl:text>{"Group":"</xsl:text>
<xsl:value-of select="substring-before($list,$delimiter)"/>
<xsl:text>", "Name":"</xsl:text>
<xsl:call-template name="tokenizeAnother">
<xsl:with-param name="list2" select="$list2"/>
<xsl:with-param name="delimiter" select="$delimiter"/>
</xsl:call-template>
<xsl:text>"},</xsl:text>
<xsl:call-template name="tokenizeString">
<!-- store anything left in another variable -->
<xsl:with-param name="list" select="substring-after($list,$delimiter)"/>
<xsl:with-param name="list2" select="substring-after($list2,$delimiter)"/>
<xsl:with-param name="delimiter" select="$delimiter"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:text>{"Group":"</xsl:text>
<xsl:value-of select="$list"/>
<xsl:text>", "Name":"</xsl:text>
<xsl:call-template name="tokenizeAnother">
<xsl:with-param name="list2" select="$list2"/>
<xsl:with-param name="delimiter" select="$delimiter"/>
</xsl:call-template>
<xsl:text>"}</xsl:text>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="tokenizeAnother">
<xsl:param name="list2"/>
<xsl:param name="delimiter"/>
<xsl:choose>
<xsl:when test="contains($list2, $delimiter)">
<!-- get everything in front of the first delimiter -->
<xsl:value-of select="substring-before($list2,$delimiter)"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$list2"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
See it in action here.
For completion, here's a purely XSLT 1.0 solution that does not require any extension functions:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" encoding="UTF-8"/>
<xsl:variable name="Variable_01">88888,777777</xsl:variable>
<xsl:variable name="Variable_02">abc,xyz</xsl:variable>
<xsl:template match="/">
<xsl:text>[</xsl:text>
<xsl:call-template name="tokenize-to-pairs">
<xsl:with-param name="left-text" select="$Variable_01"/>
<xsl:with-param name="right-text" select="$Variable_02"/>
</xsl:call-template>
<xsl:text>]</xsl:text>
</xsl:template>
<xsl:template name="tokenize-to-pairs">
<xsl:param name="left-text"/>
<xsl:param name="right-text"/>
<xsl:param name="delimiter" select="','"/>
<!-- output -->
<xsl:text>{"Group":"</xsl:text>
<xsl:value-of select="substring-before(concat($left-text, $delimiter), $delimiter)" />
<xsl:text>", "Name":"</xsl:text>
<xsl:value-of select="substring-before(concat($right-text, $delimiter), $delimiter)" />
<xsl:text>"}</xsl:text>
<!-- recursive call -->
<xsl:if test="contains($left-text, $delimiter)">
<xsl:text>,</xsl:text>
<xsl:call-template name="tokenize-to-pairs">
<xsl:with-param name="left-text" select="substring-after($left-text, $delimiter)"/>
<xsl:with-param name="right-text" select="substring-after($right-text, $delimiter)"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Following on from an earlier question, and this is more about xsl syntax. I want to split part of a URL variable into a new variable in xsl.
This code works when the variable is sitting part way along a URL. EG:
http://www.mysite.com/test.aspx?aVar=something&bVar=somethingMore&cVar=yetMoreStill
<xsl:variable name="testVar" select="substring-after($url, 'bVar=')"/>
<xsl:value-of select="substring-before($testVar, '&')" />
The problem is the variable can sometime sit at the end of the URL (I have no control over this) EG:
http://www.mysite.com/test.aspx?aVar=something&bVar=somethingMore
So the above code fails. Is there away I can allow for both occurrences? The end game is I'm just trying to get the value of bVar no matter where it sits within the URL. Thanks.
How about the following workaround?
<xsl:variable name="testVar" select="substring-after($url, 'bVar=')"/>
<xsl:value-of select="substring-before(concat($testVar, '&'), '&')" />
Try this. This is XSLT 1.0:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:template match="/">
<xsl:call-template name="urlResolver">
<xsl:with-param name="input" select="'http://www.mysite.com/test.aspx?aVar=something&bVar=somethingMore'" />
</xsl:call-template>
<xsl:call-template name="urlResolver">
<xsl:with-param name="input" select="'http://www.mysite.com/test.aspx?aVar=something&bVar=somethingMore&cVar=yetMoreStill'" />
</xsl:call-template>
</xsl:template>
<xsl:template name="urlResolver">
<xsl:param name="input" />
<xsl:variable name="testVar" select="substring-after($input, 'bVar=')"/>
<xsl:choose>
<xsl:when test="contains($testVar, '&')"><xsl:value-of select="substring-before($testVar, '&')" /></xsl:when>
<xsl:otherwise><xsl:value-of select="$testVar" /></xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Try to make use of tokenize (available in XSLT 2.0) like the following:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes" omit-xml-declaration="yes" method="xml" version="1.0"/>
<xsl:template match="/">
<xsl:variable name="test"><![CDATA[http://www.mysite.com/test.aspx?aVar=something&bVar=somethingMore&cVar=yetMoreStill]]></xsl:variable>
<xsl:variable name="splitURL" select="tokenize($test,'&')"/>
<xsl:variable name="bvar" select="$splitURL[starts-with(.,'bVar')]"/>
<out><xsl:value-of select="substring-after($bvar, 'bVar=')"/></out>
</xsl:template>
</xsl:stylesheet>
The currently accepted answer is generally wrong.
Try it with this URL:
http://www.mysite.com/test.aspx?subVar=something&bVar=somethingMore
and you get the wrong result: something
This question was already answered... In case you read the answer you would just reuse it and get your QString from the produced result:
<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:strip-space elements="*"/>
<xsl:param name="pUrl" select=
"'http://www.mysite.com/test.aspx?subVar=something&bVar=somethingMore'"/>
<xsl:template match="/">
<xsl:variable name="vrtfQStrings">
<xsl:call-template name="GetQueryStringParams"/>
</xsl:variable>
bVar = "<xsl:value-of select="ext:node-set($vrtfQStrings)/bVar"/>"
</xsl:template>
<xsl:template name="GetQueryStringParams">
<xsl:param name="pUrl" select="$pUrl"/>
<xsl:variable name="vQueryPart" select=
"substring-before(substring-after(concat($pUrl,'?'),
'?'),
'?')"/>
<xsl:variable name="vHeadVar" select=
"substring-before(concat($vQueryPart,'&'), '&')"/>
<xsl:element name="{substring-before($vHeadVar, '=')}">
<xsl:value-of select="substring-after($vHeadVar, '=')"/>
</xsl:element>
<xsl:variable name="vRest" select="substring-after($vQueryPart, '&')"/>
<xsl:if test="string-length($vRest) > 0">
<xsl:call-template name="GetQueryStringParams">
<xsl:with-param name="pUrl" select=
"concat('?', substring(substring-after($vQueryPart, $vHeadVar), 2))"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
When applied on any XML document (not used), this transformation produces the wanted, correct result:
bVar = "somethingMore"
I have the following XML source structure:
<turnovers>
<turnover repid="1" amount="500" rate="0.1"/>
<turnover repid="5" amount="600" rate="0.5"/>
<turnover repid="4" amount="400" rate="0.2"/>
<turnover repid="1" amount="700" rate="0.05"/>
<turnover repid="2" amount="100" rate="0.15"/>
<turnover repid="1" amount="900" rate="0.25"/>
<turnover repid="2" amount="1000" rate="0.18"/>
<turnover repid="5" amount="200" rate="0.55"/>
<turnover repid="9" amount="700" rate="0.40"/>
</turnovers>
I need an XSL:value-of select statement that will return the sum of the product of the rate attribute and the amount attribute for a given rep ID. So for rep 5 I need ((600 x 0.5) + (200 x 0.55)).
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
<xsl:template match="/turnovers">
<val>
<!-- call the sum function (with the relevant nodes) -->
<xsl:call-template name="sum">
<xsl:with-param name="nodes" select="turnover[#repid='5']" />
</xsl:call-template>
</val>
</xsl:template>
<xsl:template name="sum">
<xsl:param name="nodes" />
<xsl:param name="sum" select="0" />
<xsl:variable name="curr" select="$nodes[1]" />
<!-- if we have a node, calculate & recurse -->
<xsl:if test="$curr">
<xsl:variable name="runningsum" select="
$sum + $curr/#amount * $curr/#rate
" />
<xsl:call-template name="sum">
<xsl:with-param name="nodes" select="$nodes[position() > 1]" />
<xsl:with-param name="sum" select="$runningsum" />
</xsl:call-template>
</xsl:if>
<!-- if we don't have a node (last recursive step), return sum -->
<xsl:if test="not($curr)">
<xsl:value-of select="$sum" />
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Gives:
<val>410</val>
The two <xsl:if>s can be replaced by a single <xsl:choose>. This would mean one less check during the recursion, but it also means two additional lines of code.
In plain XSLT 1.0 you need a recursive template for this, for example:
<xsl:template match="turnovers">
<xsl:variable name="selectedId" select="5" />
<xsl:call-template name="sum_turnover">
<xsl:with-param name="turnovers" select="turnover[#repid=$selectedId]" />
</xsl:call-template>
</xsl:template>
<xsl:template name="sum_turnover">
<xsl:param name="total" select="0" />
<xsl:param name="turnovers" />
<xsl:variable name="head" select="$turnovers[1]" />
<xsl:variable name="tail" select="$turnovers[position()>1]" />
<xsl:variable name="calc" select="$head/#amount * $head/#rate" />
<xsl:choose>
<xsl:when test="not($tail)">
<xsl:value-of select="$total + $calc" />
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="sum_turnover">
<xsl:with-param name="total" select="$total + $calc" />
<xsl:with-param name="turnovers" select="$tail" />
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
This should do the trick, you'll need to do some further work to select the distinct repid's
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:variable name="totals">
<product>
<xsl:for-each select="turnovers/turnover">
<repid repid="{#repid}">
<value><xsl:value-of select="#amount * #rate"/></value>
</repid>
</xsl:for-each>
</product>
</xsl:variable>
<totals>
<total repid="5" value="{sum($totals/product/repid[#repid='5']/value)}"/>
</totals>
</xsl:template>
</xsl:stylesheet>
In XSLT 1.0 the use of FXSL makes such problems easy to solve:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:f="http://fxsl.sf.net/"
xmlns:ext="http://exslt.org/common"
exclude-result-prefixes="xsl f ext"
>
<xsl:import href="zipWith.xsl"/>
<xsl:output method="text"/>
<xsl:variable name="vMultFun" select="document('')/*/f:mult-func[1]"/>
<xsl:template match="/">
<xsl:call-template name="profitForId"/>
</xsl:template>
<xsl:template name="profitForId">
<xsl:param name="pId" select="1"/>
<xsl:variable name="vrtfProducts">
<xsl:call-template name="zipWith">
<xsl:with-param name="pFun" select="$vMultFun"/>
<xsl:with-param name="pList1" select="/*/*[#repid = $pId]/#amount"/>
<xsl:with-param name="pList2" select="/*/*[#repid = $pId]/#rate"/>
</xsl:call-template>
</xsl:variable>
<xsl:value-of select="sum(ext:node-set($vrtfProducts)/*)"/>
</xsl:template>
<f:mult-func/>
<xsl:template match="f:mult-func" mode="f:FXSL">
<xsl:param name="pArg1"/>
<xsl:param name="pArg2"/>
<xsl:value-of select="$pArg1 * $pArg2"/>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the originally posted source XML document, the correct result is produced:
310
In XSLT 2.0 the same solution using FXSL 2.0 can be expressed by an XPath one-liner:
sum(f:zipWith(f:multiply(),
/*/*[xs:decimal(#repid) eq 1]/#amount/xs:decimal(.),
/*/*[xs:decimal(#repid) eq 1]/#rate/xs:decimal(.)
)
)
The whole transformation:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:f="http://fxsl.sf.net/"
exclude-result-prefixes="f xs"
>
<xsl:import href="../f/func-zipWithDVC.xsl"/>
<xsl:import href="../f/func-Operators.xsl"/>
<!-- To be applied on testFunc-zipWith4.xml -->
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/">
<xsl:value-of select=
"sum(f:zipWith(f:multiply(),
/*/*[xs:decimal(#repid) eq 1]/#amount/xs:decimal(.),
/*/*[xs:decimal(#repid) eq 1]/#rate/xs:decimal(.)
)
)
"/>
</xsl:template>
</xsl:stylesheet>
Again, this transformation produces the correct answer:
310
Note the following:
The f:zipWith() function takes as arguments a function fun() (of two arguments) and two lists of items having the same length. It produces a new list of the same length, whose items are the result of the pair-wise application of fun() on the corresponding k-th items of the two lists.
f:zipWith() as in the expression takes the function f:multiply() and two sequences of corresponding "ammount" and "rate" attributes. The sesult is a sequence, each item of which is the product of the corresponding "ammount" and "rate".
Finally, the sum of this sequence is produced.
There is no need to write an explicit recursion and it is also guaranteed that the behind-the scenes recursion used within f:zipWith() is never going to crash (for all practical cases) with "stack overflow"
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
version="2.0">
<xsl:variable name="repid" select="5" />
<xsl:template match="/">
<xsl:value-of select=
"sum(for $x in /turnovers/turnover[#repid=$repid] return $x/#amount * $x/#rate)"/>
</xsl:template>
</xsl:stylesheet>
You can do this if you just need the value and not xml.
The easiest way to do it in XSLT is probably to use programming language bindings, so that you can define your own XPath functions.
I have this xslt:
<xsl:template name="dumpDebugData">
<xsl:param name="elementToDump" />
<xsl:for-each select="$elementToDump/#*">
<xsl:text>
</xsl:text> <!-- newline char -->
<xsl:value-of select="name()" /> : <xsl:value-of select="." />
</xsl:for-each>
</xsl:template>
i want to display every element (as in name/value), how do i call this template?
Since the template expects a node set, you must do:
<xsl:call-template name="dumpDebugData">
<xsl:with-param name="elementToDump" select="some/xpath" />
</xsl:call-template>
Try something like this:
<xsl:call-template name="dumpDebugData">
<xsl:with-param name="elementToDump">foo</xsl:with-param>
</xsl:call-template>
The original answer does not use the parameter. It only works if the paramater = the current element. This takes the parameter into account.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output indent="yes" />
<xsl:strip-space elements="*" />
<xsl:template match="element()">
<xsl:call-template name="dumpDebugData">
<xsl:with-param name="elementToDump" select="." />
</xsl:call-template>
<xsl:apply-templates />
</xsl:template>
<xsl:template name="dumpDebugData">
<xsl:param name="elementToDump" />
Node:
<xsl:value-of select="name($elementToDump)" />
:
<xsl:value-of select="text($elementToDump)" />
<xsl:for-each select="$elementToDump/#*">
Attribute:
<xsl:value-of select="name()" />
:
<xsl:value-of select="." />
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
There are a number of issues in your original XSLT, so I worked through it and got you the following code which does what you want I believe:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output indent="yes" />
<xsl:strip-space elements="*" />
<xsl:template match="element()">
<xsl:call-template name="dumpDebugData">
<xsl:with-param name="elementToDump" select="." />
</xsl:call-template>
<xsl:apply-templates />
</xsl:template>
<xsl:template name="dumpDebugData">
<xsl:param name="elementToDump" />
Node:
<xsl:value-of select="name()" />
:
<xsl:value-of select="text()" />
<xsl:for-each select="attribute::*">
Attribute:
<xsl:value-of select="name()" />
:
<xsl:value-of select="." />
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>