Last element of this group of node xslt - xslt

XML data :
<SECTION_CONTENT_LIST_ITEM>
<NTC_NAV3>
<IMMUTABLE_ID>5786
</IMMUTABLE_ID>
<INSTRUCTION_LIST>
<INSTRUCTION_LIST_ITEM>
<NTC_NAV3INSTRUCT>
<DESCRIPTION>
<P>descrzione breve di una storia</P>
</DESCRIPTION>
</NTC_NAV3INSTRUCT>
</INSTRUCTION_LIST_ITEM>
</INSTRUCTION_LIST>
</NTC_NAV3>
</SECTION_CONTENT_LIST_ITEM>
<SECTION_CONTENT_LIST_ITEM>
<NTC_NAV3>
<IMMUTABLE_ID>5787
</IMMUTABLE_ID>
<INSTRUCTION_LIST>
<INSTRUCTION_LIST_ITEM>
<NTC_NAV3INSTRUCT>
<DESCRIPTION>
<P>descriviamo in questo capitolo qualcosa</P>
</DESCRIPTION>
</NTC_NAV3INSTRUCT>
</INSTRUCTION_LIST_ITEM>
</INSTRUCTION_LIST>
</NTC_NAV3>
</SECTION_CONTENT_LIST_ITEM>
XSL:
<xsl:for-each select="SECTION_CONTENT_LIST">
<xsl:for-each select="SECTION_CONTENT_LIST_ITEM">
<xsl:sort select="NTC_NAV3/IMMUTABLE_ID" data-type="text" order="ascending"/>
<xsl:for-each select="NTC_NAV3">
<div style="text-align:left; border-top:2px solid black;">
<span style="line-height:normal; text-align:center; ">
<xsl:value-of select=".//NTC_NAV3INSTRUCT/DESCRIPTION"/>
</span>
</div>
</xsl:for-each>
</xsl:for-each>
</xsl:for-each>
In output I would only the output :
descriviamo in questo capitolo qualcosa
this is the last description orderd by immutable_id

You could use
<xsl:for-each select="SECTION_CONTENT_LIST">
<xsl:for-each select="SECTION_CONTENT_LIST_ITEM">
<xsl:sort select="NTC_NAV3/IMMUTABLE_ID" data-type="text" order="ascending"/>
<xsl:for-each select="NTC_NAV3[last()]">
<div style="text-align:left; border-top:2px solid black;">
<span style="line-height:normal; text-align:center; ">
<xsl:value-of select=".//NTC_NAV3INSTRUCT/DESCRIPTION"/>
</span>
</div>
</xsl:for-each>
</xsl:for-each>
</xsl:for-each>
assuming you only want to process the last NTC_NAV3 element.
If that does not help then consider to show a sample of the XML input and the corresponding HTML output you want to create with XSLT.

Related

How to evaluate XSLT current-group elements to add a parent to a specific subset of elements?

I have a flat input of paragraphs with classes identifying the information type:
<body>
<p class="Step">First step</p>
<p class="ListBullet">List item</p>
<p class="ListBullet">List item</p>
<p class="ListBullet">List item</p>
<p class="Information">Step information</p>
<p class="CodeSample">Step sample</p>
<p class="Step">Second step</p>
<p class="Information">Step information</p>
<p class="CodeSample">Step sample</p>
</body>
I need to turn this into a format structured into steps, such as:
<step>
<ol>
<li>List item 1</li>
<li>List item 2</li>
<li>List item 3</li>
</ol>
<info>Step information</info>
<code>Step sample</code>
</step>
<step>
<info>Step information</info>
<code>Step sample</code>
</step>
I use a for-each-group function to form step groups and then iterate through the group elements, calling separate transformation templates for each child, as the number and type of child elements may differ for each step.
<xsl:for-each-group select="//p" group-starting-with="p[#class='Step']">
<step>
<xsl:apply-templates select="current-group()"/>
</step>
</xsl:for-each-group>
<xsl:template match="//p[#class='ListBullet']" >
<li>
<xsl:value-of select="."/>
</li>
</xsl:template>
<xsl:template match="//p[#class='Information']" >
<info>
<xsl:value-of select="."/>
</info>
</xsl:template>
<xsl:template match="//p[#class='CodeSample']" >
<code>
<xsl:value-of select="."/>
</code>
</xsl:template>
However, I am unable to figure out how to add the elements. I can make it appear either for all list items or none. I assume I'd need some kind of when/otherwise statement in the main template, but can't figure out how... Can someone please help?
It seems you just want/need a nested group-adjacent:
<xsl:for-each-group select="//p" group-starting-with="p[#class='Step']">
<step>
<xsl:for-each-group select="tail(current-group())" group-adjacent="#class = 'ListBullet'">
<xsl:choose>
<xsl:when test="current-grouping-key()">
<ol>
<xsl:apply-templates select="current-group()"/>
</ol>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="current-group()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</step>
</xsl:for-each-group>

Using Xsl-Key and generate-id() function

I would associate at the first IMG the first ATTACHED_FILENAME and
at the second IMG the second attached filename.
This is my XML:
<INSTRUCTION_LIST_ITEM>
<NTC_SD_INSTRUCT>
<ACTION>Sostituire</ACTION>
<PLACEMENT>le righe 10 ÷ 18 con:</PLACEMENT>
<DESCRIPTION>
<P>Il porto è protetto da un molo foraneo.</P>
<P>
<IMG border="0" hspace="0" alt="" align="baseline" src="C:\Users\l_sturla\Desktop\albany.jpg"/>
</P>
<P>Ben visibile da nord è il faro della Vittoria.</P>
<P>
<IMG border="0" hspace="0" alt="" align="baseline" src="C:\Users\l_sturla\Desktop\Faro vittoria.JPG"/>
</P>
<P> </P>
<P>Mantenersi a distanza di sicurezza.</P>
</DESCRIPTION>
<ATTACHMENT_LIST>
<ATTACHMENT>
<ATTACHED_FILENAME>albany.jpg</ATTACHED_FILENAME>
</ATTACHMENT>
<ATTACHMENT>
<ATTACHED_FILENAME>Faro vittoria.JPG</ATTACHED_FILENAME>
</ATTACHMENT>
</ATTACHMENT_LIST>
</NTC_SD_INSTRUCT>
</INSTRUCTION_LIST_ITEM>
I create this XSLT:
<xsl:template match="//IMG">
<span style="font-style:italic">
<xsl:choose>
<xsl:when test="count(ancestor::DESCRIPTION//IMG) = count(ancestor::DESCRIPTION/following-sibling::ATTACHMENT_LIST/ATTACHMENT/ATTACHED_FILENAME)">
<img>
<xsl:attribute name="src">
<xsl:value-of select="ancestor::NTC_SD_INSTRUCT/ATTACHMENT_LIST/ATTACHMENT/ATTACHED_FILENAME"/>
</xsl:attribute>
</img>
</xsl:when>
</xsl:choose>
</span>
</xsl:template>
But this give always the first image. ATTACHED_FILENAME tag is the parameter of attribute SRC.
Try
<xsl:template match="IMG">
<xsl:variable name="counter">
<xsl:number level="any" from="DESCRIPTION"/>
</xsl:variable>
<img src="{(//ATTACHED_FILENAME)[number($counter)]}"/>
</xsl:template>
If you define a key <xsl:key name="attachment-by-pos" match="NTC_SD_INSTRUCT/ATTACHMENT_LIST/ATTACHMENT" use="concat(generate-id(ancestor::NTC_SD_INSTRUCT), '|', count(preceding-sibling::ATTACHMENT))"/>, then you can use a template
<xsl:template match="IMG">
<span style="font-style:italic">
<img src="{key('attachment-by-pos', concat(generate-id(ancestor::NTC_SD_INSTRUCT), '|', count(../preceding-sibling::*//IMG)))/ATTACHED_FILENAME}"/>
</span>
</xsl:template>

how to manipulate XSLT file to only display one div instead of dl with dt's dd's and span's

I have a Section in a result from and XSLT file that is like this:
<dl class="price-list">
<dt> </dt>
<dd class="salesprice">
DKK <span class="pricethousands">2</span>
<span class="pricegroupingseparator">.</span>
<span class="pricehundreds">714</span>
<span class="pricedecimalseparator">,</span><span class="pricedecimals">40</span>
</dd>
</dl>
I have tried a few times to change the XSLT but i do not seem to be able to change it.
I'd like the result to become something like this or similar:
<div>
<p>DKK </p>
<p>2.714,40</p>
</div>
the xslt is currently using something like this:
<xsl:template match="/">
<xsl:variable name="html">
<xsl:for-each select="msxml:node-set($list)/product">
<xsl:call-template name="list-product">
<xsl:with-param name="list-product" select="."/>
<xsl:with-param name="list-vat" select="$list-vat"/>
....
</xsl:call-template>
</xsl:for-each>
</xsl:variable>
<xsl:copy-of select="$html" />
</xsl:template>
I hope you can help me to make this work. I know I haven't provided it all but as it is currently I can't provide more because I'm not allowed to.
<product>
<estocklevel></estocklevel>
<variantof></variantof>
<id></id>
<weightingram>137750</weightingram>
<unit></unit>
<volume>0</volume>
<discountgroup></discountgroup>
<manufactor></manufactor>
<deliverydate></deliverydate>
<url></url>
<texts></texts>
<name></name>
<longdescription></longdescription>
<shortdescription></shortdescription>
<htmltitle></htmltitle>
<metadescription></metadescription>
<metakeywords></metakeywords>
<group></group>
<alternativeitemid></alternativeitemid>
<alternativeitemrule></alternativeitemrule>
<duties></duties>
<priceinfo currency="" price="" standardsalesprice="" vatincludedinprice="False" vatrate="" quantity="">
<incvat price="">
<price></price>
<standardsalesprice></standardsalesprice>
<discountamount></discountamount>
<discountpercentage></discountpercentage>
<vat></vat>
</incvat>
<exvat price="">
<price></price>
<standardsalesprice></standardsalesprice>
<discountamount></discountamount>
<discountpercentage></discountpercentage>
<vat></vat>
</exvat>
</priceinfo>
<bulkdiscountpricelist></bulkdiscountpricelist>
<productcategories extra="productcategories"></productcategories>
<indicativedate extra="indicativedate"></indicativedate>
<oncampaign extra="oncampaign"></oncampaign>
<cnt extra="cnt"></cnt>
<weight extra="weight"></weight>
<misc extra="misc"></misc>
<dcurlv2 extra="dcurlv2"></dcurlv2>

xslt 1.0 - Trying to get nodes for a specific value

I've got an XML file that has many similarly named nodes but attributes within certain nodes are unique. I want to output into an HTML page only the nodes that fall under a certain attribute value.
Here is the xml:
<document>
<component>
<section>
<templateId value="temp_1" />
<entry>
<act>
<code displayName="temp_1:code_1" />
</act>
</entry>
<entry>
<act>
<code displayName="temp_1:code_2" />
</act>
</entry>
<entry>
<act>
<code displayName="temp_1:code_3" />
</act>
</entry>
</section>
<section>
<templateId value="temp_2" />
<entry>
<act>
<code displayName="temp_2:code_1" />
</act>
</entry>
<entry>
<act>
<code displayName="temp_2:code_2" />
</act>
</entry>
</section>
</component>
</document>
From this specific example, I want to only get the displayName value from the section that has the templateId value of temp_2. This is the XSL code that I'm using but it is getting everything, not just the section that I want. i know the first "when" is working because the right header (between the span tags) is displaying properly. It's just the for-each through the entries.
<xsl:tempalte match="/">
<xsl:choose>
<xsl:when test="//templateId/#value='temp_2'">
<div style="margin-bottom: 5px; padding: 5px; border-bottom: 1px solid #000000;">
<span style="font-weight: bold;">Template 2: </span>
<br />
<xsl:choose>
<xsl:when test="count(//section/entry) != 0">
<xsl:for-each select="//section/entry">
<xsl:choose>
<xsl:when test="position() = 1">
<xsl:value-of select="act/code/#displayName" />
</xsl:when>
<xsl:otherwise>
<br/>
<xsl:value-of select="act/code/#displayName" />
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:when>
<xsl:otherwise>
No codes to display
</xsl:otherwise>
</xsl:choose>
</div>
</xsl:when>
</xsl:choose>
</xsl:template>
It should display like so:
temp_2:code_1
<br>temp_2:code_2
Any help would be greatly appreciated.
I guess you want to completely restudy XSLT and its philosophy. Don't program it like it was BASIC. The basic pattern, at least for your case, is that an XSLT program is a collection of templates to handle matching elements. Instead of littering your code with if and choose, write templates with the proper matching conditions. Instead of BASIC's FOR I=1 TO 10, use <xsl:apply-templates/> to "iterate" over the children. Here's the basic idea:
<xsl:template match="/">
<html>
<xsl:apply-templates/>
</html>
</xsl:template>
<xsl:template match="templateId"/> <!-- skip templateID elements by default -->
<xsl:template match="templateId[#value='temp_2']">
<div style="margin-bottom: 5px; padding: 5px; border-bottom: 1px solid #000000;">
<span style="font-weight: bold;">Template 2: </span>
<xsl:apply-templates/>
</div>
</xsl:template>
<xsl:template match="code">
<xsl:value-of select="#displayName"/>
<xsl:if test="position() != 1"><br/></xsl:if>
</xsl:template>
<xsl:template match="section[count(entry)=0]">
No codes to display
</xsl:template>
Why no template for act elements? Well, by default XSLT will provide you with a template which does a <xsl:apply-templates/>.
Based on your description it sounds like you only want the temp_2 values in your for-each.
That being the case you can just update your select to the following:
<xsl:for-each select="//section[templateId/#value = 'temp_2']/entry">
This says to grab any entry under section that has a templateId with an attribute of value that equals 'temp_2'.

XSLT 1.0 Grouping on multiple values on multiple levels

edited in response to comments *
Hello,
I am an XSLT noob and need some help. I am trying to do an filter/group combination with XSLT 1.0 (can't use XSLT 2.0 for this application).
Here is an example of the xml
<entry>
<item>
<name>Widget 2</name>
<rank>2</rank>
<types>
<type>Wood</type>
<type>Fixed</type>
<type>Old</type>
</types>
</item>
<item>
<name>Widget 1</name>
<rank>2</rank>
<types>
<type>Metal</type>
<type>Broken</type>
<type>Old</type>
</types>
</item>
<item>
<name>Widget 3</name>
<rank>1</rank>
<types>
<type>Metal</type>
<type>New</type>
</types>
</item>
</entry>
Now what I want to do is output html where I get a subset of the XML based on <type> and then group on rank. For example, if the user selects all items with the type Metal, the output should be:
<p class="nospace"><font color="#800000">
<b>Rank 1</b></font></p>
<li id="mylist"><b>Widget 3</b></li>
<br\>
<p class="nospace"><font color="#800000">
<b>Rank 2</b></font></p>
<li id="mylist"><b>Widget 1</b></li>
<br\>
of if the user user chooses the type Old the output would be
<p class="nospace"><font color="#800000">
<b>Rank 2</b></font></p>
<li id="mylist"><b>Widget 1</b></li>
<li id="mylist"><b>Widget 2</b></li>
<br\>
I can group using keys on rank along easily enough, but trying to do both is not working. Here is a sample of the xslt I have tried:
<xsl:param name="typeParam"/>
<xsl:key name="byRank" use="rank" match="item"/>
<xsl:for-each select="item[count(.|key('byRank',rank)[1])=1]">
<xsl:sort data-type="number" select="rank"/>
<xsl:for-each select="key('byRank',rank)">
<xsl:sort select="name"/>
<xsl:if test="count(rank)>0">
<p class="nospace"><font color="#800000"><b>Rank<xsl:value-of select="rank"/></b></font></p>
<xsl:for-each select="types[types=$typeParam]">
<li id="mylist"><b><xsl:value-of select="../name"/></b></li>
</xsl:for-each>
<br/>
</xsl:if>
</xsl:for-each>
</xsl:for-each>
The result I get from this is I do indeed get the subset of my xml that I want but it also displays all of the various rank values. I want to limit it to just the ranks of the type that is specified in $typeParam.
I have tried moving the for-each statement to earlier in the code as well as modifying the if statement to select for $typeParam but neither works. I have also tried concat-ing my key with rank and type but that doesn't seem to work either (It only works if the type in $typeParam is the first child under types).
Thanks
jeff
This stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="kItemByRank" match="item" use="rank"/>
<xsl:param name="pType" select="'Old'"/>
<xsl:template match="entry">
<xsl:for-each select="item[count(.|key('kItemByRank',rank)[1])=1]">
<xsl:sort select="rank" data-type="number"/>
<xsl:variable name="vGroup" select="key('kItemByRank',rank)[
types/type = $pType
]"/>
<xsl:if test="$vGroup">
<p class="nospace">
<font color="#800000">
<b>
<xsl:value-of select="concat('Rank ',rank)"/>
</b>
</font>
</p>
<xsl:apply-templates select="$vGroup">
<xsl:sort select="name"/>
</xsl:apply-templates>
<br/>
</xsl:if>
</xsl:for-each>
</xsl:template>
<xsl:template match="item">
<li id="mylist">
<b>
<xsl:value-of select="name"/>
</b>
</li>
</xsl:template>
</xsl:stylesheet>
Output:
<p class="nospace">
<font color="#800000">
<b>Rank 1</b>
</font>
</p>
<li id="mylist">
<b>Widget 3</b>
</li>
<br />
<p class="nospace">
<font color="#800000">
<b>Rank 2</b>
</font>
</p>
<li id="mylist">
<b>Widget 1</b>
</li>
<br />
And whit pType param set to 'Old', output:
<p class="nospace">
<font color="#800000">
<b>Rank 2</b>
</font>
</p>
<li id="mylist">
<b>Widget 1</b>
</li>
<li id="mylist">
<b>Widget 2</b>
</li>
<br />