Return nodes of unique values based on 2 key parameters in XSLT1.0 - xslt-1.0

Have the following input
<LIN>
<REF>
<D_127>EQ</D_127>
</S_REF>
<L_SDP>
<S_FST>
<D_127>D0317D02</D_127>
</S_FST>
</L_SDP>
<L_SDP>
<S_FST>
<D_127>D0317D03</D_127>
</S_FST>
</L_SDP>
<L_SDP>
<S_FST>
<D_127>D0317D04</D_127>
</S_FST>
</L_SDP>
</L_LIN>
<L_LIN>
<S_REF>
<D_127>1A</D_127>
</S_REF>
<L_SDP>
<S_FST>
<D_127>D0317D02</D_127>
</S_FST>
</L_SDP>
</L_LIN>
<L_LIN>
<S_REF>
<D_127>1A</D_127>
</S_REF>
<L_SDP>
<S_FST>
<D_127>D0317D02</D_127>
</S_FST>
</L_SDP>
</L_LIN>
Would now like to have the following output
<DOC>
<ITEM>
<D_127>EQ</D_127>
<D_127>D0317D02</D_127>
</ITEM>
<ITEM>
<D_127>EQ</D_127>
<D_127>D0317D03</D_127>
</ITEM>
<ITEM>
<D_127>EQ</D_127>
<D_127>D0317D04</D_127>
</ITEM>
<ITEM>
<D_127>1A</D_127>
<D_127>D0317D02</D_127>
</ITEM>
</DOC>
i want unique based on ref value and fst value.like to have items with combination of unique REF(EQ or 1A ) with S_FST(D0317D02) values. I am using XSLT 1.0 .when i use generate id i am getting some missing values...tried many combinations but couldn't get it . used for each loop for ref and fst values but couldn't get the required no of lines with combination.

As with most (advanced) grouping tasks in XSLT 1, declare a key, here one that concatenates the two values, then process only the first item for each group established by the key:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output indent="yes"/>
<xsl:key name="group" match="S_FST/D_127" use="concat(., '|', ancestor::L_LIN/S_REF/D_127)"/>
<xsl:template match="ROOT">
<xsl:copy>
<xsl:apply-templates
select="//S_FST/D_127[generate-id() = generate-id(key('group', concat(., '|', ancestor::L_LIN/S_REF/D_127))[1])]"/>
</xsl:copy>
</xsl:template>
<xsl:template match="S_FST/D_127">
<ITEM>
<xsl:copy-of select="ancestor::L_LIN/S_REF/D_127"/>
<xsl:copy-of select="."/>
</ITEM>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/bEJbVrb

Related

How to get sum of elements by it's position. I have below XML and wanted to get sum of DonneeRensCompl by position using XSLT

I have below code and wanted to get sum of DonneeRensCompl by it's position. Example Position1 = 3.60 (1.30+ 2.30 = 3.60) likewise for all possible positions.
<DATA_DS>
<R>
<Annee>2021</Annee>
<CaseRensCompl>
<CodeRensCompl>235</CodeRensCompl>
<DonneeRensCompl>1.30</DonneeRensCompl>
</CaseRensCompl>
<CaseRensCompl>
<CodeRensCompl>B-1</CodeRensCompl>
<DonneeRensCompl>10650.00</DonneeRensCompl>
</CaseRensCompl>
<CaseRensCompl>
<CodeRensCompl>RZ-RJ</CodeRensCompl>
<DonneeRensCompl>10650.00</DonneeRensCompl>
</CaseRensCompl>
</R>
<R>
<Annee>2021</Annee>
<CaseRensCompl>
<CodeRensCompl>235</CodeRensCompl>
<DonneeRensCompl>2.30</DonneeRensCompl>
</CaseRensCompl>
<CaseRensCompl>
<CodeRensCompl>RZ-CA</CodeRensCompl>
<DonneeRensCompl>10650.00</DonneeRensCompl>
</CaseRensCompl>
</R>
</DATA_DS>
If you don't know in advance the number of positions, then you must increment the position in a loop until there are no nodes in the current position:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/DATA_DS">
<root>
<xsl:call-template name="sum-by-positions"/>
</root>
</xsl:template>
<xsl:template name="sum-by-positions">
<xsl:param name="i" select="1"/>
<xsl:variable name="cases" select="//CaseRensCompl[$i]" />
<xsl:if test="$cases">
<sum position="{$i}">
<xsl:value-of select="format-number(sum($cases/DonneeRensCompl), '#,##0.00')"/>
</sum>
<!-- recursive call -->
<xsl:call-template name="sum-by-positions">
<xsl:with-param name="i" select="$i + 1"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Applied to your input example, this will produce:
Result
<?xml version="1.0" encoding="UTF-8"?>
<root>
<sum position="1">3.60</sum>
<sum position="2">21,300.00</sum>
<sum position="3">10,650.00</sum>
</root>
To understand how this works, read this note in the XPath 1.0 specification:
NOTE: The location path //para[1] does not mean the same as the location path /descendant::para[1]. The latter selects the first descendant para element; the former selects all descendant para elements that are the first para children of their parents.

Remove both duplicate records using XSLT

I am trying to remove both the duplicate records in an XML
I already can remove the second occurrence but I need to remove both records in this case.
This is the XSLT mapping that I have
<?xml version="1.0" encoding="UTF-8"?>
<xsl:transform version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="item">
<xsl:copy-of select="." />
</xsl:template>
<xsl:template match="/ZTABLE/Record">
<ZTABLE>
<Record>
<xsl:apply-templates select="item[not(ID=preceding-sibling::item/ID)]" />
</Record>
</ZTABLE>
</xsl:template>
</xsl:transform>
The input XML is:
<ZTABLE>
<Record>
<item>
<ID>400400</ID>
</item>
<item>
<ID>100100</ID>
</item>
<item>
<ID>200200</ID>
</item>
<item>
<ID>300300</ID>
</item>
<item>
<ID>400400</ID>
</item>
</Record>
</ZTABLE>
The expected output is
<ZTABLE>
<Record>
<item>
<ID>100100</ID>
</item>
<item>
<ID>200200</ID>
</item>
<item>
<ID>300300</ID>
</Record>
</ZTABLE>
Try it this way:
<xsl:stylesheet version="1.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:key name="item" match="item" use="ID" />
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="item[count(key('item', ID)) > 1]"/>
</xsl:stylesheet>
For explanation, read about Muenchian grouping.

XSLT 1.0 grouping on one or multiple levels

Today's challenge was grouping in XSLT 1.0. Found out there are something called keys and the Muenchian grouping.
Input XML:
<Items>
<Item>
<ID>1</ID>
<Name>A</Name>
<Country>Sweden</Country>
<Region>Småland</Region>
</Item>
<Item>
<ID>2</ID>
<Name>B</Name>
<Country>Sweden</Country>
<Region>Norrland</Region>
</Item>
<Item>
<ID>3</ID>
<Name>C</Name>
<Country>USA</Country>
<Region>Alaska</Region>
</Item>
<Item>
<ID>4</ID>
<Name>D</Name>
<Country>USA</Country>
<Region>Texas</Region>
</Item>
<Item>
<ID>5</ID>
<Name>E</Name>
<Country>Sweden</Country>
<Region>Norrland</Region>
</Item>
</Items>
I need to make thins XML into a better structure, and from this sample XML I't like to get items structured by country and region. Below is wanted result where country and region gets sorted as well:
<Items>
<Country Name="Sweden">
<Region Name="Norrland">
<Item>
<ID>2</ID>
<Name>B</Name>
</Item>
<Item>
<ID>5</ID>
<Name>E</Name>
</Item>
</Region>
<Region Name="Småland">
<Item>
<ID>1</ID>
<Name>A</Name>
</Item>
</Region>
</Country>
<Country Name="USA">
<Region Name="Alaska">
<Item>
<ID>3</ID>
<Name>C</Name>
</Item>
</Region>
<Region Name="Texas">
<Item>
<ID>4</ID>
<Name>D</Name>
</Item>
</Region>
</Country>
</Items>
EDIT:
I also want to make sure regions end up in their own country, even if there are duplicates. I edited the answer accordingly.
Also, I'd like to hint about xsltfiddle.liberty-development.net as an easy way of doing trial-and-error XSLT development...
Inspired by this article, I found a neat solution to this problem:
I have included comments for using it for single or double grouping, see comments in the code. Notice how I use first key (index) as input to the secon for-each loop:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="country" match="Item" use="Country" />
<xsl:key name="region" match="Item" use="concat(Region, '|', Country)" />
<xsl:template match="/Items">
<Items>
<xsl:for-each select="Item[generate-id(.) = generate-id(key('country', Country))]">
<xsl:sort select="Country" />
<xsl:variable name="_country" select="Country" />
<xsl:element name="Country">
<xsl:attribute name="Name"><xsl:value-of select="$_country" /></xsl:attribute>
<!-- single level grouping -->
<!--<xsl:apply-templates select="key('country', Country)" />-->
<!-- double grouping -->
<!-- START -->
<xsl:for-each select="key('country', Country)[generate-id(.) = generate-id(key('region', concat(Region, '|', Country)))]">
<xsl:sort select="Region" />
<xsl:variable name="_region" select="Region" />
<xsl:element name="Region">
<xsl:attribute name="Name"><xsl:value-of select="$_region" /></xsl:attribute>
<xsl:apply-templates select="key('region', concat(Region, '|', Country))" />
</xsl:element>
</xsl:for-each>
<!-- END -->
</xsl:element>
</xsl:for-each>
</Items>
</xsl:template>
<xsl:template match="Item">
<xsl:element name="Item">
<xsl:element name="ID"><xsl:value-of select="ID" /></xsl:element>
<xsl:element name="Name"><xsl:value-of select="Name" /></xsl:element>
</xsl:element>
</xsl:template>
</xsl:stylesheet>

XSLT sort data in variable and save them to another variable

I have a problem. I have the following XML
<countryList>
<country>
<name>Afghanistan</name>
<population>29117000</population>
<area>654329</area>
</country>
<country>
<name>Albania</name>
<population>3195000</population>
<area>28748</area>
</country>
<country>
<name>Algeria</name>
<population>35423000</population>
<area>2381741</area>
</country>
<country>
<name>Andorra</name>
<population>84082</population>
<area>468</area>
</country>
</countryList>
I have one question. All I need to do is divide population/area and sort these divisions for each country. However, I tried this
<xsl:variable name="Podiel">
<xsl:value-of select="../population div ../area"/>
</xsl:variable>
<xsl:variable name="PodielPodiel">
<xsl:for-each select="$Podiel">
<xsl:sort select="." data-type="number" order="descending"/>
</xsl:for-each>
</xsl:variable>
but I still get error
The 'select' expression does not evaluate to a node set.
no result for data1.xml
Any help? I just want to know the maximum of all divisions.
Thanks.
Not sure whether the issue has been resolved, however for using node-set in XSLT 1.0, extension functions have to be used. Please refer to node-set extension for additional details on it's usage in XSLT 1.0.
If at all the country list in the input XML needs to be sorted on the value of population div area, the number() function can be used within <xsl:sort>. Declaring variables and calculating the additional values and then accessing the variable is not required.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >
<xsl:output method="xml" />
<xsl:strip-space elements="*" />
<!-- identity transform -->
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="countryList">
<xsl:copy>
<xsl:apply-templates select="country">
<xsl:sort select="number(population div area)" data-type="number" order="descending" />
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Output
<countryList>
<country>
<name>Andorra</name>
<population>84082</population>
<area>468</area>
</country>
<country>
<name>Albania</name>
<population>3195000</population>
<area>28748</area>
</country>
<country>
<name>Afghanistan</name>
<population>29117000</population>
<area>654329</area>
</country>
<country>
<name>Algeria</name>
<population>35423000</population>
<area>2381741</area>
</country>
</countryList>

XSLT: Convert Name/Value pair and transform an XML

I need to convert a name value pair into XML. I'm able to generate an XML, but the element name should be grouped and it should not be duplicated. Please see below. The FieldValue element contains 2 OrderItem values in the Detail node. If the FieldValue with OrderItem repeats, then the result should be grouped into one OrderItem node. Please help.
Source XML:
<SC>
<Header>
<Record>
<FieldName>Schema</FieldName>
<FieldValue>OrderHeader</FieldValue>
</Record>
<Record>
<FieldName>Order</FieldName>
<FieldValue>1234</FieldValue>
</Record>
</Header>
<Detail>
<Record>
<FieldName>Schema</FieldName>
<FieldValue>OrderItem</FieldValue>
</Record>
<Record>
<FieldName>Item</FieldName>
<FieldValue>1</FieldValue>
</Record>
<Record>
<FieldName>Qty</FieldName>
<FieldValue>10</FieldValue>
</Record>
</Detail>
<Detail>
<Record>
<FieldName>Schema</FieldName>
<FieldValue>OrderItem</FieldValue>
</Record>
<Record>
<FieldName>Item</FieldName>
<FieldValue>2</FieldValue>
</Record>
<Record>
<FieldName>Qty</FieldName>
<FieldValue>20</FieldValue>
</Record>
</Detail>
</SC>
Target XML:
<Order>
<OrderItem>
<Item>
<Item>1</Item>
<Qty>10</Qty>
</Item>
<Item>
<Item>2</Item>
<Qty>20</Qty>
</Item>
</OrderItem>
</Order>
XSLT:
<xsl:template match="#*|node()">
<Order>
<xsl:for-each select="Detail">
<Item>
<xsl:apply-templates select="Record[position()>1]"/>
</Item>
</xsl:for-each>
</Order>
</xsl:template>
<xsl:template match="Record">
<xsl:element name="{FieldName}">
<xsl:value-of select="FieldValue"/>
</xsl:element>
</xsl:template>
The grouping can be done as follows:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
version="2.0">
<xsl:output indent="yes"/>
<xsl:template match="SC">
<Order>
<xsl:for-each-group select="Detail" group-by="Record[1]/FieldValue">
<xsl:element name="{current-grouping-key()}">
<xsl:apply-templates select="current-group()"/>
</xsl:element>
</xsl:for-each-group>
</Order>
</xsl:template>
<xsl:template match="Detail">
<Item>
<xsl:apply-templates select="Record[position() gt 1]"/>
</Item>
</xsl:template>
<xsl:template match="Record">
<xsl:element name="{FieldName}">
<xsl:value-of select="FieldValue"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
It appears that you are trying to define two XSLT templates, when one should be sufficient. You want to match on the root and then that you want to iterate over each SC/Detail.
Then, you want to take the FieldValue of the sibling of the FieldName node that is 'Item' (for item value) and 'Qty' (for quantity value), but only those listed under 'Record'.
Note: You have specified a doubly-nested <Item> in your transformed output and this solution reflects that requirement.
This XSLT should do what you are requesting:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="/">
<xsl:for-each select="SC/Detail">
<Order>
<OrderItem>
<Item>
<Item>
<xsl:value-of select="Record[FieldName[text()='Item']]/FieldValue" />
</Item>
<Qty>
<xsl:value-of select="Record[FieldName[text()='Qty']]/FieldValue" />
</Qty>
</Item>
</OrderItem>
</Order>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>