XSLT 1.0: if statement when position matches a variable value - xslt

I'm trying to insert an IF statement into an existing FOR-EACH loop to do something slightly different if it matches a variable from another node (the node I want is actually a sibling of it's parent - if that makes sense!?).
The value is a simple integer. I basically want to say: If the position is equal to the variable number then do XXXX.
Here is the XSLT, it's only v1.0 and not 2.0 that I can use.
<xsl:for-each select="/Properties/Data/Datum[#ID='ID1']/DCR[#Type='accordion_tab']/accordion_tab/sections">
<h3 class="accordionButton">
<xsl:if test="position()='openpane value to go here'">
<xsl:attribute name="class">
<xsl:text>new text</xsl:text>
</xsl:attribute>
</xsl:if>
</xsl:for-each>
My XML extract is here:
<sections>
<title>title</title>
<text>some text</text>
</sections>
<openpane>2</openpane>

You didn't make this clear in your question, but I assume you iterate over the sections elements in your for-each loop. From the for-each loop you can reach the openpane element by going through the parent of the current sections element:
<xsl:for-each select="sections">
<xsl:if test="position() = ../openpane">
...
</xsl:if>
</xsl:for-each>
You could also define a variable referring to the openpane element first:
<xsl:variable name="openpane" select="openpane"/>
<xsl:for-each select="sections">
<xsl:if test="position() = $openpane">
...
</xsl:if>
</xsl:for-each>

Related

XSLT / conditional for-each with same parent (uncle) node value

I need some help understanding how I can select only certain nodes having same parent (or uncle) element value. My XML looks like this
<shipment>
<goodslines>
<goodsline>
<position>1</position>
<packagenumbers>
<packagenumber>123</packagenumber>
</packagenumbers>
</goodsline>
<goodsline>
<position>1</position>
<packagenumbers>
<packagenumber>456</packagenumbers>
</packagenumbers>
</goodsline>
<goodsline>
<position>2</position>
<packagenumbers>
<packagenumber>789</packagenumbers>
</packagenumbers>
</goodsline>
</goodslines>
</shipment>
and the desired output would be:
123,456
789
So I would need to do for-each to "packagenumber" - level so, that it would take in consideration the "position" - element from upper level
The XSL might be something like this?
<xsl:for-each select="shipment/goodslines/goodsline[some condition here?]/packagenumbers/packagenumber">
<xsl:value-of select="current()"/>
<xsl:if test="not(position() = last())">
<xsl:text>,</xsl:text>
</xsl:if>
<xsl:text>
</xsl:text>
</xsl:for-each>
Any help would be appreciated.
If your xml always has the same structure you can use this:
<xsl:template match="goodsline[position = preceding-sibling::goodsline[1]/position]">
<xsl:text>,</xsl:text>
<xsl:value-of select="packagenumbers/packagenumber"/>
</xsl:template>
<xsl:template match="goodsline">
<xsl:text>
</xsl:text>
<xsl:value-of select="packagenumbers/packagenumber"/>
</xsl:template>
More specific templates (the one with the condition) will always hit first and have higher priority. There is probably another simple solution with for-each-group.

Find preceding-sibling of the parent element in xslt

<p>
<p1>c</p1>
<a languageCode="en">a1</a>
<a languageCode="de">a2</a>
<ca>
<cc>056</cc>
<cs>BE-VAN</cs>
<cs>BG-VLI</cs>
<cs>BG-VLI</cs>
</ca>
<ca>
<cc>056</cc>
<cs>BE-VAN</cs>
<cs>BG-VLI</cs>
<cs>BG-VLI</cs>
</ca>
</p>
i tried this does not work:
<xsl:for-each select="p/ca/*">
<xsl:if test="not(preceding-sibling::*[1]/name() = local-name())">
<precedingParent n="{preceding-sibling::*[1]/parent::*/name()}"></precedingParent>
</xsl:if>
</xsl:for-each>
but expected
output should be:
<precedingParent n="a"></precedingParent>
I am looping in ca and for instance in first occurence of cs
If a want to find the preceding sibling of the parent for parent ca for element cs ? how do i do it?
The expression you are looking for is ../preceding-sibling::*[1]/name(). For example,
<xsl:for-each select="p/ca/*">
<xsl:if test="not(preceding-sibling::*[1]/name() = local-name())">
<precedingParent n="{../preceding-sibling::*[1]/name()}"></precedingParent>
</xsl:if>
</xsl:for-each>
Note that name(../preceding-sibling::*[1]) would also work (in XSLT 1.0 and XSLT 2.0).
On the other hand, you could be slightly more efficient by having a nested loop, and getting the preceding value before selecting the child elements, to save it having to be recalculated each time:
<xsl:for-each select="p/ca">
<xsl:variable name="precedingParent" select="name(preceding-sibling::*[1])" />
<xsl:for-each select="*">
<xsl:if test="not(preceding-sibling::*[1]/name() = local-name())">
<precedingParent n="{$precedingParent}"></precedingParent>
</xsl:if>
</xsl:for-each>
</xsl:for-each>

XSLT: Get Element Name of "Tokenized Item"

Is there a way to gather element name of a tokenized value? I have been trying to do it but it is giving me an error "[Saxon-PE 9.6.0.7] XPTY0004: Required item type of first argument of name() is node(); supplied value has item type xs:string"
Here are my sample set of data:
<SET>
<REAL_TAGNAME> 1 2 3 4 </REAL_TAGNAME>
</SET>
If I have use this code:
<xsl:for-each select="SET/REAL_TAGNAME">
<xsl:for-each select="tokenize(normalize-space(.),'\s+')">
<Hardcode_Tag>
<xsl:value-of select="."/>
</Hardcode_Tag>
</xsl:for-each>
</xsl:for-each>
then I will successfully have the following:
<Hardcode_Tag>1</Hardcode_Tag>
<Hardcode_Tag>2</Hardcode_Tag>
<Hardcode_Tag>3</Hardcode_Tag>
<Hardcode_Tag>4</Hardcode_Tag>
But I want to move away from hard-coding and would like to use its original tag name to have something like:
<REAL_TAGNAME>1</REAL_TAGNAME>
<REAL_TAGNAME>2</REAL_TAGNAME>
<REAL_TAGNAME>3</REAL_TAGNAME>
<REAL_TAGNAME>4</REAL_TAGNAME>
While I try below with the xsl:element, it keeps giving me an error mentioned above:
<xsl:for-each select="SET/REAL_TAGNAME">
<xsl:for-each select="tokenize(normalize-space(.),'\s+')">
<xsl:element name="{name(.)}">
<xsl:value-of select="."/>
</xsl:element>
</xsl:for-each>
</xsl:for-each>
does anyone have any idea on how I can fix this? Thanks in advance for your help!
Within the xsl:for-each, the expression . refers to the current item, i.e. the current token extracted from the string value of the element. If you want to remember the name of the element that was the current element before you entered the for-each, just set a variable before the inner xsl:for-each:
<xsl:for-each select="SET/REAL_TAGNAME">
<xsl:variable name="element-name" select="name(.)"/>
<xsl:for-each select="tokenize(normalize-space(.),'\s+')">
<xsl:element name="{$element-name}">
<xsl:value-of select="."/>
</xsl:element>
</xsl:for-each>
</xsl:for-each>
In the code you show, of course, the value of $element-name will invariably be 'REAL_TAGNAME'. I'm assuming that's not true in the general case.

xslt assigning variable value to string literal

I have
<xsl:for-each select="ancestor-or-self::*">
<xsl:variable name="expression" select="name()" ></xsl:variable>
</xsl:for-each>
below I have href and i want to set the value this expression variable in href #######
Delete
I also tried with :
Delete
None of them worked.
Can Anybody help me out how to do that?
Basically my task is to find the expression for current node and send the expression for the same in href?
Adding More Info :
<br/><xsl:for-each select="ancestor-or-self::*">
<xsl:variable name="expression" select="name()" />
</xsl:for-each>
<b>Click Me</b>
when above xsl code is transformed it is giving below error:
Variable or parameter 'expression' is undefined.
In XSLT1.0, you could try setting the variable like so:
<xsl:variable name="expression">
<xsl:for-each select="ancestor-or-self::*">
<xsl:text>/</xsl:text>
<xsl:variable name="name" select="name()"/>
<xsl:value-of select="$name"/>
</xsl:for-each>
</xsl:variable>
So, assuming the following XML structure
<As>
<a>
<Bs>
<b>5</b>
</Bs>
</a>
<a>
<Bs>
<b>9</b>
</Bs>
</a>
<a/>
<a>
<Bs>
<b>12</b>
<b>14</b>
<b>15</b>
</Bs>
</a>
</As>
If you were positioned on the b element with the value of 14, then expression would be set to /As/a/Bs/b
However, this does not take into account multiple nodes with the same name, and so would not be sufficient if you wanted accurate XPath to select the node.
Instead, you could try the following:
<xsl:variable name="expression">
<xsl:for-each select="ancestor-or-self::*">
<xsl:text>/</xsl:text>
<xsl:variable name="name" select="name()"/>
<xsl:value-of select="$name"/>
<xsl:text>[</xsl:text>
<xsl:value-of select="count(preceding-sibling::*[name() = $name]) + 1"/>
<xsl:text>]</xsl:text>
</xsl:for-each>
</xsl:variable>
This would return /As[1]/a[4]/Bs[1]/b[2], which may be what you want.
Very simply, variables are scoped in XSL and exist only within the containing tag. Thus the variable named expression exists only within the for-each block. Also, variables can only be set once. Attempting to set a variable value a second time has no effect.
Therfore you have to declare the variable at or above the level where you want to use it, and put all the code to generate the value inside the variable declaration. If you can use XSLT2, the following will do what you want:
string-join(for $n in ancestor-or-self::* return name($n), '/')
I know it can also be done in XSLT 1 with a recursive template, but I don't have an example handy.

XSL for-each: how to detect last node?

I have this simple code:
<xsl:for-each select="GroupsServed">
<xsl:value-of select="."/>,<br/>
</xsl:for-each></font>
I'm trying to add a comma for each item added.
This has 2 flaws:
Case of when there's only 1 item: the code would unconditionally add a comma.
Case of when there's more than 1 item: the last item would have a comma to it.
What do you think is the most elegant solution to solve this?
I'm using XSLT 2.0
If you're using XSLT 2.0, the canonical answer to your problem is
<xsl:value-of select="GroupsServed" separator=", " />
On XSLT 1.0, the somewhat CPU-expensive approach to finding the last element in a node-set is
<xsl:if test="position() = last()" />
Final answer:
<xsl:for-each select="GroupsServed">
<xsl:value-of select="."/>
<xsl:choose>
<xsl:when test="position() != last()">,<br/></xsl:when>
</xsl:choose>
</xsl:for-each>
<xsl:variable name="GROUPS_SERVED_COUNT" select="count(GroupsServed)"/>
<xsl:for-each select="GroupsServed">
<xsl:value-of select="."/>
<xsl:if test="position() < $GROUPS_SERVED_COUNT">
,<br/>
</xsl:if>
</xsl:for-each></font>
Insert the column delimiter before each new item, except the first one. Then insert the line break outside the for-each loop.
<xsl:for-each select="GroupsServed">
<xsl:if test="position() != 1">,</xsl:if>
<xsl:value-of select="."/>
</xsl:for-each>
<br/>
In other words, treat every item like the last item. The exception is that the first item does not need a comma separator in front of it. The loop ends after the last item is processed, which also tells us where to put the break.