XSLT group every nth item in to new group - xslt

I have done few XSLT in the past, but I am facing challenge in this.
I am working with PLC tag, for each tag i am getting three rowset node, so after every three Rowset i need to create new "Row" group.
Updated with XSLT
Input XML:
<?xml version="1.0" encoding="UTF-8"?>
<Rowsets >
<Rowset>
<Row>
<DateTime>2021-07-05T07:33:38</DateTime>
<WC_ID>0001</WC_ID>
</Row>
</Rowset>
<Rowset>
<Row>
<DateTime>2021-07-05T07:33:38</DateTime>
<Tag1_Good>6817</Tag1_Good>
</Row>
</Rowset>
<Rowset>
<Row>
<DateTime>2021-07-05T07:33:38</DateTime>
<Tag1_Bad>0</Tag1_Bad>
</Row>
</Rowset>
<Rowset>
<Row>
<DateTime>2021-07-05T07:33:38</DateTime>
<WC_ID>0002</WC_ID>
</Row>
</Rowset>
<Rowset>
<Row>
<DateTime>2021-07-05T07:33:38</DateTime>
<Tag2_Good>6800</Tag2_Good>
</Row>
</Rowset>
<Rowset>
<Row>
<DateTime>2021-07-05T07:33:38</DateTime>
<Tag2_Bad>0</Tag2_Bad>
</Row>
</Rowset>
</Rowsets>
Expected output:
<?xml version="1.0" encoding="UTF-8"?>
<Rowset>
<Row>
<WC_ID>0001</WC_ID>
<Tag1_Good>6817</Tag1_Good>
<Tag1_Bad>0</Tag1_Bad>
</Row>
<Row>
<WC_ID>0002</WC_ID>
<Tag1_Good>6800</Tag1_Good>
<Tag1_Bad>0</Tag1_Bad>
</Row>
</Rowset>
My XSLT:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<Rowsets >
<xsl:variable name="batchSize" select="3"/>
<Rowset>
<xsl:for-each select="/Rowsets/Rowset[position() mod $batchSize >= 0]"
<Row>
<xsl:value-of select="Row/*[2]" />
</Row>
</xsl:for-each>
</Rowset>
</Rowsets>
</xsl:template>
</xsl:stylesheet>
I am not able to make this into a new group

Use this:
<Rowsets>
<xsl:variable name="batchSize" select="3"/>
<Rowset>
<xsl:for-each select="/Rowsets/Rowset">
<xsl:variable name="pos" select="position()"/>
<xsl:if test="$pos mod $batchSize = 0">
<Row>
<WC_ID>
<xsl:value-of select="/Rowsets/Rowset[$pos - 2]/Row/*[2]"/>
</WC_ID>
<Tag1_Good>
<xsl:value-of select="/Rowsets/Rowset[$pos - 1]/Row/*[2]"/>
</Tag1_Good>
<Tag1_Bad>
<xsl:value-of select="/Rowsets/Rowset[$pos]/Row/*[2]"/>
</Tag1_Bad>
</Row>
</xsl:if>
</xsl:for-each>
</Rowset>
</Rowsets>

Related

Group XML elements with comma seperated values with XSLT program

We are new to xslt programming, can you please help us with xslt program.
We need to group xml elements based on "id" tag and concatenate the other xml tag with comma.
input xml file:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<row>
<id>123</id>
<functional_manager__c.users>1234567</functional_manager__c.users>
</row>
<row>
<id>123</id>
<functional_manager__c.users>1200000</functional_manager__c.users>
</row>
<row>
<id>111</id>
<functional_manager__c.users>11111111</functional_manager__c.users>
</row>
<row>
<id>111</id>
<functional_manager__c.users>2222222</functional_manager__c.users>
</row>
<row>
<id>123</id>
<editor__v.users>1234567</editor__v.users>
</row>
<row>
<id>123</id>
<editor__v.users>1200000</editor__v.users>
</row>
<row>
<id>111</id>
<learning_partner__c.users>11111111</learning_partner__c.users>
</row>
<row>
<id>111</id>
<learning_partner__c.users>2222222</learning_partner__c.users>
</row>
</root>
Required Output:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<row>
<id>123</id>
<functional_manager__c.users>1234567,1200000</functional_manager__c.users>
</row>
<row>
<id>111</id>
<functional_manager__c.users>11111111,2222222</functional_manager__c.users>
</row>
<row>
<id>123</id>
<editor__v.users>1234567,1200000</editor__v.users>
</row>
<row>
<id>111</id>
<learning_partner__c.users>11111111,2222222</learning_partner__c.users>
</row>
</root>
code we tried:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="2.0" exclude-result-prefixes="xsl wd xsd this env"
xmlns:wd="urn:com.workday/bsvc"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:this="urn:this-stylesheet">
<xsl:output indent="yes" method="xml"/>
<xsl:template match="/">
<Sharingsettings>
<xsl:for-each-group select="/root/row" group-by="id">
<row>
<ID>
<xsl:value-of select="id"/>
</ID>
<functional_manager__c.users>
<xsl:value-of select="//current-group()//functional_manager__c.users">
</xsl:value-of>
</functional_manager__c.users>
</row>
</xsl:for-each-group>
</Sharingsettings>
</xsl:template>
</xsl:stylesheet>
we are trying with XSLT program but it is not giving required output properly.
Thank you so much in advance
With XSLT 3 you can use
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="3.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all"
expand-text="yes">
<xsl:output method="xml" indent="yes"/>
<xsl:mode on-no-match="shallow-copy"/>
<xsl:template match="root">
<xsl:copy>
<xsl:for-each-group select="row" group-adjacent="id">
<xsl:copy>
<xsl:apply-templates/>
</xsl:copy>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
<xsl:template match="row/*[not(self::id)]">
<xsl:copy>
<xsl:value-of select="current-group()/*[node-name() = node-name(current())]" separator=","/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

Removing comma from number format with unique XSLT coding

With the example below, the XSLT is doing a few things, it is grouping by column 1 and column2, if it is the same, then it will group the column 3 amount. As well, within the PayAmount tags, it is reversing negative (-) number into a positive and vice versa. What I am having troubles with is writing logic to remove the comma (,) from column 3 in the output.
Below is my XSLT Code
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" />
<xsl:template match="data">
<xsl:for-each select="row[generate-id(.) = generate-id(key(''rows'', concat(column1, ''||'', column2)))]">
</Record>
<Detail>
<Amount>
<xsl:variable name="mySum">
<xsl:value-of select="sum(key(''rows'', concat(column1, ''||'', column2))/column3)" />
</xsl:variable>
<xsl:value-of select="translate(($mySum * ($mySum >= 0) - $mySum * not($mySum >= 0)),'','','''')" />
</Amount>
</Detail>
</Record>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
I have tried the following logic, but the comma remains in the output:
- <xsl:value-of select="translate(($mySum * ($mySum >= 0) - $mySum * not($mySum >= 0)),'','', '''')" />
- <xsl:value-of select="format-number($mySum * ($mySum >= 0) - $mySum * not($mySum >= 0),'#,##0.00')" />
- <xsl:value-of select="translate(sum(key(''rows'', concat(column1, ''||'', column2))/column3),'','','''')" />
Below is a sample data in XML:
<data>
<row>
<column1>200040</column1>
<column2>Auto</column2>
<column3>-500.00</column3>
</row>
<row>
<column1>200040</column1>
<column2>Auto</column2>
<column3>-5,000.00</column3>
</row>
<row>
<column1>200040</column1>
<column2>Auto</column2>
<column3>-1,000.00</column3>
</row>
<row>
<column1>200040</column1>
<column2>Auto</column2>
<column3>300.00</column3>
</row>
<row>
<column1>200040</column1>
<column2>Auto</column2>
<column3>-4,000.00</column3>
</row>
<row>
<column1>200041</column1>
<column2>Bike</column2>
<column3>-1,700.00</column3>
</row>
<row>
<column1>200041</column1>
<column2>Bike</column2>
<column3>-1,000.00</column3>
</row>
<row>
<column1>200041</column1>
<column2>Bike</column2>
<column3>800.00</column3>
</row>
<row>
<column1>200045</column1>
<column2>Bus</column2>
<column3>200.00</column3>
</row>
<row>
<column1>200045</column1>
<column2>Bus</column2>
<column3>-10,000.00</column3>
</row>
<row>
<column1>200045</column1>
<column2>Bus</column2>
<column3>5,000.00</column3>
</row>
</data>
The output I would like to achieve after running the XSLT is
200040 | Auto | 10200.00
200041 | Bike | 1900.00
200045 | Bus | 4800.00
Any help would be greatly appreciated!
A value that contains a comma is not a number and cannot be summed. You need to remove the commas before you attempt to sum the values.
Here's a simplified example:
XSLT 1.0 + EXSLT node-set()
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
extension-element-prefixes="exsl">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" />
<xsl:key name="a" match="amount" use="#key"/>
<xsl:template match="data">
<!-- first pass: convert amounts to numbers -->
<xsl:variable name="amounts">
<xsl:for-each select="row">
<amount key="{concat(column1, '|', column2)}">
<xsl:value-of select="translate(column3, ',', '')"/>
</amount>
</xsl:for-each>
</xsl:variable>
<!-- output -->
<Output>
<xsl:for-each select="exsl:node-set($amounts)/amount[generate-id() = generate-id(key('a', #key))]">
<Record>
<Detail1>
<xsl:value-of select="substring-before(#key, '|')"/>
</Detail1>
<Detail2>
<xsl:value-of select="substring-after(#key, '|')"/>
</Detail2>
<xsl:variable name="sum" select="sum(key('a', #key))"/>
<Amount>
<xsl:value-of select="format-number($sum, '0.00')"/>
</Amount>
</Record>
</xsl:for-each>
</Output>
</xsl:template>
</xsl:stylesheet>
Applied to your input example, this will produce:
Result
<?xml version="1.0" encoding="UTF-8"?>
<Output>
<Record>
<Detail1>200040</Detail1>
<Detail2>Auto</Detail2>
<Amount>-10200.00</Amount>
</Record>
<Record>
<Detail1>200041</Detail1>
<Detail2>Bike</Detail2>
<Amount>-1900.00</Amount>
</Record>
<Record>
<Detail1>200045</Detail1>
<Detail2>Bus</Detail2>
<Amount>-4800.00</Amount>
</Record>
</Output>
To reverse negative amounts to positive (and vice versa), you could simply change:
<xsl:value-of select="format-number($sum, '0.00')"/>
to:
<xsl:value-of select="format-number(-$sum, '0.00')"/>

How to group xml elements that is in sequence, using XSLT?

There are examples to group items using xsl:key, but those don't work for my scenario.
Each set with column1="H" should be named <transaction>, and all the items with column1="D" following the "H" should be inside the <transaction> as <item>, until it reaches the next "H". Then it repeats with the same rule.
Problem: The values are enclosed in double quotes, but the output shouldn't have double quotes.
<root>
<row>
<column1>"H"</column1>
<column2>"2016-09-09"</column2>
</row>
<row>
<column1>"D"</column1>
<column2>"Conference Services Meeting Package"</column2>
</row>
<row>
<column1>"D"</column1>
<column2>"Audio Visual Meeting Package"</column2>
</row>
<row>
<column1>"H"</column1>
<column2>"2016-09-09"</column2>
</row>
<row>
<column1>"D"</column1>
<column2>"Meeting Package Lunch"</column2>
</row>
<row>
<column1>"D"</column1>
<column2>"Marinated Roasted Olives</column2>
</row>
<row>
<column1>"D"</column1>
<column2>"Mezza Plate Humus with Smoked Paprika Butter"</column2>
</row>
<row>
<column1>"D"</column1>
<column2>"Pastry Bread Block Loaf Bread"</column2>
</row>
</root>
Output:
<xml>
<transaction>
<item>Conference Services Meeting Package</item>
<item>Audio Visual Meeting Package</item>
</transaction>
<transaction>
<item>Meeting Package Lunch</item>
<item>Marinated Roasted Olives</item>
<item>Mezza Plate Humus with Smoked Paprika Butter</item>
<item>Pastry Bread Block Loaf Bread</item>
</transaction>
</xml>
This transformation:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:key name="kFollowing" match='row[column1=&apos;"D"&apos;]'
use='generate-id(preceding-sibling::row[column1=&apos;"H"&apos;][1])'/>
<xsl:template match="/*">
<xml>
<xsl:apply-templates select='row[column1=&apos;"H"&apos;]'/>
</xml>
</xsl:template>
<xsl:template match="row">
<transaction>
<xsl:apply-templates select="key('kFollowing', generate-id())/column2"/>
</transaction>
</xsl:template>
<xsl:template match="column2">
<item><xsl:value-of select=
'concat(translate(substring(.,1,1),&apos;"&apos;,""),
substring(.,2, string-length(.) -2),
translate(substring(.,string-length()),&apos;"&apos;,""))'/></item>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document:
<root>
<row>
<column1>"H"</column1>
<column2>"2016-09-09"</column2>
</row>
<row>
<column1>"D"</column1>
<column2>"Conference Services Meeting Package"</column2>
</row>
<row>
<column1>"D"</column1>
<column2>"Audio Visual Meeting Package"</column2>
</row>
<row>
<column1>"H"</column1>
<column2>"2016-09-09"</column2>
</row>
<row>
<column1>"D"</column1>
<column2>"Meeting Package Lunch"</column2>
</row>
<row>
<column1>"D"</column1>
<column2>"Marinated Roasted Olives</column2>
</row>
<row>
<column1>"D"</column1>
<column2>"Mezza Plate Humus with Smoked Paprika Butter"</column2>
</row>
<row>
<column1>"D"</column1>
<column2>"Pastry Bread Block Loaf Bread"</column2>
</row>
</root>
produces exactly the wanted, correct result:
<xml>
<transaction>
<item>Conference Services Meeting Package</item>
<item>Audio Visual Meeting Package</item>
</transaction>
<transaction>
<item>Meeting Package Lunch</item>
<item>Marinated Roasted Olives</item>
<item>Mezza Plate Humus with Smoked Paprika Butter</item>
<item>Pastry Bread Block Loaf Bread</item>
</transaction>
</xml>
Explanation:
A key that defines all corresponding "D" <column2> elements as a function of the generate-id() of their corresponding (nearest preceding) "H" <column1> element.
Translating the 1st and last characters from (only if they are a quote) " to the empty string. Thus "Marinated Roasted Olives is processed correctly, even though it doesn't end with a quote -- which most probably is accidental mistake.
No <xsl:for-each> (even nested!) instruction.
Try it this way:
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:key name="tx" match="row[column1='"D"']" use="generate-id(preceding-sibling::row[column1='"H"'][1])" />
<xsl:template match="/root">
<xml>
<xsl:for-each select="row[column1='"H"']">
<transaction>
<xsl:for-each select="key('tx', generate-id())">
<item>
<xsl:value-of select="column2"/>
</item>
</xsl:for-each>
</transaction>
</xsl:for-each>
</xml>
</xsl:template>
</xsl:stylesheet>
This solves the problem of grouping adjacent items. The problem of removing the double quotes is rather trivial, and can be solved easily by using the substring() function. Post a separate question if you can't make it work.

use xlst to count intermediary elements

How can I count intermediary elements? I think the solution to this is related to this question. Supposing I had something like this:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<row>
<cell>Title</cell>
</row>
<row>
<cell padding='true'>Chapter</cell>
<cell>example additional cell</cell>
</row>
<row>
<cell padding='true'>Chapter</cell>
</row>
<row>
<cell padding='true'>Chapter</cell>
</row>
<row>
<cell>Title</cell>
</row>
<row>
<cell padding='true'>Chapter</cell>
</row>
<row>
<cell>Title</cell>
</row>
<row>
<cell padding='true'>Chapter</cell>
</row>
<row>
<cell padding='true'>Chapter</cell>
</row>
<row>
<cell>Title</cell>
</row>
<row>
<cell padding='true'>Chapter</cell>
</row>
</root>
And, applying a transformation like this:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<xsl:template match="/root">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="row">
<xsl:choose>
<!--these are the indented ones-->
<xsl:when test="cell/#padding">
<xsl:number
value="count(preceding-sibling::row[child::cell[1][#padding]]) + 1"
format="a. "/>
</xsl:when>
<xsl:otherwise>
<xsl:number
value="count(preceding-sibling::row[child::cell[1][not(#padding)]]) + 1"
format="1. "/>
</xsl:otherwise>
</xsl:choose>
<xsl:value-of select="cell/text()"/>
</xsl:template>
</xsl:stylesheet>
I'm getting this result:
1. Title
a. Chapter
b. Chapter
c. Chapter
2. Title
d. Chapter
3. Title
e. Chapter
f. Chapter
4. Title
g. Chapter
What I would like is to have the "sub" items restart their numbering. I just can't figure how change the axis to stop looking back at earlier elements.
I was looking to have the "chapters" counted. So:
1. Title
a. Chapter
b. Chapter
c. Chapter
2. Title
a. Chapter
3. Title
a. Chapter
b. Chapter
4. Title
a. Chapter
I would suggest you try it this way:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" encoding="UTF-8" />
<xsl:strip-space elements="*"/>
<xsl:template match="/root">
<xsl:apply-templates select="row[not(cell/#padding)] | row/cell[#padding]"/>
</xsl:template>
<xsl:template match="row">
<xsl:number count="row[not(cell/#padding)]" format="1. "/>
<xsl:value-of select="cell"/>
<xsl:text>
</xsl:text>
</xsl:template>
<xsl:template match="cell">
<xsl:number count="cell[#padding]" level="any" from="row[not(cell/#padding)]" format=" a. "/>
<xsl:value-of select="."/>
<xsl:text>
</xsl:text>
</xsl:template>
</xsl:stylesheet>

XSLT 1.0 - Remove Duplicate Nodes From Variable

I've seen many posts on this, but none of them have helped me figure out my problem.
Test1.xml
<table>
<row>
<col1>A</col1>
</row>
<row>
<col1>B</col1>
</row>
<row>
<col1>C</col1>
</row>
</table>
Test2.xml
<table>
<row>
<col1>A</col1>
<col2>ABC</col2>
</row>
<row>
<col1>B</col1>
<col2>ABC</col2>
</row>
<row>
<col1>A</col1>
<col2>ABC</col2>
</row>
<row>
<col1>C</col1>
<col2>ABC</col2>
</row>
<row>
<col1>A</col1>
<col2>DEF</col2>
</row>
</table>
Test.xsl (XSLT 1.0)
<xsl:variable name="input" select="document('test1.xml')/>
<xsl:template match="/">
<xsl:apply-templates select="$input" mode="special"/>
</xsl:template>
<xsl:template match="node()|#" mode="special">
<xsl:copy>
<xsl:apply-templates select="node()|#" mode="special"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Row" mode="special">
<xsl:variable name="cols" select="document('test2.xml')/Table/Row[current()/Col1 = Col1]/Col2"/>
<xsl:variable name="unique_cols" select="$cols[not(. = preceding-sibling::*)]"/>
<!-- Debug -->
<xsl:for-each select="$unique_cols">
<xsl:copy-of select="."/>
</xsl:for-each>
----
</xsl:template>
Expected Output:
<col2>ABC</col2>
<col2>DEF</col2>
----
<col2>ABC</col2>
----
<col2>ABC</col2>
Current Output:
<col2>ABC</col2>
<col2>ABC</col2>
<col2>DEF</col2>
----
<col2>ABC</col2>
----
<col2>ABC</col2>
The col2 values in $unique_cols should be distinct per col1 values. If unique col2 values could be selected in $cols, even better.
Just replace:
<xsl:variable name="unique_cols" select="$cols[not(. = preceding-sibling::*)]"/>
with:
<xsl:variable name="unique_cols" select=
"$cols[not(../col1 = ../preceding-sibling::*/col1)]"/>
The full transformation (initially corrrected to fix a multitude of lexical errors) now becomes (also using my own file-Uris):
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:variable name="input" select=
"document('file:///c:/temp/delete/test1.xml')"/>
<xsl:template match="/">
<xsl:apply-templates select="$input" mode="special"/>
</xsl:template>
<xsl:template match="node()|#*" mode="special">
<xsl:copy>
<xsl:apply-templates select="node()|#*" mode="special"/>
</xsl:copy>
</xsl:template>
<xsl:template match="row" mode="special">
<xsl:variable name="cols" select=
"document('file:///c:/temp/delete/test2.xml')
/table
/row[current()/col1 = col1]
/col2"/>
<xsl:variable name="unique_cols" select=
"$cols[not(../col1 = ../preceding-sibling::*/col1)]"/>
<!-- Debug -->
<xsl:for-each select="$unique_cols">
<xsl:copy-of select="."/>
</xsl:for-each>
----
</xsl:template>
</xsl:stylesheet>
and the files are:
c:/temp/delete/test1.xml:
<table>
<row>
<col1>A</col1>
</row>
<row>
<col1>B</col1>
</row>
<row>
<col1>C</col1>
</row>
</table>
and: c:/temp/delete/test2.xml:
<table>
<row>
<col1>A</col1>
<col2>ABC</col2>
</row>
<row>
<col1>B</col1>
<col2>ABC</col2>
</row>
<row>
<col1>A</col1>
<col2>ABC</col2>
</row>
<row>
<col1>C</col1>
<col2>ABC</col2>
</row>
</table>
When the transformation is performed on any XML document (not used), the wanted, correct result is produced:
<table>
<col2>ABC</col2>
----
<col2>ABC</col2>
----
<col2>ABC</col2>
----
</table>
I think I may have figured this out based on an answer provided on another post (https://groups.google.com/forum/?fromgroups#!topic/microsoft.public.xsl/i8FwJUD0r8U).
<xsl:variable name="cols" select=
"document('test2.xml')/table/row[current()/col1 = col1]/col2[not(. = ../preceding-sibling::*/col2[current()/col1 = ../col1])]"/>
This appears to select unique col2 values into $cols eliminating the need for the second variable.