How do I get an XML node with xpath in a loop, based on an attribute with the same name as the attribute in the tree I'm searching? - xslt

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.

Related

Inline element data not appearing where expected after XSL transform

With the following XML
<para>Refer to Table 3 and Figure <grphcref refid="apm00-02-02-000018" shownow="0">6</grphcref>
for the door dimensions and clearances.</para>
and this XSL:
<xsl:template match="prcitem">
<xsl:for-each select="para">
<p>
<xsl:value-of select="." />
<xsl:apply-templates select="./grphcref" />
</p>
</xsl:for-each>
<xsl:apply-templates select="grphcref" />
<xsl:apply-templates select="table" />
<xsl:apply-templates select="unlist" />
</xsl:template>
<xsl:template match="grphcref">
<xsl:variable name="gotoimg" select="concat('#',#refid)"/>
<a href="{$gotoimg}" >
<xsl:value-of select="." /> - <xsl:value-of select="#refid" /> </a>
</xsl:template>
I get:
<p>Refer to Table 3 and Figure 6 for the door dimensions and clearances.
6 - apm00-02-02-000018
when I expected:
<p>Refer to Table 3 and Figure 6 - apm00-02-02-000018
for the door dimensions and clearances.
Can anyone offer guidance as to where I went wrong?
thx
If a para element is supposed to be transformed to a p element then in my view the "natural" way in XSLT is a template
<xsl:template match="para">
<p>
<xsl:apply-templates/>
</p>
</xsl:template>
If any other elements need special treatment add a template e.g.
<xsl:template match="grphcref">
<xsl:variable name="gotoimg" select="concat('#',#refid)"/>
<a href="{$gotoimg}" >
<xsl:value-of select="." /> - <xsl:value-of select="#refid" /> </a>
</xsl:template>
Text nodes are copied through to the result by the built-in templates.
The input sample doesn't explain why you mix template matching and for-each and why or whether you need xsl:apply-templates with any particularly selected nodes; as long as the input order should be preserved a simple processing of all child nodes with <xsl:apply-templates/> should suffice.

How to conditionally filter out children in XSLT

I currently have something similar to the following XML:
<div class="newsFeed">
<div class="newsItem"><news position="3"/></div>
<categoryFilter dayFilter="4">
<div class="newsItem"><news position="2"/></div>
</categoryFilter>
</div>
I need to copy the XML, and output the nth news item on the node. Futhermore, I need to be able to filter that news. For this example, lets construct my news as follows:
<xsl:variable name="news">
<xsl:for-each select="1 to 30">
<item>
<day><xsl:value-of select=". mod 4" /></day>
<content>Content: <xsl:value-of select="." /></content>
</item>
</xsl:for-each>
</xsl:variable>
I actually use document() and use the for-each to sort it, but I'm trying to keep it succinct. This would mean that my output XML would be something like the following:
<div class="newsFeed">
<div class="newsItem">Content: 3</div>
<div class="newsItem">Content: 8</div>
</div>
The reason the second one is 8 is because the categoryFilter filters out every <item> where the day isn't 4 (which happens to be the 4th, 8th, 12th, and so on), and then we select the second one.
The XSLT to produce the above is as follows:
XSLT:
<xsl:template match="news">
<xsl:param name="items" select="$news" />
<xsl:variable name="position" select="#position" />
<xsl:copy-of select="$items/item[position()=$position]/content" />
</xsl:template>
<xsl:template match="categoryFilter">
<xsl:param name="items" select="$news" />
<xsl:variable name="day" select="#dayFilter" />
<xsl:variable name="filteredItems">
<xsl:for-each select="$items/item[day=$day]">
<xsl:copy-of select="." />
</xsl:for-each>
</xsl:variable>
<xsl:apply-templates>
<xsl:with-param name="items" select="$filteredItems">
</xsl:apply-templates>
</xsl:template>
My problem lies with the <for-each>. It seems silly that I have to use a for-each to filter out the <item> nodes, but I can't find a better way. Simply doing a <xsl:variable select="$items/item[day=$day]"> changes the structure, and makes it so that the <xsl:template match="news"> doesn't work.
Is there a way to filter out child nodes without using a for-each? I am using <xsl:stylesheet version="3.0">
Instead of doing this...
<xsl:variable name="filteredItems">
<xsl:for-each select="$items/item[day=$day]">
<xsl:copy-of select="." />
</xsl:for-each>
</xsl:variable>
... you could use a sequence. This will select the actual items, as opposed to create copies of them
<xsl:variable name="filteredItems">
<xsl:sequence select="$items/item[day=$day]" />
</xsl:variable>
This was, doing $filteredItems/item will still work.
Alternatively, you could take the opposite approach, and do away with the need to specify /item in all the expressions.
First, define your news variable like so:
<xsl:variable name="news" as="element()*">
This means you can write the expression that uses it like so:
<xsl:copy-of select="$news[position()=$position]/content" />
And similarly for filteredItems....
<xsl:variable name="filteredItems" select="$items[day=$day]" />

xslt - adding Lp. to node

I'm new in xslt, so I've some problems with adding Lp to my transformation.
This's my simple xml data:
<booking>
<bookingID>ww1</bookingID>
<voucherNumber>R-108</voucherNumber>
</booking>
<booking>
<bookingID>ww2</bookingID>
<voucherNumber>R-108</voucherNumber>
</booking>
<booking>
<bookingID>ww3</bookingID>
<voucherNumber>R-108</voucherNumber>
</booking>
<booking>
<bookingID>ww4</bookingID>
<voucherNumber>R-109</voucherNumber>
</booking>
<booking>
<bookingID>ww5</bookingID>
<voucherNumber>R-109</voucherNumber>
</booking>
<booking>
<bookingID>ww6</bookingID>
<voucherNumber>R-110</voucherNumber>
</booking>
The key is voucherNumber, i need to add Lp for the same voucherNumber
I'need output text file to look like this:
ID;VN,LP
ww1;108;1
ww2;108;2
ww3;108;3
ww4;109;1
ww5;109;2
ww6;110;1
I add the key on voucherNumber
<xsl:key name="x" match="booking" use="voucherNumber"/>
in for-each statement I've add this code: it's adding me on the last position (i know that i can change this for another position) the number of count my items for the same voucherNumber, but how i can add number Lp for the other items?
<xsl:choose>
<xsl:when test="generate-id(.) =generate-id(key('x',voucherNumber)[last()])">
<xsl:value-of select="count(key('x',voucherNumber)) "/>
</xsl:when>
<xsl:otherwise>
-- need LP for other items --
</xsl:otherwise>
</xsl:choose>
I can use only version 1.0 of xslt stylesheet.
Thank you for your advice
Best regards
It looks like you are trying to use Muenchian Grouping here, but what you probably should do is start off by selected the booking elements with the first occurrence of each distinct voucherNumber
<xsl:for-each select="booking[generate-id() = generate-id(key('x',voucherNumber)[1])]">
Then, you have a nested xsl:for-each where you get all the booking elements within that group (i.e. the booking elements with the same voucherNumber)
<xsl:for-each select="key('x', voucherNumber)">
Then, within this next xsl:for-each you can use the position() function to get the count of the record within that specific group
Try this XSLT
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="text" />
<xsl:key name="x" match="booking" use="voucherNumber"/>
<xsl:template match="/*">
<xsl:for-each select="booking[generate-id() =generate-id(key('x',voucherNumber)[1])]">
<xsl:for-each select="key('x', voucherNumber)">
<xsl:value-of select="bookingID" />
<xsl:text>,</xsl:text>
<xsl:value-of select="substring-after(voucherNumber, '-')" />
<xsl:text>,</xsl:text>
<xsl:value-of select="position()" />
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Note, this assumed your actual XML is well-formed and there is a single root element containing all your booking elements.
I have no idea what "Lp" means. Assuming you want to number the bookings sequentially, restarting on voucherNumber, try something like:
-- Edit --
The proper solution here would be to use <xsl:number> to number the nodes. However, since I could not find a single combination of attributes that would work the same way with all XSLT 1.0 processors, I have resorted to the following hack:
<xsl:key name="booking-by-voucherNumber" match="booking" use="voucherNumber"/>
<xsl:template match="/root">
<xsl:for-each select="booking">
<!-- get id and voucher number -->
<xsl:variable name="id" select="generate-id()" />
<xsl:for-each select="key('booking-by-voucherNumber', voucherNumber)">
<xsl:if test="generate-id()=$id">
<xsl:value-of select="position()"/>
</xsl:if>
</xsl:for-each>
<!-- new line -->
</xsl:for-each>
</xsl:template>

Get Parent Attribute in the Current Position with XSLT

I have the following data:
<books>
<entry id="8">
<author name="tony-blair">Tony Blair</author>
</entry>
<entry id="9">
<author name="william-campbell">William Campbell</author>
</entry>
</books>
And use the following template
<xsl:template match="books/entry">
<xsl:value-of select="author"/>
<xsl:value-of select="ancestor::books/entry/#id"/>
</xsl:template>
I try to use ancestor::books/entry/#id but it results only the first id.
How to get the parent entry id while we are in the current position entry?
<xsl:template match="books/entry">
<xsl:value-of select="author"/>
<xsl:value-of select="#id"/>
</xsl:template>
Within a
<xsl:template match="books/entry">
the current context node is the entry element, so you can just use
<xsl:value-of select="#id" />
You don't need to go up to the books element and back down again.
<xsl:template match="entry">
<xsl:value-of select="author"/>
<xsl:value-of select="#id"/>
</xsl:template>
As others have said, you don't need to go up and down the tree.
books/entry is a tiny performance optimisation but it seems you're not there yet, so keep things simple and you're probably not processing massive documents anyway.

XSL. Evaluate expression

Sorry for my English.
XSL 1.0. How can I to calculate expression from element or attribute value?
For example XML:
<position>
<localizedName>ref-help</localizedName>
<reference>concat('../help/', $lang, '/index.html')</reference>
</position>
I try use expression from 'reference' attribute:
<xsl:for-each select="/content/positions/position">
<li>
<!--Save expression to variable...-->
<xsl:variable name="path" select="reference"/>
<!--Evaluate variable and set it value to 'href'-->
<a target="frDocument" href="{$path}">
<xsl:variable name="x" select="localizedName"/>
<xsl:value-of select="$resources/lang:resources/lang:record[#id=$x]"/>
</a>
</li>
</xsl:for-each>
But I get string:
file:///C:/sendbox/author/application/support/concat('../help/',%20%24lang,%20'/index.html')
How can I evaluate it?
Regards
If your XSLT processor implements the EXSLT extensions, you can reference a function that dynamically evaluates strings as XPath expressions:
<xsl:stylesheet
version="1.0"
xmlns="http://www.w3.org/1999/XSL/Transform"
xmlns:dyn="http://exslt.org/dynamic"
extension-element-prefixes="dyn"
>
<xsl:template match="content">
<xsl:apply-templates select="positions/position" />
</xsl:template>
<xsl:template match="position">
<li>
<a target="frDocument" href="{dyn:evaluate(reference)}">
<xsl:value-of select="
$resources/lang:resources/lang:record[#id=current()/localizedName]
"/>
</a>
</li>
</xsl:template>
</xsl:stylesheet>
Note:
there is no need to save things in a variable before using them
there is a current() function you might have missed
use <xsl:apply-templates> and <xsl:template> in favor of <xsl:for-each>