How to iterate counter in XSLT 1.0 based on some condition - templates

I have a requirement where I have to increment the count and assign that value to one of the target element.
For ex - My input request is something like this
<Deliveries>
<Delivery>
<OrderNo>
1234
</OrderNo>
<orderItem>
12
</orderItem>
</Delivery>
<Delivery>
<OrderNo>
S1234
</OrderNo>
<orderItem>
12
</orderItem>
</Delivery>
</Deliveries>
Delivery is parent element which is unbounded and my output XSD is nothing but table columns.
<Order_table>
<Orders>
<column1>
<column2>
</Orders>
</Order_table>
Now My requirement is that If OrderNo contains first letter as S then I have to assign 1 value to column1 else I have to simply copy the value of orderItem. and every time I get the OrderNo value starting with S then I have to increment the value by 1 but if orderNo is not starting with S then it should not increase by 1. Due to this logic , I am not able to use position function as well.
For example -
If my input is something like -
<Deliveries>
<Delivery>
<OrderNo>
S1234
</OrderNo>
<orderItem>
12
</orderItem>
</Delivery>
<Delivery>
<OrderNo>
1234
</OrderNo>
<orderItem>
12
</orderItem>
</Delivery>
<Delivery>
<OrderNo>
S1234
</OrderNo>
<orderItem>
12
</orderItem>
</Delivery>
</Deliveries>
Then my output should be -
<Order_table>
<Orders>
<column1>1</column1> <-- First value as 1 as Order starts from S
<column2>
</Orders>
<Orders>
<column1>12</column1> copy of orderItem bcz orderNo don't start with S
<column2>
</Orders>
<Orders>
<column1>2</column1> Increment from 1 to 2 as Order again starts from S
<column2>
</Orders>
</Order_table>
Could any please help me with my issue ? A piece of code will help me a lot.

Try:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<Order_table>
<xsl:apply-templates select="//OrderNo"/>
</Order_table>
</xsl:template>
<xsl:template match="OrderNo[starts-with(normalize-space(.), 'S')]">
<Orders>
<column1>
<xsl:number level="any" count="OrderNo[starts-with(normalize-space(.), 'S')]"/>
</column1>
<column2/>
</Orders>
</xsl:template>
<xsl:template match="OrderNo">
<Orders>
<column1><xsl:value-of select="normalize-space(../orderItem)"/></column1>
<column2/>
</Orders>
</xsl:template>
</xsl:stylesheet>

When your context is placed at a Delivery node, test for starts-with(OrderNo,'S'), then count backward the number of siblings using the same test. This will give you your incrementing value.
<xsl:template match="Delivery">
<Orders>
<column1>
<xsl:choose>
<xsl:when test="starts-with(OrderNo,'S')">
<xsl:value-of select="1+count(preceding-sibling::Delivery[starts-with(OrderNo,'S')])"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="orderItem"/>
</xsl:otherwise>
</xsl:choose>
</column1>
<column2/>
</Orders>
</xsl:template>
You will of course, also need complete the remainder of the stylesheet yourself, but this covers the logical that you outline in your question.

Related

XSLT 2.0 : picking up XML node by matching one child node and comparing other

I have input XML like below:
<parent>
<payment>
<id>123456</id>
<type>CANCELLED</type>
<date>2020-12-03</date>
<amount>100</amount>
</payment>
<payment>
<id>234567</id>
<type>FORCE</type>
<date>2020-12-01</date>
<amount>200</amount>
</payment>
<payment>
<id>345678</id>
<type>CANCELLED</type>
<date>2020-12-01</date>
<amount>300</amount>
</payment>
<payment>
<id>456789</id>
<type>FORCE</type>
<date>2020-12-01</date>
<amount>400</amount>
</payment>
<payment>
<id>456788</id>
<type>CANCELLED</type>
<date>2020-12-01</date>
<amount>500</amount>
</payment>
</parent>
Now, we want nodes where type = 'CANCELLED' and tricky condition is within these cancelled payments, if date is equal then we need to pick up node with highest id. SO, in above sample input, only 1st and 5th node should be selected. date for first node doesn't match with any other node, so it is selected. While date for 5th node matches with 3rd node but 5th is selected as it has higher id.
To achieve this, I tried to write below XSLT,
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:variable name="input" select="parent"/>
<xsl:template match="/">
<xsl:for-each select="parent/payment[type='CANCELLED']">
<xsl:choose>
<xsl:when test="date = $input/payment[type='CANCELLED']/date">
<xsl:message>
<xsl:value-of select="amount"/>
</xsl:message>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="amount"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
For-each is required as in actual example we are extracting some values, so we need to tweak for-each loop condition or write if condition inside it. I tried to declare variable input with whole input xml and compare node which is selected within for-each iteration with input node but then it matches with self as well. Am I going in right direction? Please guide.
Thanks in advance.
To identify the payments with the same date use grouping, then in each group select the item(s) with the highest/maximum id:
<xsl:for-each-group select="//payment[type = 'CANCELLED']" group-by="date">
<xsl:copy-of select="current-group()[id = max(current-group()/id)]"/>
</xsl:for-each-group>

how to match all elements and print them in the original order

I have an xml file i'm receiving and i want to format it using xsl.But the requirement is to
grab the elements and print them out in the order i recieve the xml file.
Looking at the sample below the first special element has got an order element and then the next one doesn't have one and the third one has one.
So i want the output exactly that way.
Thanks i nAdvance
<main>
<submain>
<detail>
<specials>
<spec-qty>1</spec-qty>
<spec-desc> Receivable </spec-desc>
</specials>
<order>
<text>Test</text>
</order>
<specials>
<spec-qty>-1</spec-qty>
<spec-desc>Receivable1 </spec-desc>
</specials>
<specials>
<spec-qty>-1</spec-qty>
<spec-desc> Receivable2 </spec-desc>
</specials>
<order>
<text>Test2</text>
</order>
</detail>
</submain></main>
Output should be:
qty 1 Receivable order:Test qty -1 Receivable1 qty -1 Receivable2 order: Test2
Thanks and sorry for the previous uncompleted code
You can use this template:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
<xsl:apply-templates select="//detail/*[self::specials or self::order]"/>
</xsl:template>
<xsl:template match="specials">
<xsl:value-of select="concat('qty ', spec-qty, ' ')"/>
<xsl:value-of select="spec-desc"/>
<xsl:text> </xsl:text>
</xsl:template>
<xsl:template match="order">
<xsl:value-of select="concat('order:', text, ' ')"/>
</xsl:template>
</xsl:stylesheet>
Output:
qty 1  Receivable  order:Test qty -1 Receivable1  qty -1  Receivable2  order:Test2

Conditional Sibling Value

I need to write a XSLT to get the value of the nearest dictionary value to a give node. For example, my structure could be as below
<rootnode>
<rootcontainer>
<dictionary>
<key1> value /<key1>
</dictionary>
<pages>
<page1>
<!--xslt goes here-->
</page1>
</pages>
</rootcontainer>
<dictionary>
<key1>
independent value
</key1>
<key2>
value 2
</key2>
</dictionary>
</rootnode>
I want to create variables $key1 and $key2 inside page1. The value of $key1 will be "value" and the value of $key2 will be "value 2". If rootcontainer\dictionary\key1 doesn't exist, the value of $key1 will be "independent value".
I hope this makes sense.
Here is a compact way to define the required variables:
<xsl:variable name="vKey1" select=
"(/*/rootcontainer/dictionary/key1
|
/*/dictionary/key1
)
[1]
"/>
<xsl:variable name="vKey2" select=
"(/*/rootcontainer/dictionary/key2
|
/*/dictionary/key2
)
[1]
"/>
When wrapped in a simple xslt stylesheet:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:variable name="vKey1" select=
"(/*/rootcontainer/dictionary/key1
|
/*/dictionary/key1
)
[1]
"/>
<xsl:variable name="vKey2" select=
"(/*/rootcontainer/dictionary/key2
|
/*/dictionary/key2
)
[1]
"/>
<xsl:template match="/">
Key1: <xsl:value-of select="$vKey1"/>
Key2: <xsl:value-of select="$vKey2"/>
</xsl:template>
</xsl:stylesheet>
and applied on the provided XML document (corrected, as it was severely malformed):
<rootnode>
<rootcontainer>
<dictionary>
<key1> value </key1>
</dictionary>
<pages>
<page1> </page1>
</pages>
</rootcontainer>
<dictionary>
<key1> independent value </key1>
<key2> value 2 </key2>
</dictionary>
</rootnode>
the wanted, correct result is produced:
Key1: value
Key2: value 2
Explanation:
The expression:
(/*/rootcontainer/dictionary/key1
|
/*/dictionary/key1
)
[1]
means:
Take the nodeset of (potentially) the two elements and from them take the first one in document order.
Because, the second of these two elements comes later in document order, it will be the first (and selected), only when the first of the two XPath expressions surrounding the union ( |) operator, doesn't select any element.
I'm not sure I understand the question but you can assign a conditional value to a variable in two ways:
<xsl:choose>
<xsl:when test="your test condition">
<xsl:variable name="key1" select="your value">
</xsl:when>
<xsl:otherwise>
<xsl:variable name="key1" select="your alternative value">
</xsl:otherwise>
</xsl:choose>
Or more succintly:
<xsl:variable name="key1" select="if(your test condition) then your value else your alternative value;"/>
Update: Thanks for the update of the question. I'll give it a go now.
<xsl:template match="page1">
<xsl:choose>
<xsl:when test="../../preceding-sibling:dictionary[1]/key1">
<xsl:variable name="key1" select="../../preceding-sibling:dictionary[1]/key1">
</xsl:when>
<xsl:otherwise>
<xsl:variable name="key1" select="../../../following-sibling:dictionary[1]/key1">
</xsl:otherwise>
</xsl:choose>
</xsl:template>
So the value of $key1 will be the <key1> node in the preceding dictionary if there is one, and the <key1> node in the following dictionary if there isn't. Is that correct?
(You can use the if/then/else structure too if you want to, but I used xsl:choose because it's probably easier to read.)

Xsl grouping duplicates problem

My XML looks like this
<page>
<orderhistory>
<order>
<ordernr>10610000000001</ordernr>
<orderrecord>
<productcategory>Something</productcategory>
</orderrecord>
<orderrecord>
<productcategory>Something</productcategory>
</orderrecord>
<orderrecord>
<productcategory>Something</productcategory>
</orderrecord>
</order>
<order>
<ordernr>10210000000001</ordernr>
<orderrecord>
<productcategory>Something</productcategory>
</orderrecord>
</order>
<order>
<ordernr>10110000000001</ordernr>
<orderrecord>
<productcategory>Something</productcategory>
</orderrecord>
</order>
<order>
<ordernr>10310000000001</ordernr>
<orderrecord>
<productcategory>Something</productcategory>
</orderrecord>
<orderrecord>
<productcategory>Forms</productcategory>
</orderrecord>
</order>
</orderhistory>
What i want is to show all productcategories that belongs to a single order. so if there are more orderrecords in one order I want to show all categories without duplicates
next the ordernr.
But it looked like this
03-06-2009 10610000000001 something something
03-06-2009 10210000000001 something
03-06-2009 10110000000001 something
03-05-2009 10310000000001 Forms something
I made a XSL to group the productcategories from the orderrecords, so that duplicates are removed. It seemed to work fine, but the problem is that i want it to be grouped by order.
<xsl:key name="orderrecord-by-productcategory" match="orderrecord" use="productcategory" />
<xsl:template match="/page/orderhistory/order">
<xsl:for-each select="orderrecord[generate-id(.) =generate-id(key('artists-by-country', productcategory)[1])]" >
<xsl:value-of select="productcategory" />
</xsl:for-each>
</xsl:template>
My output looks like this now
03-06-2009 10610000000001 something
03-06-2009 10210000000001
03-06-2009 10110000000001
03-05-2009 10310000000001 Forms
but i want it to look like this.
03-06-2009 10610000000001 something
03-06-2009 10210000000001 something
03-06-2009 10110000000001 something
03-05-2009 10310000000001 Forms something
How can i do this?
I think one way to do this is to use a key that is a concetation of the order number and the product category so that duplicate categories for different orders are treated differently
<xsl:key name="prodcat" match="productcategory" use="concat(../../ordernr/text(), concat('-', text()))"/>
This concatenates the order number, a hyphen (probably not needed), and the product category. Your XSL could then look something like this:
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" />
<xsl:key name="prodcat" match="productcategory" use="concat(../../ordernr/text(), concat('-', text()))"/>
<xsl:template match="/page/orderhistory">
<xsl:for-each select="order">
<xsl:value-of select="ordernr" />
<xsl:for-each select="orderrecord/productcategory[generate-id(.) = generate-id( key( 'prodcat', concat(../../ordernr/text(), concat('-', text())) )[1] )]">
<xsl:sort select="text()" />
<xsl:value-of select="." />
</xsl:for-each>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
There aren't any line-breaks or spaces in the output produced, but I hope it gives you the general idea.

How to use XPath/Xsl to compare child element with the same child element under a different parent?

I need to use xsl/xpath (version 1.0) to do something special (for simplifying, say insert some dummy text) when the value of SupplierId changes. I need to handle 3 variations;
Do something when on the first Order
(the first occurence of SupplierId)
Do somwthing when on OrderId O3 (SupplierId changed from S1 to S2)
Do something when on the last Order (the last occurence of SupplierId)
.
<?xml version="1.0" encoding="utf-8"?>
<Orders>
<Order>
<OrderId>O1</OrderId>
<SupplierId>S1</SupplierId>
</Order>
<Order>
<OrderId>O2</OrderId>
<SupplierId>S1</SupplierId>
</Order>
<Order>
<OrderId>O3</OrderId>
<SupplierId>S2</SupplierId>
</Order>
<Order>
<OrderId>O4</OrderId>
<SupplierId>S2</SupplierId>
</Order>
<Order>
<OrderId>O5</OrderId>
<SupplierId>S2</SupplierId>
</Order>
</Orders>
I've tried using preceding-sibling, following-sibling, etc, but haven't found out of it yet. I'd appreciate any help on this newbie question.
Wally
This is one natural and easy solution:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="Order[1]">
First OrderId = <xsl:text/>
<xsl:value-of select="OrderId"/>
</xsl:template>
<xsl:template match="Order[last()]">
Last OrderId = <xsl:text/>
<xsl:value-of select="OrderId"/>
</xsl:template>
<xsl:template match=
"Order[not(position() = 1)]
[not(SupplierId
=
preceding-sibling::Order[1]/SupplierId
)
]">
Changes in Order OrderId = <xsl:text/>
<xsl:value-of select="OrderId"/>
SupplierId = <xsl:text/>
<xsl:value-of select="SupplierId"/>
Previous Order OrderId = <xsl:text/>
<xsl:value-of select=
"preceding-sibling::Order[1]/OrderId"/>
SupplierId = <xsl:text/>
<xsl:value-of select=
"preceding-sibling::Order[1]/SupplierId"/>
</xsl:template>
<xsl:template match="text()"/>
</xsl:stylesheet>
When this transformation is applied on the provided XML document:
<Orders>
<Order>
<OrderId>O1</OrderId>
<SupplierId>S1</SupplierId>
</Order>
<Order>
<OrderId>O2</OrderId>
<SupplierId>S1</SupplierId>
</Order>
<Order>
<OrderId>O3</OrderId>
<SupplierId>S2</SupplierId>
</Order>
<Order>
<OrderId>O4</OrderId>
<SupplierId>S2</SupplierId>
</Order>
<Order>
<OrderId>O5</OrderId>
<SupplierId>S2</SupplierId>
</Order>
</Orders>
the desired result is produced:
First OrderId = O1
Changes in Order OrderId = O3
SupplierId = S2
Previous Order OrderId = O2
SupplierId = S1
Last OrderId = O5
Assuming a root element Orders, the XPath expressions matching each condition become:
a. first Order (the first occurence of SupplierId)
XPath 1.0 - /Orders/Order[SupplierId][1]
XPath 2.0 - /Orders/Order[exists(SupplierId)][1]
b. On OrderId O3 (SupplierId changed from S1 to S2)
/Orders/Order[OrderId = 'O3' and SupplierId = 'S2']
c. On the last Order (the last occurence of SupplierId)
/Orders/Order[SupplierId][last()]
You can use recursion to step down the list of orders one by one, comparing the previous value to the current value as you go (the solution implies that document order is correct already, because it uses the following-sibling axis):
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
<xsl:template match="/Orders">
<xsl:apply-templates select="Order[1]" mode="watch_SupplierId" />
</xsl:template>
<xsl:template match="Order" mode="watch_SupplierId">
<xsl:param name="PrevValue" select="''" />
<xsl:if test="string(SupplierId) != $PrevValue">
<xsl:call-template name="DoSomething" />
</xsl:if>
<xsl:variable name="next" select="following-sibling::Order[1]" />
<xsl:choose>
<xsl:when test="$next">
<xsl:apply-templates select="$next" mode="watch_SupplierId">
<xsl:with-param name="PrevValue" select="string(SupplierId)" />
</xsl:apply-templates>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="concat('last Order found: ', OrderId, '
')" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="DoSomething">
<xsl:value-of select="concat(
'#SupplierId changed to "'
, SupplierId/text()
, '" in Order '
, OrderId
, '
'
)" />
</xsl:template>
</xsl:stylesheet>
Outputs:
#SupplierId changed to "S1" in Order O1
#SupplierId changed to "S2" in Order O3
last Order found: O5