<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>
Related
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.
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>
I have an XML document which contains the following example extract:
<p>
Some text <GlossaryTermRef href="123">term 1</GlossaryTermRef><GlossaryTermRef href="345">term 2</GlossaryTermRef>.
</p>
I am using XSLT to transform this to XHTML using the following template:
<xsl:template match="GlossaryTermRef">
<a href="#{#href}" class="glossary">
<xsl:apply-templates select="node()|text()"/>
</a>
</xsl:template>
This works quite well, however I need to insert a space between the two GlossaryTermRef elements if they appear next to each other?
Is there a way to detect whether there is either space or text between the current node and the following-sibling? I can't always insert a space GlossaryTermRef item, as it may be followed by a punctuation mark.
I managed to solve this myself my modifying the template as follows:
<xsl:template match="GlossaryTermRef">
<a href="#{#href}" class="glossary">
<xsl:apply-templates select="node()|text()"/>
</a>
<xsl:if test="following-sibling::node()[1][self::GlossaryTermRef]">
<xsl:text> </xsl:text>
</xsl:if>
</xsl:template>
Can anyone suggest a better way, or see any problems with this solution?
Firstly, "node()|text()" is a longwinded equivalent of "node()". Perhaps you meant "*|node()" which would select the element and text children but not the comments or PIs.
Your solution is probably as good as any. Another would be to use grouping:
<xsl:for-each-group select="node()" group-adjacent="boolean(self::GlossaryTermRef)">
<xsl:choose>
<xsl:when test="current-grouping-key()">
<xsl:for-each select="current-group()">
<xsl:if test="position() gt 1"><xsl:text> </xsl:text></xsl:if>
<xsl:apply-templates select="."/>
</xsl:for-each>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="current-group()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
Naah, that's not pretty at all.
My next attempt would be to use sibling recursion (where the parent does apply-templates on the first child, and each child does apply-templates on the immediately following sibling), but I don't think that's going to be an improvement either.
What about this one? what do you feel?
<xsl:template match="GlossaryTermRef">
<a href="#{#href}" class="glossary">
<xsl:apply-templates select="node()|text()"/>
</a>
</xsl:template>
I cant really formulate that better, so I'll go with an example instead:
XML:
<root>
<foo>
<bar id="1">sdf</bar>
<bar id="2">sdooo</bar
</foo>
<feng>
<heppa id="4">hihi</heppa>
<heppa id="2">sseeapeea</heppa>
<heppa id="1">....</heppa>
</feng>
</root>
XSLT:
<xsl:for-each select="/root/foo/bar">
<p>
<xsl:value-of select="." />: <xsl:value-of select="/root/feng/heppa[#id = #id]" />
</p>
</xsl:for-each>
Desired output:
<p>sdf: ....</p>
<p>sdooo: sseeapeea</p>
Actual output:
<p>sdf: hihi</p>
<p>sdooo: hihi</p>
For selecting nodes with XPath 1.0 only, you need to do a node set comparison:
/root/feng/heppa[#id=/root/foo/bar/#id]
Of course, this has NxM complexity (as the others XSLT solutions)
With XSLT 1.0 you should use keys because there are cross references:
<xsl:key name="kBarById" select="bar" use="#id"/>
<xsl:template match="/root/feng/heppa[key('kBarById',#id)]">
<p>
<xsl:value-of select="concat(key('kBarById',#id),': ',.)"/>
</p>
</xsl:template>
I assume you mean /root/foo/bar since /root/foo elements don't have id.
You're comparing the #id with itself, so of course it's true for the first node examined. You can use current() to reference the current node in an expression:
<xsl:for-each select="/root/foo/bar">
<p>
<xsl:value-of select="." />: <xsl:value-of select="/root/feng/heppa[#id = current()/#id]" />
</p>
</xsl:for-each>
Another solution is to read the id attribute into a variable.
<xsl:for-each select="/root/foo/bar">
<xsl:variable name="id" select="#id"/>
<p>
<xsl:value-of select="." />: <xsl:value-of select="/root/feng/heppa[#id = $id]" />
</p>
</xsl:for-each>
This might be handier, if your real use case is more complicated and you need to use the value of the id multiple times in this for-each section.
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.