Audience value not exporting to output file in XSLT transform - xslt

I'm having trouble getting the value in the audience tag to appear in my output file while running an XSLT transform on a DITA map.
This is the snippet of my input with audience="Alaska":
<topicgroup>
<topichead navtitle="Alaska Property and Casualty Adjuster Law" locktitle="yes" audience="Alaska">
<topicgroup>
<mapref href="Qbank/maps/Adj_Unit_AK.ditamap" format="ditamap"/>
</topicgroup>
</topichead>
</topicgroup>
Here's the portion of my XSLT script that contains the <!--Add audience--> section:
<xsl:template match="topichead">
<xsl:param name="all_topic_refs" select="topicref[#type='topic' or #type='concept']" as="element()*"/>
<xsl:param name="all_question_refs" select="topicref[#type='question']" as="element()*"/>
<xsl:param name="all_exam_refs" select="topicref[#type='exam']" as="element()*"/>
<xsl:param name="audience" select="topichead[#audience='property' or #type='concept']" as="element()*"/>
<xsl:param name="all_exam_maps" select="descendant-or-self::examMap" as="element()*"/>
<xsl:copy>
<xsl:attribute name="navtitle">
<xsl:choose>
<xsl:when test="navtitle">
<xsl:apply-templates select="navtitle"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="#navtitle"/>
</xsl:otherwise>
</xsl:choose>
</xsl:attribute>
<!--Add audience-->
<xsl:attribute name="audience">
<xsl:value-of select="#audience"/>
</xsl:attribute>
<xsl:apply-templates select="*[not(self::examMap[#assessment_type='test-exam-primary']) and not(self::navtitle)]">
<xsl:with-param name="all_topic_refs" select="$all_topic_refs"/>
<xsl:with-param name="all_question_refs" select="$all_question_refs"/>
<xsl:with-param name="all_exam_refs" select="$all_exam_refs"/>
<xsl:with-param name="all_exam_maps" select="$all_exam_maps"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
The output is giving me this with empty quotes in the Audience tag:
<topichead navtitle="Alaska Property and Casualty Adjuster Law" audience="">
<topicref href="questions/inslic_assess_Adj_AK.dita"/>
</topichead>
It pulls over the navtitle correctly but I can't figure why it won't pull over the audience input. Any help would be most appreciated.
Here's the full input file:
<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE bookmap PUBLIC "-//OASIS//DTD DITA BookMap//EN" "bookmap.dtd" []>
<bookmap>
<booktitle>
<mainbooktitle>Property & Casualty Insurance Adjuster Qbank</mainbooktitle>
</booktitle>
<bookmeta>
<category>course</category>
<!-- <category> will describe type of map it is (qbank, course w/final exam, final exam, etc) -->
<prodinfo>
<prodname>Insurance Adjuster Licensing</prodname>
<vrmlist>
<vrm version="2022"/>
</vrmlist>
<brand>lic</brand>
<series>adjqbank</series>
</prodinfo>
<bookid>
<bookpartno>00012345</bookpartno>
</bookid>
</bookmeta>
<!--
# Transform Type: LMS_Transform
# DITAVAL: USstate_National_Short_Adjuster_Digital_InsLic.ditaval
# File Path to Final Zip: G:\Shared drives\DITA Zip Files\Insurance Licensing\Property & Casualty\20535_National Insurance Adjuster Solution
# Reltable: N
# Glossary notes: Y/N
# Run from main folder?: N
# PDF: Standard
-->
<chapter href="Qbank/topics/Adj_Qbank_1.dita" locktitle="yes">
<topicmeta>
<navtitle>Property & Casualty Insurance Adjuster</navtitle>
</topicmeta>
<topicgroup>
<topichead navtitle="Introduction to Insurance" locktitle="yes">
<topicgroup>
<mapref href="Qbank/maps/Adj_Unit_1.ditamap" format="ditamap"/>
</topicgroup>
</topichead>
</topicgroup>
<topicgroup>
<topichead navtitle="Contracts" locktitle="yes">
<topicgroup>
<mapref href="Qbank/maps/Adj_Unit_2.ditamap" format="ditamap"/>
</topicgroup>
</topichead>
</topicgroup>
</chapter>
<chapter href="Qbank/topics/Adj_Qbank_State_Specific.dita" locktitle="yes">
<topicmeta>
<navtitle>State Regulations</navtitle>
</topicmeta>
<topicgroup>
<topichead audience="Alabama" navtitle="Alabama Property and Casualty Adjuster Law" locktitle="yes">
<topicgroup>
<mapref href="Qbank/maps/Adj_Unit_AL.ditamap" format="ditamap"/>
</topicgroup>
</topichead>
</topicgroup>
<topicgroup>
<topichead navtitle="Alaska Property and Casualty Adjuster Law" locktitle="yes" audience="Alaska">
<topicgroup>
<mapref href="Qbank/maps/Adj_Unit_AK.ditamap" format="ditamap"/>
</topicgroup>
</topichead>
</topicgroup>
<topicgroup>
<topichead navtitle="Arizona Property and Casualty Adjuster Law" locktitle="yes" audience="Arizona">
<topicgroup>
<mapref href="Qbank/maps/Adj_Unit_AZ.ditamap" format="ditamap"/>
</topicgroup>
</topichead>
</topicgroup>
</chapter>
</bookmap>
Here's the full script with the non-related stuff removed:
<?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:functx="http://www.functx.com"
xmlns:ditaarch="http://dita.oasis-open.org/architecture/2005/"
exclude-result-prefixes="xs functx ditaarch" version="2.0">
<xsl:import href="functx-1.0.xsl"/>
<xsl:import href="new_topic_task.xsl"/>
<xsl:param name="BASE_DIR"/>
<xsl:param name="OUT_DIR"/>
<xsl:output encoding="UTF-8" method="xml" indent="yes"
doctype-public="-//OASIS//DTD DITA Map//EN" doctype-system="map.dtd"/>
<xsl:strip-space elements="*"/>
<!-- Create a global variable so that we can refer back to the supermap. -->
<xsl:variable name="supermap" select="/supermap" as="element()*"/>
<!-- Create a global variable so that we can refer back to the los. -->
<xsl:variable name="los_summary" select="/supermap/los_summary" as="element()*"/>
<xsl:template match="/">
<xsl:apply-templates select="supermap"/>
</xsl:template>
<xsl:template match="supermap">
<map>
<xsl:attribute name="title">
<xsl:value-of select="#title"/>
</xsl:attribute>
<xsl:attribute name="id">
<xsl:value-of select="#id"/>
</xsl:attribute>
<!-- Handle topicheads. -->
<xsl:apply-templates select="topichead"/>
<!-- Create the final exam map outside all other topic heads. -->
<xsl:apply-templates select="topichead[#unit_map_type='final-exam']/examMap">
</xsl:apply-templates>
<!-- Handle LOS. -->
<xsl:apply-templates select="los_summary"/>
</map>
</xsl:template>
<!-- Top level topichead. -->
<xsl:template match="topichead">
<!-- Made these parameters so that subordinate topicheads behave correctly...I think. -->
<xsl:param name="all_topic_refs" select="topicref[#type='topic' or #type='concept']" as="element()*"/>
<xsl:param name="all_question_refs" select="topicref[#type='question']" as="element()*"/>
<xsl:param name="all_exam_refs" select="topicref[#type='exam']" as="element()*"/>
<xsl:param name="audience" select="topichead[#audience='property' or #type='concept']" as="element()*"/>
<!-- Added for QBank -->
<xsl:param name="all_exam_maps" select="descendant-or-self::examMap" as="element()*"/>
<!-- [SP] 2015-01-13: Don't display topichead in qbank output. -->
<xsl:choose>
<xsl:when test="#unit_map_type = 'qbank'">
<xsl:apply-templates select="*[not(self::examMap[#assessment_type='test-exam-primary'])]">
<xsl:with-param name="all_topic_refs" select="$all_topic_refs"/>
<xsl:with-param name="all_question_refs" select="$all_question_refs"/>
<xsl:with-param name="all_exam_refs" select="$all_exam_refs"/>
<xsl:with-param name="all_exam_maps" select="$all_exam_maps"/>
</xsl:apply-templates>
</xsl:when>
<xsl:otherwise>
<xsl:copy>
<xsl:attribute name="navtitle">
<xsl:choose>
<xsl:when test="navtitle">
<xsl:apply-templates select="navtitle"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="#navtitle"/>
</xsl:otherwise>
</xsl:choose>
</xsl:attribute>
<!--Add audience-->
<xsl:attribute name="audience">
<xsl:value-of select="#audience"/>
</xsl:attribute>
<xsl:apply-templates select="*[not(self::examMap[#assessment_type='test-exam-primary']) and not(self::navtitle)]">
<xsl:with-param name="all_topic_refs" select="$all_topic_refs"/>
<xsl:with-param name="all_question_refs" select="$all_question_refs"/>
<xsl:with-param name="all_exam_refs" select="$all_exam_refs"/>
<xsl:with-param name="all_exam_maps" select="$all_exam_maps"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="#*">
<xsl:copy/>
</xsl:template>
</xsl:stylesheet>

I will assume your XSLT stylesheet is applied at a certain stage from a plugin when publishing the DITA XML content using the DITA Open Toolkit. You can add an xsl:message inside the xsl:template which matches the topichead:
<xsl:message><xsl:copy-of select="."/></xsl:message>
Then look in the console output when publishing to see exactly on what XML element the template is applied.
The DITA Open Toolkit publishing engine has a pre-processing step which applies filtering and removes all profiling attributes from all elements.
https://www.dita-ot.org/dev/reference/preprocess-debugfilter.html
To preserve the profiling attributes when publishing you need to specify a DITAVAL filter file which specifies that the profiling attributes should pass through to the published output. Something like:
<val>
<prop action="passthrough" att="audience"/>
</val>
https://www.oxygenxml.com/dita/1.3/specs/langRef/ditaval/ditaval-prop.html

Related

How to get row count and total sum of fields value through XSLT?

I have a file format requirement as following where I need to get row count and total sum of the field. In the example there are three rows with the column that has amount values and in the fourth row, i would need total row count and sum of the amounts to be populated.
Alex, Scot 10091978 90.00
Brad, Ander 23061976 40.00
Rodney, Philips 04091983 35.00
03 165.00
XML Input
<wd:Report_Data
xmlns:wd="urn:com.workday.report/INT001_Test_Reward_Report">
<wd:Report_Entry>
<wd:Worker_group>
<wd:Employee_ID>12344</wd:Employee_ID>
<wd:dateOfBirth>1975-05-06</wd:dateOfBirth>
<wd:lastName>Alex</wd:lastName>
<wd:firstName>Scot</wd:firstName>
<wd:Pay_Frequency>M1</wd:Pay_Frequency>
</wd:Worker_group>
<wd:Payroll_Result_group>
<wd:Payment_Date>2019-04-02</wd:Payment_Date>
</wd:Payroll_Result_group>
<wd:Payroll_Result_Line_group>
<wd:Result_Line_Amount>40</wd:Result_Line_Amount>
</wd:Payroll_Result_Line_group>
</wd:Report_Entry>
<wd:Report_Entry>
<wd:Worker_group>
<wd:Employee_ID>12355</wd:Employee_ID>
<wd:dateOfBirth>1958-05-06</wd:dateOfBirth>
<wd:lastName>Hyrus</wd:lastName>
<wd:firstName>Marike</wd:firstName>
<wd:Pay_Frequency>M1</wd:Pay_Frequency>
</wd:Worker_group>
<wd:Payroll_Result_group>
<wd:Payment_Date>2019-04-04</wd:Payment_Date>
</wd:Payroll_Result_group>
<wd:Payroll_Result_Line_group>
<wd:Result_Line_Amount>10</wd:Result_Line_Amount>
</wd:Payroll_Result_Line_group>
</wd:Report_Entry>
<wd:Report_Entry>
<wd:Worker_group>
<wd:Employee_ID>12366</wd:Employee_ID>
<wd:dateOfBirth>1986-09-10</wd:dateOfBirth>
<wd:lastName>Elite</wd:lastName>
<wd:firstName>Samsung</wd:firstName>
<wd:Pay_Frequency>M1</wd:Pay_Frequency>
</wd:Worker_group>
<wd:Payroll_Result_group>
<wd:Payment_Date>2019-04-02</wd:Payment_Date>
</wd:Payroll_Result_group>
<wd:Payroll_Result_Line_group>
<wd:Result_Line_Amount>30</wd:Result_Line_Amount>
</wd:Payroll_Result_Line_group>
</wd:Report_Entry>
</wd:Report_Data>
XSLT I have tried so far
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:wd="urn:com.workday.report/INT001_Test_Reward_Report"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:this="urn:this-stylesheet"
version="2.0" exclude-result-prefixes="xs this">
<!--**************************************************************************************-->
<!-- Date XSLT Attachment Design -->
<!-- 06-17-2019 Transformation to transform file format from xml to .txt(fixed ) -->
<!--**************************************************************************************-->
<!-- 06-17-2019 Initial Version -->
<!--**************************************************************************************-->
<!--The final output file will be text formatted with an encoding of *NA*-->
<xsl:output encoding="UTF-8" method="text" use-character-maps="custom-char-filter" />
<!--declare global variables here such as delimiter and linefeed-->
<xsl:variable name="vDelimiter" select="''"/>
<xsl:variable name="linefeed" select="'
'" />
<xsl:variable name="linefeed2" select="'
'"/>
<xsl:variable name="vWrapQuotes" select="false()"/>
<xsl:variable name="vDateFormat" select="'[Y0001][M01][D01]'"/>
<xsl:variable name="vDateTimeFormat" select="'[D01]-[MN,*-3]-[Y0001]T[H01]:[m01]:[s01]'"/>
<xsl:variable name="pDateValue" select="current-date()"/>
<xsl:variable name="SumAmount" select="0"/>
<xsl:variable name="i" select="0"/>
<!--Root Template-->
<xsl:template match="wd:Report_Data">
<!--declare root variables here such as current datetime or other repeating values-->
<xsl:value-of select="this:setOutput(this:formatDate($pDateValue))" />
<xsl:value-of select="$linefeed"/>
<!--Process detail records-->
<xsl:apply-templates select="wd:Report_Entry" />
<xsl:text>T</xsl:text>
<xsl:value-of select="$i"/>
<xsl:value-of select="$SumAmount"/>
</xsl:template>
<!--Detail Record Template-->
<xsl:template match="wd:Report_Entry">
<!--
Detail Record
-->
<!--Record Type - -->
<xsl:text>D</xsl:text>
<xsl:value-of select="this:setOutput(this:fixedWidth(wd:Worker_group/wd:Employee_ID, ' ', 8, 'left'))" />
<xsl:value-of select="this:setOutput(this:fixedWidth((this:formatDate(wd:Worker_group/wd:dateOfBirth)), ' ', 8, 'left'))" />
<xsl:value-of select="this:setOutput(this:fixedWidth(wd:Worker_group/wd:lastName, ' ', 40,'left'))" />
<xsl:value-of select="this:setOutput(this:fixedWidth(wd:Worker_group/wd:firstName, ' ', 40, 'left'))" />
<xsl:value-of select="this:setOutput(this:fixedWidth(wd:Payroll_Result_Line_group/wd:Result_Line_Amount, ' ', 9, 'left'))" />
<xsl:value-of select="this:setOutput(this:fixedWidth(wd:Worker_group/wd:Pay_Frequency, ' ', 2, 'left'))" />
<xsl:value-of select="this:setOutput(this:fixedWidth((this:formatDate(wd:Payroll_Result_group/wd:Payment_Date)), ' ', 8, 'left'))" />
<xsl:variable name="Amount" select="this:setOutput(wd:Payroll_Result_Line_group/wd:Result_Line_Amount)"/>
<xsl:value-of select="$Amount"/>
<xsl:variable name="i" select="position()"/>
<xsl:value-of select="$i"/>
<xsl:variable name="SumAmount" select="$SumAmount + $Amount"/>
<xsl:value-of select="$linefeed" />
</xsl:template>
<!--******************************************************************************************** -->
<!--This function returns the input value followed by a delmiter -->
<!--******************************************************************************************** -->
<xsl:function name="this:setOutput">
<xsl:param name="value" />
<xsl:value-of select="this:setOutput($value,false())"/>
</xsl:function>
<!--******************************************************************************************** -->
<!--This function returns the input value followed by a delmiter -->
<!--******************************************************************************************** -->
<xsl:function name="this:setOutput">
<xsl:param name="value" />
<xsl:param name="finalValue" as="xs:boolean"/>
<xsl:variable name="vWrappedValue">
<xsl:value-of select="if($vWrapQuotes = false()) then $value else this:wrapQuotes($value)"/>
</xsl:variable>
<xsl:value-of select="if ($finalValue = false()) then concat(string($vWrappedValue),$vDelimiter) else $vWrappedValue"/>
</xsl:function>
<!--******************************************************************************************** -->
<!--This function is designed to wrap a string in quotes if required -->
<!--******************************************************************************************** -->
<xsl:function name="this:wrapQuotes">
<xsl:param name="field"/>
<xsl:value-of select="'"'"/>
<xsl:value-of select="replace($field,'"','""')"/>
<xsl:value-of select="'"'"/>
</xsl:function>
<!--******************************************************************************************** -->
<!--This function is designed to pad a string to the left or right to a fixed number of characters-->
<!-- str: a string input -->
<!-- chr: a character used for padding -->
<!-- len: a number of defining the total length of the output -->
<!-- dir: the direction of the alignment default is left; right if specified) -->
<!-- Note: if something is aligned left it will be padded on the right and vice versa -->
<!--******************************************************************************************** -->
<xsl:function name="this:padString">
<xsl:param name="str" />
<xsl:param name="chr" />
<xsl:param name="len" />
<xsl:param name="dir" />
<xsl:variable name="padLength">
<xsl:value-of select="$len - string-length($str)" />
</xsl:variable>
<xsl:variable name="pad">
<xsl:for-each select="1 to $padLength">
<xsl:value-of select="$chr" />
</xsl:for-each>
</xsl:variable>
<xsl:value-of select="if ($dir = 'right') then concat($str,$pad) else concat($pad,$str)" />
</xsl:function>
<!--******************************************************************************************** -->
<!--This function is designed to align a string to the left or right to a fixed number of characters-->
<!-- str: a string input -->
<!-- chr: a character used for padding -->
<!-- len: a number of defining the total length of the output -->
<!-- dir: the direction of the alignment default is left; right if specified) -->
<!-- Note: if something is aligned left it will be padded on the right and vice versa -->
<!--******************************************************************************************** -->
<xsl:function name="this:fixedWidth">
<xsl:param name="str" />
<xsl:param name="chr" />
<xsl:param name="len" />
<xsl:param name="dir" />
<xsl:variable name="padLength">
<xsl:value-of select="$len - string-length($str)" />
</xsl:variable>
<xsl:variable name="pad">
<xsl:for-each select="1 to $padLength">
<xsl:value-of select="$chr" />
</xsl:for-each>
</xsl:variable>
<xsl:value-of select="if ($dir = 'right') then substring(concat($pad,$str),1,$len) else substring(concat($str,$pad),1,$len)" />
</xsl:function>
<!--******************************************************************************************** -->
<!--This function can be modified as needed for the appropriate date format -->
<!--******************************************************************************************** -->
<xsl:function name="this:formatDate">
<xsl:param name="pDateValue" />
<xsl:value-of select="if (xs:string($pDateValue) = '') then '' else format-date(xs:date($pDateValue), $vDateFormat)" />
</xsl:function>
<xsl:function name="this:formatDateTime">
<xsl:param name="pDateTimeValue" />
<xsl:value-of select="if ($pDateTimeValue = '') then '' else format-dateTime(xs:dateTime($pDateTimeValue),$vDateTimeFormat )" />
</xsl:function>
<!--************************************************************************************************* -->
<!--Character Maps can be used to replace special and/or foreign characters where appropriate -->
<!--The big avantage is that these don't have to be called explicitly, the processor will handle this -->
<!--while writing the output. -->
<!--************************************************************************************************* -->
<xsl:character-map name="custom-char-filter">
<xsl:output-character character="*" string="" />
<xsl:output-character character=":" string="" />
<xsl:output-character character="^" string="" />
<xsl:output-character character="~" string="" />
</xsl:character-map>
</xsl:stylesheet>
I have also tried with few xslt functions (Total count, total row count),
but nothing seems working.
Thanks,
Jithendra.
Consider the following simplified example:
XML
<wd:Report_Data xmlns:wd="urn:com.workday.report/INT001_Test_Reward_Report">
<wd:Report_Entry>
<wd:Worker_group>
<wd:lastName>Alex</wd:lastName>
<wd:firstName>Scot</wd:firstName>
</wd:Worker_group>
<wd:Payroll_Result_Line_group>
<wd:Result_Line_Amount>90</wd:Result_Line_Amount>
</wd:Payroll_Result_Line_group>
</wd:Report_Entry>
<wd:Report_Entry>
<wd:Worker_group>
<wd:lastName>Brad</wd:lastName>
<wd:firstName>Ander</wd:firstName>
</wd:Worker_group>
<wd:Payroll_Result_Line_group>
<wd:Result_Line_Amount>40</wd:Result_Line_Amount>
</wd:Payroll_Result_Line_group>
</wd:Report_Entry>
<wd:Report_Entry>
<wd:Worker_group>
<wd:lastName>Rodney</wd:lastName>
<wd:firstName>Philips</wd:firstName>
</wd:Worker_group>
<wd:Payroll_Result_Line_group>
<wd:Result_Line_Amount>35</wd:Result_Line_Amount>
</wd:Payroll_Result_Line_group>
</wd:Report_Entry>
</wd:Report_Data>
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:wd="urn:com.workday.report/INT001_Test_Reward_Report">
<xsl:output method="text" encoding="UTF-8" />
<xsl:template match="/wd:Report_Data">
<!-- records -->
<xsl:for-each select="wd:Report_Entry">
<xsl:value-of select="wd:Worker_group/wd:lastName"/>
<xsl:text>, </xsl:text>
<xsl:value-of select="wd:Worker_group/wd:firstName"/>
<xsl:text> </xsl:text>
<xsl:value-of select="wd:Payroll_Result_Line_group/wd:Result_Line_Amount"/>
<xsl:text>
</xsl:text>
</xsl:for-each>
<!-- summary -->
<xsl:value-of select="count(wd:Report_Entry)"/>
<xsl:text> </xsl:text>
<xsl:value-of select="sum(wd:Report_Entry/wd:Payroll_Result_Line_group/wd:Result_Line_Amount)"/>
</xsl:template>
</xsl:stylesheet>
Result
Alex, Scot 90
Brad, Ander 40
Rodney, Philips 35
3 165

XSL : Replace value inside tag

Input:
<Remarks>Random data## B2B## abc,controls,free text ## B2B## random data</Remarks>
The XSL should replace
"## B2B## abc,controls,free text ## B2B##"
in the Remarks tag with
"value1:abc,value2:controls,value3:free text"
Desired output:
<Remarks>Random data,value1:abc,value2:controls,value3:free text,random data</Remarks>
Note : the values inside ## B2B## tags are unknown and change everytime, for now I gave the sample values.
Assuming there will always be exactly one "placeholder" delimited by ## B2B##, and that it will always contain exactly 3 comma-delimited "tokens", you could do this quite simply by:
<xsl:template match="Remarks">
<xsl:copy>
<xsl:value-of select="substring-before(., '## B2B##')" />
<xsl:variable name="placeholder" select="substring-before(substring-after(., '## B2B##'), '## B2B##')" />
<xsl:text> value1: </xsl:text>
<xsl:value-of select="substring-before($placeholder, ',')" />
<xsl:text> value2: </xsl:text>
<xsl:value-of select="substring-before(substring-after($placeholder, ','), ',')" />
<xsl:text> value3: </xsl:text>
<xsl:value-of select="substring-after(substring-after($placeholder, ','), ',')" />
<xsl:value-of select="substring-after(substring-after(., '## B2B##'), '## B2B##')" />
</xsl:copy>
</xsl:template>
This solution is somewhat complex. It uses one template to extract the string between the tags and another to separate this string by the delimiter. What string is replaced by what other string is outsourced to another file containing the mapping (replacement.xml). Because XSLT-1.0 returns RTFs(Resulting Tree Fragments) in variables, the replacement is hardcoded in the template. In XSLT-2.0 you could use a variable for that making the solution more flexible.
Here is the replacement.xml containing the mappings:
<?xml version="1.0" encoding="UTF-8"?>
<replacement>
<r src="abc">value1</r>
<r src="controls">value2</r>
<r src="free text">value3</r>
</replacement>
And the XSLT incorporating these mappings into the string:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/lines/Remarks">
<xsl:variable name="tagName" select="'## B2B##'" />
<xsl:variable name="subStrToReplace">
<xsl:call-template name="strInTag">
<xsl:with-param name="str" select="text()" />
<xsl:with-param name="tagName" select="$tagName" />
</xsl:call-template>
</xsl:variable>
<xsl:variable name="replacementString">
<xsl:call-template name="splitAndReplace">
<xsl:with-param name="pText" select="$subStrToReplace" />
</xsl:call-template>
</xsl:variable>
<Remarks>
<xsl:value-of select="concat(normalize-space(substring-before(text(),$tagName)), ',',$replacementString, normalize-space(substring-after(substring-after(text(),$tagName),$tagName)))" />
</Remarks>
</xsl:template>
<xsl:template name="strInTag">
<xsl:param name="str" />
<xsl:param name="tagName" />
<xsl:value-of select="normalize-space(substring-before(substring-after($str,$tagName),$tagName))" />
</xsl:template>
<xsl:template name="splitAndReplace">
<xsl:param name="pText"/>
<xsl:if test="string-length($pText) > 0">
<xsl:variable name="vNextItem" select="substring-before(concat($pText, ','), ',')"/>
<xsl:value-of select="concat($vNextItem,':',document('replacement.xml')/replacement/r[#src = $vNextItem]/text(),',')"/>
<xsl:call-template name="splitAndReplace">
<xsl:with-param name="pText" select="substring-after($pText, ',')"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>

updating element name and value using XSLT

In context to my earlier question at "XSLT transformation from XML to XML document". Further i was trying
to rename the "<value>" element to "<sName>" along with updating the value to "ABC" if the value of "dataset/type/text() ='test'". I wrote below code
for the same but it was not working as expected. please help.
Source XML-
<soapenv:Header/>
<soapenv:Body>
<v1:QueryRequest version="1">
<subject>
<dataList>
<!--1 or more repetitions:-->
<dataset>
<type>company</type>
<value>abc</value>
</dataset>
<dataset>
<type>user</type>
<value>xyz</value>
</dataset>
</dataList>
</subject>
<testList>
<!--1 or more repetitions:-->
<criteria>
<type>test</type>
<value>1234</value>
</criteria>
<criteria>
<type>test2</type>
<value>false</value>
</criteria>
</testList>
</v1:QueryRequest>
</soapenv:Body>
</soapenv:Envelope>
XSL file :-
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:old="http://www.abc.com/s/v1.0"
exclude-result-prefixes="old"
xmlns:v2="http://www.abc.com/s/v2.0">
<xsl:output method="xml" encoding="utf-8" indent="yes"
version="1.0" />
<xsl:param name="newversion" select="'2.0'" />
<xsl:param name="ABC" select="'ABC'" />
<xsl:param name="XYZ" select="'XYZ'" />
<xsl:param name="DFG" select="'DFG'" />
<!-- fix namespace declarations on root element -->
<xsl:template match="/*">
<xsl:element name="{name()}" namespace="{namespace-uri()}">
<!-- copy the v2: binding from the stylesheet document -->
<xsl:copy-of select="document('')/xsl:stylesheet/namespace::v2" />
<xsl:apply-templates select="#* | node()" />
</xsl:element>
</xsl:template>
<!-- replace namespace of elements in old namespace -->
<xsl:template match="old:*">
<xsl:element name="v2:{local-name()}">
<xsl:apply-templates select="#* | node()" />
</xsl:element>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="old:QueryRequest/#version">
<xsl:attribute name="version">
<xsl:value-of select="$newversion" />
</xsl:attribute>
</xsl:template>
<xsl:template match="criteria[type='service']/value">
<xsl:choose>
<xsl:when test="criteria[type='service']/value/text()='1234'">
<xsl:element name="sName">
<xsl:value-of select="$ABC"/>
</xsl:element>
</xsl:when>
<xsl:when test="criteria[type='service']/value/text()='5464'">
<xsl:element name="sName">
<xsl:value-of select="$XYZ"/>
</xsl:element>
</xsl:when>
<xsl:when test="criteria[type='service']/value/text()='8755'">
<xsl:element name="sName">
<xsl:value-of select="$DFG"/>
</xsl:element>
</xsl:when>
</xsl:choose>
</xsl:template>
<xsl:template match="type/text()">
<xsl:value-of
select="translate(., 'abcdefghijklmnopqrstuvwxyz','ABCDEFGHIJKLMNOPQRSTUVWXYZ')" />
</xsl:template>
</xsl:stylesheet>
Expected Output:
<testList>
<criteria>
<type>test</type>
<sName>ABC</sName>
</criteria>
<criteria>
<type>test2</type>
<value>false</value>
</criteria>
</testList>
Within a
<xsl:template match="criteria[type='service']/value">
the value element is the current context node, so your other XPath expressions need to be relative to that, i.e. instead of
<xsl:when test="criteria[type='service']/value/text()='1234'">
you would just use
<xsl:when test="text()='1234'">
or better
<xsl:when test=".='1234'">

XSLT 1.0 group by year and add the not existing year

I have xml as below(using xslt 1.0):
<?xml version="1.0" encoding="utf-8"?>
<receives>
<receive>
<Year>2013</Year>
<money>120</money>
</receive>
<receive>
<Year>2013</Year>
<money>150</money>
</receive>
<receive>
<Year>2014</Year>
<money>130</money>
</receive>
<receive>
<Year>2011</Year>
<money>120</money>
</receive>
</receives>
I want to group-by the money by year, if the year is not in the list(as above xml, there is no 2011), I need to put 2012 in the result with totalamount=0 as following:
<year>2011</year>
<totalamount>120</totalamount>
<year>2012</year>
<totalamount>0</totalamount>
<year>2013</year>
<totalamount>270</totalamount>
<year>2014</year>
<totalamount>130</totalamount>
Cureently, I have finished the xslt as following:
<xsl:key name="receive-key" match="receive" use="Year" />
<xsl:template match="/receives">
<xsl:for-each
select="receive[generate-id() = generate-id(key('receive-key', Year))]">
<xsl:sort select="../receive[Year = current()/Year]/Year"></xsl:sort>
<year>
<xsl:value-of select="../receive[Year = current()/Year]/Year" />
</year>
<totalamount>
<xsl:value-of select="sum(../receive[Year = current()/Year]/money)" />
</totalamount>
</xsl:for-each>
</xsl:template>
This can only group the money by the existing year:
<year>2011</year>
<totalamount>120</totalamount>
<year>2013</year>
<totalamount>270</totalamount>
<year>2014</year>
<totalamount>130</totalamount>
Any idea about how to insert
<year>2012</year>
<totalamount>0</totalamount>
into the result?
Many thanks!
One way to do this could be with a named template, which is called with a parameter of a year. If this year does not exist in the key, output an empty value, and call it for the next year.
<xsl:template name="Year">
<xsl:param name="Year"/>
<xsl:if test="not(key('receive-key', $Year))">
<year>
<xsl:value-of select="$Year"/>
</year>
<totalamount>0</totalamount>
<xsl:call-template name="Year">
<xsl:with-param name="Year" select="$Year + 1"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
So, as soon as it finds a year in the key, it will stop outputting the missing years.
One other thing to note, is one of the expressions in your XSLT
<xsl:value-of select="../receive[Year = current()/Year]/Year" />
This can actually be simplified to just this!
<xsl:value-of select="Year"/>
Similarly, you can change your sum to make use of the xsl:key for efficiency
<xsl:value-of select="sum(key('receive-key', Year)/money)"/>
Here is the full XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:key name="receive-key" match="receive" use="Year"/>
<xsl:template match="/receives">
<xsl:for-each select="receive[generate-id() = generate-id(key('receive-key', Year))]">
<xsl:sort select="Year"/>
<year>
<xsl:value-of select="Year"/>
</year>
<totalamount>
<xsl:value-of select="sum(key('receive-key', Year)/money)"/>
</totalamount>
<xsl:if test="position() != last()">
<xsl:call-template name="Year">
<xsl:with-param name="Year" select="number(Year) + 1"/>
</xsl:call-template>
</xsl:if>
</xsl:for-each>
</xsl:template>
<xsl:template name="Year">
<xsl:param name="Year"/>
<xsl:if test="not(key('receive-key', $Year))">
<year>
<xsl:value-of select="$Year"/>
</year>
<totalamount>0</totalamount>
<xsl:call-template name="Year">
<xsl:with-param name="Year" select="$Year + 1"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
When applied to your XML, the following is output
<year>2011</year>
<totalamount>120</totalamount>
<year>2012</year>
<totalamount>0</totalamount>
<year>2013</year>
<totalamount>270</totalamount>
<year>2014</year>
<totalamount>130</totalamount>

Using XSLT to generate nodes based on pipe-delimited attributes

(first off: I'm terribly sorry that you have to look at this document structure; it's hideous)
I have the following XML document:
<MENUS>
<MENU id="192748" servedate="20120213" mealid="3" mealname="Lunch" menuname="Cafeteria" menuid="26" totalcount="200">Cafeteria</MENU>
<RECIPES>
<NUTRIENTS>Calories~Energy (kcal)~kcal|Protein~Protein~gm|Fat~Fat~gm|Carbs~Total Carbohydrates~gm|Cholestrol~Cholesterol~mg|Calcium~Calcium~mg|Sodium~Sodium~mg|Iron~Iron~mg|</NUTRIENTS>
<RECIPE id="6461-200" plucode="" shortname="Chipotle Spinach" numservings="100" portion="4 ounces" isselected="0" ismainitem="0" group="On the Side" publishingdescription="Chipotle Spinach" publishingtext="" enticingdescription="" price="1.53" category="Vegetables" productionarea="Hot Production" nutrients="152|2.3|13.8|6.5|0|74|346|1.85|" nutrientsuncertain="0|0|0|0|0|0|0|0|">Chipotle Spinach,4U</RECIPE>
<RECIPE id="6586-300" plucode="" shortname="Asiago Crusted Chix" numservings="120" portion="3-3/4 ounces" isselected="0" ismainitem="0" group="Main Fare" publishingdescription="Asiago Crusted Chicken" publishingtext="" enticingdescription="" price="2.25" category="Chicken" productionarea="Hot Production" nutrients="203|19.6|7.6|13.2|56|124|387|1.37|" nutrientsuncertain="0|0|0|0|0|0|0|0|">Asiago Crusted Chicken,4U</RECIPE>
<!-- any number of <RECIPE> elements ... -->
</RECIPES>
</MENUS>
The <NUTRIENTS> element contains a pipe-delimited string; the components of this string need to somehow become elements for each <RECIPE>. Furthermore, the values of these new elements are specified by looking at the corresponding position within the pipe-delimited string found in <RECIPE>\<nutrients>.
The overall structure I'm shooting for is:
All of the attributes for a <RECIPE> element are converted into child elements.
The elements of <RECIPE>/<nutrients> map to the same position
within the <NUTRIENTS> element.
Using XSLT 1.0.
So, here would be my expected structure:
<?xml version="1.0"?>
<MENUS>
<MENU id="192748" servedate="20120213" mealid="3" mealname="Lunch" menuname="Cafeteria" menuid="26" totalcount="200">Cafeteria</MENU>
<RECIPES>
<NUTRIENTS>Calories~Energy (kcal)~kcal|Protein~Protein~gm|Fat~Fat~gm|Carbs~Total Carbohydrates~gm|Cholestrol~Cholesterol~mg|Calcium~Calcium~mg|Sodium~Sodium~mg|Iron~Iron~mg|</NUTRIENTS>
<RECIPE>
<id>6461-200</id>
<plucode/>
<shortname>Chipotle Spinach</shortname>
<numservings>100</numservings>
<portion>4 ounces</portion>
<isselected>0</isselected>
<ismainitem>0</ismainitem>
<group>On the Side</group>
<publishingdescription>Chipotle Spinach</publishingdescription>
<publishingtext/>
<enticingdescription/>
<price>1.53</price>
<category>Vegetables</category>
<productionarea>Hot Production</productionarea>
<nutrients>152|2.3|13.8|6.5|0|74|346|1.85|</nutrients>
<nutrientsuncertain>0|0|0|0|0|0|0|0|</nutrientsuncertain>
<CaloriesEnergykcalkcal>152</CaloriesEnergykcalkcal>
<ProteinProteingm>2.3</ProteinProteingm>
<FatFatgm>13.8</FatFatgm>
<CarbsTotalCarbohydrates>6.5</CarbsTotalCarbohydrates>
<CholestrolCholestrolmg>0</CholestrolCholestrolmg>
<CalciumCalciummg>74</CalciumCalciummg>
<SodiumSodiummg>346</SodiumSodiummg>
<IronIronmg>1.85</IronIronmg>
</RECIPE>
<RECIPE>
<id>6586-300</id>
<plucode/>
<shortname>Asiago Crusted Chix</shortname>
<numservings>120</numservings>
<portion>3-3/4 ounces</portion>
<isselected>0</isselected>
<ismainitem>0</ismainitem>
<group>Main Fare</group>
<publishingdescription>Asiago Crusted Chicken</publishingdescription>
<publishingtext/>
<enticingdescription/>
<price>2.25</price>
<category>Chicken</category>
<productionarea>Hot Production</productionarea>
<nutrients>203|19.6|7.6|13.2|56|124|387|1.37|</nutrients>
<nutrientsuncertain>0|0|0|0|0|0|0|0|</nutrientsuncertain>
<CaloriesEnergykcalkcal>203</CaloriesEnergykcalkcal>
<ProteinProteingm>19.6</ProteinProteingm>
<FatFatgm>7.6</FatFatgm>
<CarbsTotalCarbohydrates>13.2</CarbsTotalCarbohydrates>
<CholestrolCholestrolmg>56</CholestrolCholestrolmg>
<CalciumCalciummg>124</CalciumCalciummg>
<SodiumSodiummg>387</SodiumSodiummg>
<IronIronmg>1.37</IronIronmg>
</RECIPE>
<!-- ... -->
</RECIPES>
</MENUS>
(notice, again, that I don't care about the field names we use for these new data points [which begin after <nutrientsuncertain>]; however, bonus points if you would like to show me how to relatively easily specify some sort of array, for lack of a better term, of field names)
Here's my current XSLT, which achieves goal #1; it's goal #2 that I'm stumped on:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="no" indent="yes"/>
<xsl:strip-space elements="*"/>
<!-- Template #1 - Identity Transform -->
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<!-- Template #2 - Convert all of a <RECIPE> element's attributes to child elements -->
<xsl:template match="RECIPE/#*">
<xsl:element name="{name()}">
<xsl:value-of select="." />
</xsl:element>
</xsl:template>
<!-- Template #3 - Remove extraneous text from each <RECIPE> -->
<xsl:template match="RECIPE/text()" />
</xsl:stylesheet>
That's it. Thanks so much for your help!
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
exclude-result-prefixes="exsl"
version="1.0">
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="*|#*|text()">
<xsl:copy>
<xsl:apply-templates select="*|#*|text()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="RECIPE">
<xsl:copy>
<xsl:apply-templates select="#*"/>
<xsl:variable name="nutrients-table-tmp">
<xsl:call-template name="tokenize-table">
<xsl:with-param name="text" select="../NUTRIENTS/text()"/>
<xsl:with-param name="delimiter-row" select="'|'"/>
<xsl:with-param name="delimiter-col" select="'~'"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="nutrients-table" select="exsl:node-set($nutrients-table-tmp)/table"/>
<xsl:variable name="nutrients">
<xsl:call-template name="tokenize">
<xsl:with-param name="text" select="#nutrients"/>
<xsl:with-param name="delimiter" select="'|'"/>
</xsl:call-template>
</xsl:variable>
<xsl:for-each select="exsl:node-set($nutrients)/token">
<xsl:variable name="pos" select="position()"/>
<xsl:variable name="value" select="text()"/>
<xsl:variable name="row" select="$nutrients-table/row[$pos]"/>
<xsl:variable name="name" select="$row/cell[1]/text()"/>
<xsl:variable name="description" select="$row/cell[2]/text()"/>
<xsl:variable name="unit" select="$row/cell[3]/text()"/>
<xsl:element name="{$name}">
<xsl:attribute name="unit">
<xsl:value-of select="$unit"/>
</xsl:attribute>
<xsl:value-of select="$value"/>
</xsl:element>
</xsl:for-each>
</xsl:copy>
</xsl:template>
<xsl:template match="RECIPE/#*">
<xsl:element name="{name()}">
<xsl:value-of select="."/>
</xsl:element>
</xsl:template>
<xsl:template name="tokenize">
<xsl:param name="text"/>
<xsl:param name="delimiter" select="' '"/>
<xsl:choose>
<xsl:when test="contains($text,$delimiter)">
<token>
<xsl:value-of select="substring-before($text,$delimiter)"/>
</token>
<xsl:call-template name="tokenize">
<xsl:with-param name="text" select="substring-after($text,$delimiter)"/>
<xsl:with-param name="delimiter" select="$delimiter"/>
</xsl:call-template>
</xsl:when>
<xsl:when test="$text">
<token>
<xsl:value-of select="$text"/>
</token>
</xsl:when>
</xsl:choose>
</xsl:template>
<xsl:template name="tokenize-table">
<xsl:param name="text"/>
<xsl:param name="delimiter-row"/>
<xsl:param name="delimiter-col"/>
<xsl:variable name="rows">
<xsl:call-template name="tokenize">
<xsl:with-param name="text" select="$text"/>
<xsl:with-param name="delimiter" select="$delimiter-row"/>
</xsl:call-template>
</xsl:variable>
<table>
<xsl:for-each select="exsl:node-set($rows)/token">
<xsl:variable name="items">
<xsl:call-template name="tokenize">
<xsl:with-param name="text" select="text()"/>
<xsl:with-param name="delimiter" select="$delimiter-col"/>
</xsl:call-template>
</xsl:variable>
<row>
<xsl:for-each select="exsl:node-set($items)/token">
<cell>
<xsl:value-of select="text()"/>
</cell>
</xsl:for-each>
</row>
</xsl:for-each>
</table>
</xsl:template>
</xsl:stylesheet>
Output:
<?xml version="1.0" encoding="utf-8"?>
<MENUS>
<MENU id="192748" servedate="20120213" mealid="3" mealname="Lunch" menuname="Cafeteria" menuid="26" totalcount="200">Cafeteria</MENU>
<RECIPES>
<NUTRIENTS>Calories~Energy (kcal)~kcal|Protein~Protein~gm|Fat~Fat~gm|Carbs~Total Carbohydrates~gm|Cholestrol~Cholesterol~mg|Calcium~Calcium~mg|Sodium~Sodium~mg|Iron~Iron~mg|</NUTRIENTS>
<RECIPE>
<id>6461-200</id>
<plucode/>
<shortname>Chipotle Spinach</shortname>
<numservings>100</numservings>
<portion>4 ounces</portion>
<isselected>0</isselected>
<ismainitem>0</ismainitem>
<group>On the Side</group>
<publishingdescription>Chipotle Spinach</publishingdescription>
<publishingtext/>
<enticingdescription/>
<price>1.53</price>
<category>Vegetables</category>
<productionarea>Hot Production</productionarea>
<nutrients>152|2.3|13.8|6.5|0|74|346|1.85|</nutrients>
<nutrientsuncertain>0|0|0|0|0|0|0|0|</nutrientsuncertain>
<Calories unit="kcal">152</Calories>
<Protein unit="gm">2.3</Protein>
<Fat unit="gm">13.8</Fat>
<Carbs unit="gm">6.5</Carbs>
<Cholestrol unit="mg">0</Cholestrol>
<Calcium unit="mg">74</Calcium>
<Sodium unit="mg">346</Sodium>
<Iron unit="mg">1.85</Iron>
</RECIPE>
<RECIPE>
<id>6586-300</id>
<plucode/>
<shortname>Asiago Crusted Chix</shortname>
<numservings>120</numservings>
<portion>3-3/4 ounces</portion>
<isselected>0</isselected>
<ismainitem>0</ismainitem>
<group>Main Fare</group>
<publishingdescription>Asiago Crusted Chicken</publishingdescription>
<publishingtext/>
<enticingdescription/>
<price>2.25</price>
<category>Chicken</category>
<productionarea>Hot Production</productionarea>
<nutrients>203|19.6|7.6|13.2|56|124|387|1.37|</nutrients>
<nutrientsuncertain>0|0|0|0|0|0|0|0|</nutrientsuncertain>
<Calories unit="kcal">203</Calories>
<Protein unit="gm">19.6</Protein>
<Fat unit="gm">7.6</Fat>
<Carbs unit="gm">13.2</Carbs>
<Cholestrol unit="mg">56</Cholestrol>
<Calcium unit="mg">124</Calcium>
<Sodium unit="mg">387</Sodium>
<Iron unit="mg">1.37</Iron>
</RECIPE>
</RECIPES>
</MENUS>
I am not 100% sure I understand your question, but I think you should take a look at the XSL tokenize function. If you combine this in a variable with the position() function you should be able to achieve that correlated output?
To further add to this, you can combine the name() with a (replace for 2.0/translate for 1.0) to get the element name automatically) within a for-each, extracting the positions.
Refer my implementation:-
XSLT File:
<?xml version="1.0" encoding="UTF-8"?>
<!--<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
</xsl:stylesheet>-->
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="no" indent="yes"/>
<xsl:strip-space elements="*"/>
<!-- Template #1 - Identity Transform -->
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<!-- Template #2 - Convert all of a <RECIPE> element's attributes to child elements -->
<xsl:template match="RECIPE/#*">
<xsl:element name="{name()}">
<xsl:value-of select="."/>
</xsl:element>
</xsl:template>
<!-- Template #3 - Remove extraneous text from each <RECIPE> -->
<xsl:template match="RECIPE/text()"/>
<!-- Identifying the last attribute -->
<xsl:template match="RECIPE/#*[position()=last()]">
<xsl:element name="{name()}">
<xsl:value-of select="."/>
</xsl:element>
<!--- Call the String Tokenize template -->
<xsl:call-template name="tokenize">
<xsl:with-param name="string" select="/MENUS/RECIPES/NUTRIENTS/text()"/>
<xsl:with-param name="strValue" select="/MENUS/RECIPES/RECIPE/#nutrients"/>
</xsl:call-template>
</xsl:template>
<!--- String Tokenize -->
<xsl:template name="tokenize">
<xsl:param name="string"/>
<xsl:param name="strValue"/>
<xsl:param name="delimiter" select="'|'"/>
<xsl:choose>
<xsl:when test="$delimiter and contains($string, $delimiter) and contains($strValue, $delimiter)">
<xsl:variable name="subbef" select="translate(substring-before($string, $delimiter), '()~ ', '')"/>
<xsl:text disable-output-escaping="yes"><</xsl:text>
<xsl:value-of select="$subbef"/>
<xsl:text disable-output-escaping="yes">></xsl:text>
<xsl:value-of select="substring-before($strValue, $delimiter)"/>
<xsl:text disable-output-escaping="yes"></</xsl:text>
<xsl:value-of select="$subbef"/>
<xsl:text disable-output-escaping="yes">></xsl:text>
<xsl:call-template name="tokenize">
<xsl:with-param name="string" select="translate(substring-after($string, $delimiter), '()~ ', '')"/>
<xsl:with-param name="strValue" select="substring-after($strValue, $delimiter)"/>
<xsl:with-param name="delimiter" select="$delimiter"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:if test="string($string) and string($strValue)">
<xsl:text disable-output-escaping="yes"><</xsl:text>
<xsl:value-of select="$string"/>
<xsl:text disable-output-escaping="yes">></xsl:text>
<xsl:value-of select="$strValue"/>
<xsl:text disable-output-escaping="yes"></</xsl:text>
<xsl:value-of select="$string"/>
<xsl:text disable-output-escaping="yes">></xsl:text>
</xsl:if>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
OUTPUT:
<?xml version="1.0" encoding="UTF-8"?>
<MENUS>
<MENU id="192748" servedate="20120213" mealid="3" mealname="Lunch" menuname="Cafeteria" menuid="26" totalcount="200">Cafeteria</MENU>
<RECIPES>
<NUTRIENTS>Calories~Energy (kcal)~kcal|Protein~Protein~gm|Fat~Fat~gm|Carbs~Total Carbohydrates~gm|Cholestrol~Cholesterol~mg|Calcium~Calcium~mg|Sodium~Sodium~mg|Iron~Iron~mg|</NUTRIENTS>
<RECIPE>
<id>6461-200</id>
<plucode />
<shortname>Chipotle Spinach</shortname>
<numservings>100</numservings>
<portion>4 ounces</portion>
<isselected>0</isselected>
<ismainitem>0</ismainitem>
<group>On the Side</group>
<publishingdescription>Chipotle Spinach</publishingdescription>
<publishingtext />
<enticingdescription />
<price>1.53</price>
<category>Vegetables</category>
<productionarea>Hot Production</productionarea>
<nutrients>152|2.3|13.8|6.5|0|74|346|1.85|</nutrients>
<nutrientsuncertain>0|0|0|0|0|0|0|0|</nutrientsuncertain>
<CaloriesEnergykcalkcal>152</CaloriesEnergykcalkcal>
<ProteinProteingm>2.3</ProteinProteingm>
<FatFatgm>13.8</FatFatgm>
<CarbsTotalCarbohydratesgm>6.5</CarbsTotalCarbohydratesgm>
<CholestrolCholesterolmg>0</CholestrolCholesterolmg>
<CalciumCalciummg>74</CalciumCalciummg>
<SodiumSodiummg>346</SodiumSodiummg>
<IronIronmg>1.85</IronIronmg>
</RECIPE>
<RECIPE>
<id>6586-300</id>
<plucode />
<shortname>Asiago Crusted Chix</shortname>
<numservings>120</numservings>
<portion>3-3/4 ounces</portion>
<isselected>0</isselected>
<ismainitem>0</ismainitem>
<group>Main Fare</group>
<publishingdescription>Asiago Crusted Chicken</publishingdescription>
<publishingtext />
<enticingdescription />
<price>2.25</price>
<category>Chicken</category>
<productionarea>Hot Production</productionarea>
<nutrients>203|19.6|7.6|13.2|56|124|387|1.37|</nutrients>
<nutrientsuncertain>0|0|0|0|0|0|0|0|</nutrientsuncertain>
<CaloriesEnergykcalkcal>152</CaloriesEnergykcalkcal>
<ProteinProteingm>2.3</ProteinProteingm>
<FatFatgm>13.8</FatFatgm>
<CarbsTotalCarbohydratesgm>6.5</CarbsTotalCarbohydratesgm>
<CholestrolCholesterolmg>0</CholestrolCholesterolmg>
<CalciumCalciummg>74</CalciumCalciummg>
<SodiumSodiummg>346</SodiumSodiummg>
<IronIronmg>1.85</IronIronmg>
</RECIPE>
<!-- any number of <RECIPE> elements ... -->
</RECIPES>
</MENUS>