How do i render a limited number of elements in XSLT? - xslt

I need to render a specified number of elements from an XML source, where the elements "DueDate" is not exeeded. Here is an example of the xml:
<Items>
<Item>
<Title>Title 1</Title>
<DueDate>01-02-2008</DueDate>
</Item>
<Item>
<Title>Title 2</Title>
<DueDate>01-02-2009</DueDate>
</Item>
<Item>
<Title>Title 3</Title>
<DueDate>01-02-2010</DueDate>
</Item>
<Item>
<Title>Title 4</Title>
<DueDate>01-02-2011</DueDate>
</Item>
<Item>
<Title>Title 5</Title>
<DueDate>01-02-2012</DueDate>
</Item>
<Item>
<Title>Title 6</Title>
<DueDate>01-02-2013</DueDate>
</Item>
</Items>
The number of elements to display and the current date are passed to the XSLT as paramaters.
Is it possible to count the number of rendered elements in a for-each loop in Xslt? Or is there a better approach?
An example could be that the limit was set to 3 elements. In this example I would expect to see the following results: "Title 3", "Title 4" and "Title 5".

You can do something like this. Make it into a template you can call with paramters:
<xsl:template match="/">
<xsl:variable name="count" select="3"/>
<xsl:for-each select="Items/Item">
<xsl:if test="position() < $count">
<xsl:value-of select="Title"/> - <xsl:value-of select="DueDate"/>
</xsl:if>
</xsl:for-each>

Try nesting this inside a standard xsl for-each where n in your case is 3:
<xsl:if test="position() < n">
But if you also want to check the date then you will need nest another if and create dates in the format yyyyMMdd which can be numerically compared like this:
<xsl:variable name="secondDate" select="concat(substring(submissionDeadline, 1,4),substring(submissionDeadline, 6,2),substring(submissionDeadline, 9,2))"/>
<xsl:if test="$firstDate > $secondDate">

The main problem is that your input XML does not conform to the useful 'yyyy-mm-dd' format. This makes sorting/filtering these items a pain in the ass behind.
If I understand you correctly, you need to
filter on due date first
apply a maximum to the output after that
The XPath for selecting all <Item>s up to a certain maximum due date would go like this:
Item[
substring-before(DueDate, '-')
<=
substring-before($MaxDueDate, '-')
and
substring-before(substring-after(DueDate, '-'), '-')
<=
substring-before(substring-after($MaxDueDate, '-'), '-')
and
substring-after(substring-after(DueDate, '-'), '-')
<=
substring-after(substring-after($MaxDueDate, '-'), '-')
][
position() <= $MaxCount
]
Now compare that to the trivial way, if you had 'yyyy-mm-dd' dates:
Item[
DueDate <= $MaxDueDate
][
position() <= $MaxCount
]
So, to copy only these elements, you would go:
<xsl:template match="Items">
<xsl:copy>
<xsl:copy-of select="
{ the expression I gave above }
" />
</xsl:copy>
</xsl:template>
For parameter values of '01-02-2012' and 4, respectively, I get:
<Items>
<Item>
<Title>Title 1</Title>
<DueDate>01-02-2008</DueDate>
</Item>
<Item>
<Title>Title 2</Title>
<DueDate>01-02-2009</DueDate>
</Item>
<Item>
<Title>Title 3</Title>
<DueDate>01-02-2010</DueDate>
</Item>
<Item>
<Title>Title 4</Title>
<DueDate>01-02-2011</DueDate>
</Item>
</Items>

Related

Usage of the Variable inside the select-value clause to traverse through the XML path

I am trying to fetch the XML value based on a condition, if the variable value matches the value of the XML path mentioned then to obtain the value of its own sub elements.
The Input XML looks like below
<ns1:productSpecificationFullDTO xmlns:ns1="http://www.micros.com/creations/core/domain/dto/v1p0/full" xmlns:ns2="http://www.micros.com/creations/core/domain/dto/v1p0/simple">
<ns1:product>
<ns1:name>Test Component 1</ns1:name>
<ns1:parent>false</ns1:parent>
</ns1:product>
<ns1:product>
<ns1:name>Test Component 2</ns1:name>
<ns1:parent>false</ns1:parent>
</ns1:product>
<ns1:specification>
<ns1:name>Test Component 1</ns1:name>
<ns1:parent>false</ns1:parent>
<ns1:Labeling>
<ns1:mainProductTitle>Test1</ns1:ns1:mainProductTitle>
</ns1:Labeling>
</ns1:specification>
<ns1:specification>
<ns1:name>Test Component 2</ns1:name>
<ns1:parent>false</ns1:parent>
<ns1:Labeling>
<ns1:mainProductTitle>Test2</ns1:ns1:mainProductTitle>
</ns1:Labeling>
</ns1:specification>
My XSLT Definition is below
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:ns1="http://www.micros.com/creations/core/domain/dto/v1p0/full" xmlns:ns2="http://www.micros.com/creations/core/domain/dto/v1p0/simple" exclude-result-prefixes="ns1 ns1">
<xsl:template match="/">
<ItemDetails>
<Items>
<!-- Food section start here -->
<xsl:for-each select="/ns1:productSpecificationFullDTO/ns1:product/ns1:parent[text() != 'true']/../ns1:name[text() != 'Parent']/..">
<xsl:variable name="subItem" select="ns1:name/text()"/>
<Item>
<name>
<xsl:value-of select="$subItem"/>
</name>
<LongDescription>
<xsl:value-of select="normalize-space(ns1:productSpecificationFullDTO/ns1:specification/ns1:parent[text() != 'true']/../ns1:name[text() = '''$subItem''']/../ns1:Labeling/ns1:mainProductTitle/text())"/>
</LongDescription>
</Item>
</xsl:for-each>
</Items>
</ItemDetails>
</xsl:template>
The output is as below
<Items>
<Item>
<name>Test Component 1</name>
<LongDescription/>
</Item>
<Item>
<name>Test Component 2</name>
<LongDescription/>
</Item>
Desired Output is
<Items>
<Item>
<name>Test Component 1</name>
<LongDescription>Test1<LongDescription/>
</Item>
<Item>
<name>Test Component 2</name>
<LongDescription>Test2<LongDescription/>
</Item>
As Seen above i'm unable to fetch the value of that variable's sub element.
Please advise, Thanks
I think this solves what you are trying to accomplish, simplifying your XPath expressions and using a key to get to the linked descriptions.
<xsl:stylesheet version="3.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ns1="http://www.micros.com/creations/core/domain/dto/v1p0/full"
xmlns:ns2="http://www.micros.com/creations/core/domain/dto/v1p0/simple"
exclude-result-prefixes="ns1 ns2">
<xsl:output method="xml" indent="yes"/>
<xsl:key name="keySpec" match="ns1:specification" use="ns1:name"/>
<xsl:template match="/">
<ItemDetails>
<Items>
<!-- Food section start here -->
<xsl:for-each select="/ns1:productSpecificationFullDTO/ns1:product[not(ns1:parent='true') and not(ns1:name='Parent')]">
<Item>
<name>
<xsl:value-of select="ns1:name"/>
</name>
<LongDescription>
<xsl:value-of select="key('keySpec',ns1:name)/ns1:Labeling/ns1:mainProductTitle"/>
</LongDescription>
</Item>
</xsl:for-each>
</Items>
</ItemDetails>
</xsl:template>
</xsl:stylesheet>
See it working here : https://xsltfiddle.liberty-development.net/6qjt5Sw/1

What does the expression $data[not(key('myKey',#myRef))] in XSLT mean?

I read expressions like this
<xsl:variable name="myVar" select="$data[not(key('myKey',#myRef))]"/>
in legacy code. Most likeley it is code from experts ;-). I'm wondering what it does, how it works and how i could reeng it in order to make it more readable. Thank you.
Keys are an important aspect of XSLT. Instead of re-engineering them, it's better to learn the concept.
Keys can be understood as tables with nodes stored under specific keys. They are defined like this:
<xsl:key name="addressByStreet" match="address" use="street"/>
The name attribute is just a QName (similar to a variable name). The match attribute holds an XPath expression that works similarly to the match attribute of <xsl:template>. When the processor finds a node that matches the expression, it evaluates the XPath expression of the use attribute in the context of the matched element. If this expression returns values, they will be used to create new entries in the "key table" for the matched element.
To illustrate that: The above key creates a table with all the <address> elements in the processed document, keyed by the value of their <street> child. This means, if you have these elements:
<address>
<street>Main Street</street>
<number>123</number>
</address>
<address>
<street>Main Street</street>
<number>456</number>
</address>
<address>
<street>Country Road</street>
<street>Country Rd.</street>
<number>789</number>
</address>
… you could then use key('addressByStreet', 'Main Street') to retrieve all the listed addresses in Main Street.
You can use both key('addressByStreet', 'Country Road') and key('addressByStreet', 'Country Rd.') to retrieve the last address.
Why use keys here? The above expression could be re-implemented like //address[street='Main Street'], but now every time this expression is called, the XSLT processor likely goes through the entire document again. That's a problem if a template or loop is called often. Keys can have huge performance benefits (e.g. reduce complexity from O(n²) to O(n)) because the results are "cached".
There are many applications and patterns in which keys are used. For example if you have this XML:
<street-list>
<street>Main Street</street>
<street>Bumpy Road</street>
</street-list>
The expression street-list/street[not(key('addressByStreet', .))] will filter the list of streets and only return streets for which there is no address in the above list – i.e. only "Bumpy Road" in this case because for "Main Street", a key entry exists.
A typical application of keys in XSLT 1 is Muenchian grouping.
I've got the use case now. No, it is not legacy code. This is clear from context and definition of key and data.
If we have data like this:
<xsl:variable name="dict">
<ITEMS>
<ITEM id="1" content="it1">
<ITEM-REF ref="3"/>
</ITEM>
<ITEM id="2" content="it2">
<ITEM-REF ref="1"/>
</ITEM>
<ITEM id="3" content="it3">
<ITEM-REF ref="6"/>
</ITEM>
<ITEM id="4" content="it4">
<ITEM-REF ref="3"/>
</ITEM>
<ITEM id="5" content="it5">
<ITEM-REF ref="5"/>
</ITEM>
<ITEM id="6" content="it6">
<ITEM-REF ref="8"/>
</ITEM>
<ITEM id="7" content="it7">
<ITEM-REF ref="9"/>
</ITEM>
</ITEMS>
</xsl:variable>
And we want to get all ITEM-REF elements with #ref values where there is no ITEM with the same #id value (broken links) the expression can help out:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl">
<xsl:output method="xml" encoding="utf-8" indent="yes"/>
<xsl:variable name="dict">
<ITEMS>
<ITEM id="1" content="it1">
<ITEM-REF ref="3"/>
</ITEM>
<ITEM id="2" content="it2">
<ITEM-REF ref="1"/>
</ITEM>
<ITEM id="3" content="it3">
<ITEM-REF ref="6"/>
</ITEM>
<ITEM id="4" content="it4">
<ITEM-REF ref="3"/>
</ITEM>
<ITEM id="5" content="it5">
<ITEM-REF ref="5"/>
</ITEM>
<ITEM id="6" content="it6">
<ITEM-REF ref="8"/>
</ITEM>
<ITEM id="7" content="it7">
<ITEM-REF ref="9"/>
</ITEM>
</ITEMS>
</xsl:variable>
<xsl:key name="itemkey" match="ITEM" use="#id"/>
<xsl:template match="START">
<xsl:variable name="allItems" select="msxsl:node-set($dict)//ITEM"/>
<xsl:variable name="allItemRefs" select="msxsl:node-set($dict)//ITEM-REF"/>
<xsl:variable name="itemRefsNotReferencingOtherItems" select="$allItemRefs[not(key('itemkey',#ref))]"/>
<REFERENCED-NOT-EXISTING>
<xsl:for-each select="msxsl:node-set($itemRefsNotReferencingOtherItems)">
<ITEM>
<xsl:attribute name="id">
<xsl:value-of select="#ref"/>
</xsl:attribute>
</ITEM>
</xsl:for-each>
</REFERENCED-NOT-EXISTING>
</xsl:template>
</xsl:stylesheet>
Output:
<?xml version="1.0" encoding="utf-8"?>
<REFERENCED-NOT-EXISTING>
<ITEM id="8" />
<ITEM id="9" />
</REFERENCED-NOT-EXISTING>
Input file:
<?xml version="1.0" encoding="utf-8"?>
<START/>

Copy single value in XML to two different places in other XML

I have an XML file with this structure:
<DetailTxt>
<Text>
<span>Some Text</span>
</Text>
<TextComplement Kind="Owner" MarkLbl="1">
<ComplCaption>
Caption 1
</ComplCaption>
<ComplBody>
Body 1
</ComplBody>
</TextComplement>
<Text>
<span>More Text</span>
</Text>
</DetailTxt>
Here is the part of the XSLT that is relevant here:
<xsl:template match="*[local-name() = 'DetailTxt']">
<xsl:apply-templates select="*[local-name() = 'Text']"/>
</xsl:template>
<xsl:template match="*[local-name() = 'Text']">
<item name="{local-name()}">
<richtext>
<par>
<run>
<xsl:text disable-output-escaping="yes"><![CDATA[</xsl:text>
<xsl:apply-templates/>
<xsl:text disable-output-escaping="yes">]]></xsl:text>
</run>
</par>
</richtext>
</item>
<item name="{local-name()}">
<richtext>
<par>
<run>
<xsl:text disable-output-escaping="yes"><![CDATA[</xsl:text>
<xsl:value-of select="concat('[', ../TextComplement/#Kind, ../TextComplement/#MarkLbl,']')" />
<xsl:text disable-output-escaping="yes">]]></xsl:text>
</run>
</par>
</richtext>
</item>
</xsl:template>
I expect the output to look like this:
<item name="Text">
<richtext>
<par>
<run><![CDATA[
<span>Some Text</span>
</p>]]></run>
</par>
</richtext>
</item>
<item name="Text">
<richtext>
<par>
<run><![CDATA[[Owner1]]]></run>
</par>
</richtext>
</item>
But the line using the TextComplement XPath looks like this:
<run><![CDATA[[]]]></run>
All values from TextComplement are missing. Whats wrong with the XPath here?
EDIT: I completely reworked my question and put in a CONCRETE question resulting from the first answer. That kind of invalidates the first answer but IMHO improves the question.
Not sure how the XSLT looks like but you can try adding the following template with the concat() function for getting the output.
<xsl:template match="Text">
<document version="9.0" form="Form1">
<item name="{local-name()}">
<xsl:copy-of select="span" />
</item>
<item name="{local-name()}">
<span>
<xsl:value-of select="concat('[', ../TextComplement/#Kind, ../TextComplement/#MarkLbl, ']')" />
</span>
</item>
</document>
</xsl:template>
This template is applied to the <Text> node and the ../ is used to go up one level and then access the attributes of <TextComplement> using the XPath.
The output of the template when applied to your XML will look like.
<document form="Form1" version="9.0">
<item name="Text">
<span>Some Text</span>
</item>
<item name="Text">
<span>[Owner1]</span>
</item>
</document>
The same template will also get applied to the <Text> node having More Text content and produce similar output.
I found a solution myself for the concrete question. I quess this is IBM Notes / LotusScript specific issue.
When using the selector
../TextComplement/#Kind
the parser returned an empty string. I changed to
../*[local-name() = 'TextComplement']/#Kind
and later (more concrete) to:
./following-sibling::*[local-name() = 'TextComplement']/#Kind
And that worked. I personally see no difference in these notations, but it seams that internally they are handled differently.

XSLT Generating incremental values

I have the following xml:
<Details>
<Head>
<pageid>123</pageid> <!-- Needs to be sequential starting with 0000000001 -->
</Head>
<Start>
<pageid>124</pageid>
<value>Details of Minerals</value>
</Start>
<Item>
<pageid>12</pageid>
<name>Coal</name>
</Item>
<Quantity>
<pageid>45</pageid>
<value>3</value>
<comments>NONE MENTIONED</comments>
</Quantity>
<Item>
<pageid>459</pageid>
<name>MICA</name>
</Item>
<Quantity>
<pageid>65</pageid>
<value>2</value>
<comments>NONE MENTIONED</comments>
</Quantity>
<END>
<pageid>78</pageid>
</END>
</Details>
I want to the value pageid to be incremental with 10 digits.
Sample o/p
<Details>
<Head>
<pageid>0000000001</pageid>
</Head>
<Start>
<pageid>0000000002</pageid>
<value>Details of Minerals</value>
</Start>
<Item>
<pageid>0000000003</pageid>
<name>Coal</name>
</Item>
<Quantity>
<pageid>0000000004</pageid>
<value>3</value>
<comments>NONE MENTIONED</comments>
</Quantity>
<Item>
<pageid>0000000005</pageid>
<name>MICA</name>
</Item>
<Quantity>
<pageid>0000000006</pageid>
<value>2</value>
<comments>NONE MENTIONED</comments>
</Quantity>
<END>
<pageid>0000000007</pageid>
</END>
</Details>
I tried using the following construct:
<xsl:variable name="counter" select="0000000000" saxon:assignable="yes"/>
<xsl:template match="//*[local-name()='pageid']">
<saxon:assign name="counter" select="$counter+0000000001"/>
<imp1:Line_id>
<xsl:value-of select="$counter"></xsl:value-of>
</imp1:Line_id>
But this wasnt helpful. Can u suggest a easier way to do it?
Instead of trying to use a variable counter, you could just make use of the xsl:number element here:
<xsl:template match="//*[local-name()='pageid']">
<imp1:Line_id>
<xsl:number level="any" format="0000000000" />
</imp1:Line_id>
</xsl:template>

Setting XSLT variables within for-each select

Good Day,
I have an XSLT template I'm assembling that look like:
<xsl:for-each select="CarParts/Items">
<div class="columns"><xsl:value-of select="Quantity"/></div>
<div class="columns"><xsl:value-of select="Amount"/></div>
<div class="columns">[SUBTOTAL]</div><br />
</xsl:for-each>
I know that I can define an XSLT variable like this:
<xsl:variable name="totalAmount" select="sum(CarParts/Items/Amount)" />
But I want my XSLT variable to be [SUBTOTAL] which is equal to Quantity * Amount within the for-each select loop. Is this possible? If this was SQL, this would be the equivalent of a computed column.
Any suggestions?
TIA,
coson
What you want to do is cast the value to a number, then you can multiply it as desired:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="/">
<results>
<xsl:for-each select="CarParts/Items">
<Item id="{position()}">
<q><xsl:value-of select="Quantity"/></q>
<a><xsl:value-of select="Amount"/></a>
<st><xsl:value-of select="number(Quantity)*number(Amount)"/></st>
</Item>
</xsl:for-each>
</results>
</xsl:template>
</xsl:stylesheet>
I changed the formatting a little since there was no provided input/CSS, but you should see what I was going for. Running it on my sample input of
<CarParts>
<Items>
<Quantity>1</Quantity>
<Amount>100.00</Amount>
</Items>
<Items>
<Quantity>2</Quantity>
<Amount>25.00</Amount>
</Items>
<Items>
<Quantity>3</Quantity>
<Amount>6</Amount>
</Items>
</CarParts>
I get the result of
<?xml version="1.0" encoding="utf-8"?>
<results>
<Item id="1">
<q>1</q>
<a>100.00</a>
<st>100</st>
</Item>
<Item id="2">
<q>2</q>
<a>25.00</a>
<st>50</st>
</Item>
<Item id="3">
<q>3</q>
<a>6</a>
<st>18</st>
</Item>
</results>