XSLT to convert elements in a concatenated line - xslt

I have this situation:
Two variables:
<xsl:variable name="varDep" select="DepAir"/>
<xsl:variable name="varArr" select="ArrAir"/>
Value of variables:
<testa>
<DepAir>SDU</DepAir>
<DepAir>CGH</DepAir>
</testa>
<testb>
<ArrAir>CGH</ArrAir>
<ArrAir>SDU</ArrAir>
</testb>
And I need transform in a concatenated line, like this:
<db:P_IAT>SDU;CGH;CGH;SDU;</db:P_IAT>
How can I do that?

Kind dynamic way will be to put loop for DepAir and ArrAir inside above variables as below:
<xsl:variable name="varDep">
<xsl:for-each select="//DepAir">
<xsl:value-of select="concat(., ';')"/>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="varArr">
<xsl:for-each select="//ArrAir">
<xsl:value-of select="concat(., ';')"/>
</xsl:for-each>
</xsl:variable>
And then use concat inside required node:
<required_node><xsl:value-of select="concat($varDep, $varArr)"/></required_node>
Note! In next questions please put your XSL (what you have tried) to avoid "Down Votes". Hope it will help in your case.

Related

XSLT 2.0 tokenising delimiters within delimiters

In XSLT 2.0 I have long string (parameter) with a delimiter (;) inside a delimiter (~), more specifically a triplet inside a delimiter.
Data is organized like so:
<parameter>qrsbfs;qsvsv;tfgz~dknk;fvtea;gtvath~pksdi;ytbdi;oiunhu</parameter>
The first tokenize($mystring,'~') in a for-each produces :
qrsbfs;qsvsv;tfgz
dknk;fvtea;gtvath
pksdi;ytbdi;oiunhu
Within that tokenization, I need to treat it by looping again:
qrsbfs
qsvsv
tfgz
dknk
fvtea
gtvath
pksdi
ytbdi
oiunhu
I can do intensive string manipulation to get there using concat, string-length, and substring-before/substring-after, but I wondered if there wasn't a more elegant solution that my neophyte mind wasn't overlooking?
EDIT, adding nested tokenize that returned incorrect results:
<xsl:for-each select="tokenize($myparameter,'~')">
<xsl:for-each select="tokenize(.,';')">
<xsl:if test="position()=1">
<xsl:value-of select="."/>
</xsl:if>
<xsl:if test="position()=2">
<xsl:value-of select="."/>
</xsl:if>
<xsl:if test="position()=3">
<xsl:value-of select="."/>
</xsl:if>
</xsl:for-each>
</xsl:for-each>
If you wanted a one line solution, you could do something like this, using nested for-in-return statements:
<xsl:sequence select="for $n in tokenize(.,'~') return concat(string-join(tokenize($n,';'),'
'),'
')"/>
If you don't need to tokenize them separately, you could replace the ~ with ; and tokenize all 9 elements at the same time:
tokenize(replace(parameter,'~',';'),';')
For what it's worth, the code in https://xsltfiddle.liberty-development.net/pPqsHUe uses
<xsl:template match="parameter">
<xsl:for-each select="tokenize(., '~')">
<xsl:value-of select="tokenize(., ';')" separator="
"/>
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>
and with output method text produces
qrsbfs
qsvsv
tfgz
dknk
fvtea
gtvath
pksdi
ytbdi
oiunhu

Working on Variable in XSL loop to hold dynamic value

Working on the xsl and i am looking for variable inside a loop to reset on new record.
Fetching records from oracle table
<xsl:variable name="curr_temp_emp_no" select="'##'"/>
<xsl:for-each select="/data/test/loof/super_incompleted">
<xsl:variable name="curr_temp_emp_no2" select="emp_no"/>
<xsl:choose>
<xsl:when test="$curr_temp_emp_no2 != $curr_temp_emp_no">
<xsl:value-of select="emp_no"/><fo:inline> - </fo:inline><xsl:value-of select="variable_desc"/></xsl:when>
<xsl:otherwise>
<xsl:value-of select="variable_desc"/>
</xsl:otherwise>
</xsl:choose>
<xsl:variable name="curr_temp_emp_no" select="emp_no"/>
</xsl:for-each>
I am trying to compare variable "curr_temp_emp_no" if new value only it prints "emp_no - variable_desc" otherwise(if same emp_no) then print only "variable_desc".
I understood from google that Variables in XSLT are immutable, once we assign them a value, we can't change them.
Refence: Can you simulate a boolean flag in XSLT?
Can anyone please help me over here in writting this logic.
It looks like you want to compare the last two values. I could achieve this by:
<xsl:template match="/">
<xsl:call-template name="helperTemplate">
<xsl:with-param name="nodes" select="/data/test/loof/super_incompleted"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="helperTemplate">
<xsl:param name="nodes"/>
<xsl:param name="oldVal" select="''"/>
<xsl:if test="$nodes">
<xsl:variable name="curr_temp_emp_no" select="$nodes[1]/emp_no"/>
<xsl:choose>
<xsl:when test="$curr_temp_emp_no != $oldVal">
<xsl:value-of select="$curr_temp_emp_no"/>
<fo:inline> - </fo:inline>
<xsl:value-of select="$nodes[1]/variable_desc"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$nodes[1]/variable_desc"/>
</xsl:otherwise>
</xsl:choose>
<xsl:call-template name="helperTemplate">
<xsl:with-param name="nodes" select="$nodes[position() > 1]"/>
<xsl:with-param name="oldVal" select="$nodes[1]/emp_no"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
If you want to have the comparison on all values(if there was no previous element with that content), you can sort them first and use the same code.
You're asking us to reverse engineer your requirements from non-working code, which is always a challenge, but you seem to be carrying over ideas from procedural programming languages which gives us some clues as to what you imagine you want this code to do.
Basically it looks like a "group-adjacent" problem. In XSLT 2.0 you would do
<xsl:for-each-group select="/data/test/loof/super_incompleted"
group-adjacent="emp_no">
...
</xsl:for-each-group>
If you're stuck with XSLT 1.0 then the usual approach is to process the sequence of nodes with a recursive named template rather than a for-each instruction: you pass the list of nodes as a parameter to the template, together with the current employee id, and then in the template you process the first node in the list, and call yourself recursively to process the remainder of the list.
#ChristianMosz has expanded this suggestion into working code.
Thanks for your reply.
I have used the "preceding-sibling::" which solved my problem. Now the code is something like this.
<xsl:for-each select="/data/test/loof/super_incompleted">
<xsl:variable name="curr_temp_emp_no2" select="emp_no"/>
<xsl:choose>
<xsl:when test="$curr_temp_emp_no2 != preceding-sibling::super_incompleted[1]/emp_no">
<xsl:value-of select="emp_no"/><fo:inline> - </fo:inline><xsl:value-of select="variable_desc"/></xsl:when>
<xsl:otherwise>
<xsl:value-of select="variable_desc"/>
</xsl:otherwise>
</xsl:choose>
<xsl:variable name="curr_temp_emp_no" select="emp_no"/>
</xsl:for-each>
Earlier the table was like this.
1- ABC,1- DFE,1- GFH
2- DFG,2- FGH,2- SDS,2- RTY
Now table looks like this.
1- ABC,DFE,GFH
2- DFG,FGH,SDS

xsl declare variable and reassign/change value in for-each is not working

I'm declaring variable "flag" in for-each and reassigning value inner for-each. I'm getting error duplicate variable within the scope.
My code is:
<xsl:variable name="flag" select="'0'"/>
<xsl:template match="/">
<xsl:for-each select="Properties/Property">
<xsl:variable name="flag" select="'0'"/>
<xsl:choose>
<xsl:when test="$language='en-CA'">
<xsl:for-each select="Localization/[Key=$language]">
<xsl:value-of select="Value/Value"/>
<xsl:variable name="flag" select="'1'"/>
</xsl:for-each>
<xsl:if test="$flag ='0'">
<xsl:value-of select="$flag"/>
</xsl:if>
</xsl:when>
</xsl:choose>
</xsl:for-each>
</xsl:template>
Can we update/re-assign variable value? If not Do we have any other options?
Any help?
XSLT is not a procedural language and variables in XSLT don't behave like variables in procedural languages; they behave more like variables in mathematics. That is, they are names for values. The formula x=x+1 makes no sense in mathematics and it makes no sense in XSLT either.
It's always difficult to reverse-engineer a specification from procedural code, especially from incorrect procedural code. So tell us what you are trying to achieve, and we will tell you the XSLT way (that is, the declarative/functional way) of doing it.
XSLT variables are single-assignment.
you can create an xsl template and do xsl recursion.
for example:
<xsl:template name="IncrementUntil5">
<xsl:param name="counter" select="number(1)" />
<xsl:if test="$counter < 6">
<test><xsl:value-of select="$counter"/></test>
<xsl:call-template name="IncrementUntil5">
<xsl:with-param name="counter" select="$counter + 1"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
then call it like this:
<xsl:template match="/">
<div>
<xsl:call-template name="IncrementUntil5"/>
</div>
</xsl:template>
Try this:
<xsl:template match="/">
<xsl:for-each select="Properties/Property">
<xsl:choose>
<xsl:when test="$language='en-CA' and Localization/Key='en-CA'">
<xsl:value-of select="Value/Value"/>
</xsl:when>
</xsl:choose>
</xsl:for-each>
</xsl:template>
You don't need to iterate through a collection to determine if something's present, the simple XPath Localization/Key='en-CA' will be true if there's any element matching it that exists.

XSLT: contains() for multiple strings

I have a variable in XSLT called variable_name which I am trying to set to 1, if the Product in question has attributes with name A or B or both A & B.
<xsl:variable name="variable_name">
<xsl:for-each select="product/attributes">
<xsl:if test="#attributename='A' or #attributename='B'">
<xsl:value-of select="1"/>
</xsl:if>
</xsl:for-each>
</xsl:variable>
Is there any way to match multiple strings using the if statement, as mine just matches if A is present or B is present. If both A & B are present, it does not set the variable to 1. Any help on this would be appreciated as I am a newbie in XSLT.
You can use xsl:choose statement, it's something like switch in common programming languages:
Example:
<xsl:variable name="variable_name">
<xsl:for-each select="product/attributes">
<xsl:choose>
<xsl:when test="#attributename='A'">
1
</xsl:when>
<xsl:when test=" #attributename='B'">
1
</xsl:when>
<!--... add other options here-->
<xsl:otherwise>1</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:variable>
This will set new variable with name variable_name with the value of attribute product/attributes.
For more info ... http://www.w3schools.comwww.w3schools.com/xsl/el_choose.asp
EDIT: And another way (a little dirty) by OP's request:
<xsl:variable name="variable_name">
<xsl:for-each select="product/attributes">
<xsl:if test="contains(text(), 'A') or contains(text(), 'B')">
1
</xsl:if>
</xsl:for-each>
</xsl:variable>
It will be helpful if you provide the xml you're writing your xslt against.
This might not help...
Is it 'legal' to have two XML element attributes with the same name (eg. <element x="1" x="2" />)?
Is this what you are trying to process? Try parsing your XML file through xmllint or something like it to see if it is valid.
xmllint --valid the-xml-file.xml
My guess is that you will get a 'attribute redefined' error.

Using xsl:variable in a xsl:foreach select statement

I'm trying to iterate through an xml document using xsl:foreach but I need the select=" " to be dynamic so I'm using a variable as the source. Here's what I've tried:
...
<xsl:template name="SetDataPath">
<xsl:param name="Type" />
<xsl:variable name="Path_1">/Rating/Path1/*</xsl:variable>
<xsl:variable name="Path_2">/Rating/Path2/*</xsl:variable>
<xsl:if test="$Type='1'">
<xsl:value-of select="$Path_1"/>
</xsl:if>
<xsl:if test="$Type='2'">
<xsl:value-of select="$Path_2"/>
</xsl:if>
<xsl:template>
...
<!-- Set Data Path according to Type -->
<xsl:variable name="DataPath">
<xsl:call-template name="SetDataPath">
<xsl:with-param name="Type" select="/Rating/Type" />
</xsl:call-template>
</xsl:variable>
...
<xsl:for-each select="$DataPath">
...
The foreach threw an error stating: "XslTransformException - To use a result tree fragment in a path expression, first convert it to a node-set using the msxsl:node-set() function."
When I use the msxsl:node-set() function though, my results are blank.
I'm aware that I'm setting $DataPath to a string, but shouldn't the node-set() function be creating a node set from it? Am I missing something? When I don't use a variable:
<xsl:for-each select="/Rating/Path1/*">
I get the proper results.
Here's the XML data file I'm using:
<Rating>
<Type>1</Type>
<Path1>
<sarah>
<dob>1-3-86</dob>
<user>Sarah</user>
</sarah>
<joe>
<dob>11-12-85</dob>
<user>Joe</user>
</joe>
</Path1>
<Path2>
<jeff>
<dob>11-3-84</dob>
<user>Jeff</user>
</jeff>
<shawn>
<dob>3-5-81</dob>
<user>Shawn</user>
</shawn>
</Path2>
</Rating>
My question is simple, how do you run a foreach on 2 different paths?
Try this:
<xsl:for-each select="/Rating[Type='1']/Path1/*
|
/Rating[Type='2']/Path2/*">
Standard XSLT 1.0 does not support dynamic evaluation of xpaths. However, you can achieve your desired result by restructuring your solution to invoke a named template, passing the node set you want to process as a parameter:
<xsl:variable name="Type" select="/Rating/Type"/>
<xsl:choose>
<xsl:when test="$Type='1'">
<xsl:call-template name="DoStuff">
<xsl:with-param name="Input" select="/Rating/Path1/*"/>
</xsl:call-template>
</xsl:when>
<xsl:when test="$Type='2'">
<xsl:call-template name="DoStuff">
<xsl:with-param name="Input" select="/Rating/Path2/*"/>
</xsl:call-template>
</xsl:when>
</xsl:choose>
...
<xsl:template name="DoStuff">
<xsl:param name="Input"/>
<xsl:for-each select="$Input">
<!-- Do stuff with input -->
</xsl:for-each>
</xsl:template>
The node-set() function you mention can convert result tree fragments into node-sets, that's correct. But: Your XSLT does not produce a result tree fragment.
Your template SetDataPath produces a string, which is then stored into your variable $DataPath. When you do <xsl:for-each select="$DataPath">, the XSLT processor chokes on the fact that DataPath does not contain a node-set, but a string.
Your entire stylesheet seems to be revolve around the idea of dynamically selecting/evaluating XPath expressions. Drop that thought, it is neither possible nor necessary.
Show your XML input and specify the transformation your want to do and I can try to show you a way to do it.