How to group xml elements that is in sequence, using XSLT? - 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.

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>

XSLT group every nth item in to new group

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>

XSLT: How to delete all nodes except ones having a child with certain value?

I am trying to delete from an XML file, all the nodes that not satisfy certain condition.
This is my simplified input:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<unit xmlns="http://www.srcML.org/srcML/src">
<Rowsets>
<Rowset>
<Row>
<FirstName>Michael</FirstName>
<LastName>David</LastName>
<Phone>1234567890</Phone>
</Row>
<Row>
<FirstName>David</FirstName>
<LastName>Michael</LastName>
<Phone>01234567890</Phone>
</Row>
<Row>
<FirstName>Yang</FirstName>
<LastName>Christina</LastName>
<Phone>2345678901</Phone>
</Row>
<Row>
<FirstName>Grey</FirstName>
<LastName>Meredith</LastName>
<Phone>3456789012</Phone>
</Row>
<Row>
<FirstName>Michael</FirstName>
<LastName>Shepherd</LastName>
<Phone>5678901234</Phone>
</Row>
</Rowset>
</Rowsets>
<Tag>
<FirstName>Michael</FirstName>
<LastName>Shepherd</LastName>
<Phone>5678901234</Phone>
</Tag>
</unit>
I would like to write an XSLT transformation file that will be able to delete all the nodes that don't have a child of type FirstName with value Michael.
For the input I have just provided, I would like to obtain this output:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<unit xmlns="http://www.srcML.org/srcML/src">
<Row>
<FirstName>Michael</FirstName>
<LastName>David</LastName>
<Phone>1234567890</Phone>
</Row>
<Row>
<FirstName>Michael</FirstName>
<LastName>Shepherd</LastName>
<Phone>5678901234</Phone>
</Row>
<Tag>
<FirstName>Michael</FirstName>
<LastName>Shepherd</LastName>
<Phone>5678901234</Phone>
</Tag>
</unit>
This is the transformation file I have written up now:
<xsl:stylesheet
xmlns="http://www.srcML.org/srcML/src"
xmlns:src="http://www.srcML.org/srcML/src"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:template name="removingNotMichael" match="*[child::src:FirstName[.= 'Michael']]">
<xsl:copy><xsl:apply-templates select="#*|node()"/></xsl:copy>
</xsl:template>
<xsl:template match="#*|node()">
</xsl:template>
</xsl:stylesheet>
It does not produce anything, just a completely empty file.
Could anybody please help me with this issue?
You can Try this:
<xsl:template match="/">
<unit>
<xsl:copy-of select="//*[src:FirstName = 'Michael']"/>
</unit>
</xsl:template>

Grouping elements in XSLT 1.0 and exclude some elements in this grouping

I have the following input XML:
<?xml version="1.0" encoding="UTF-8"?>
<data>
<row>
<code>EXCLUDE</code>
<value>VALUE1</value>
</row>
<row>
<code>EXCLUDEALSO</code>
<value>VALUE2</value>
</row>
<row>
<code>NUMBER</code>
<value>001</value>
</row>
<row>
<code>FROM</code>
<value>NAMELINE_FROM</value>
</row>
<row>
<code>TO</code>
<value>NAMELINE_TO</value>
</row>
<row>
<code>NUMBER</code>
<value>002</value>
</row>
<row>
<code>TO</code>
<value>NAMELINE_TO</value>
</row>
<row>
<code>NUMBER</code>
<value>003</value>
</row>
<row>
<code>FROM</code>
<value>NAMELINE_FROM</value>
</row>
<row>
<code>NUMBER</code>
<value>004</value>
</row>
<row>
<code>TO</code>
<value>NAMELINE</value>
</row>
<row>
<code>FROM</code>
<value>NAMELINE</value>
</row>
<row>
<code>EXCLUDE</code>
<value>VALUE1</value>
</row>
<row>
<code>EXCLUDEALSO</code>
<value>VALUE2</value>
</row>
</data>
And it needs to be transformed to the following XML:
<?xml version="1.0" encoding="UTF-8"?>
<data>
<row>
<code>EXCLUDE</code>
<value>VALUE1</value>
</row>
<row>
<code>EXCLUDEALSO</code>
<value>VALUE2</value>
</row>
<group>
<row>
<code>NUMBER</code>
<value>001</value>
</row>
<row>
<code>FROM</code>
<value>NAMELINE_FROM</value>
</row>
<row>
<code>TO</code>
<value>NAMELINE_TO</value>
</row>
</group>
<group>
<row>
<code>NUMBER</code>
<value>002</value>
</row>
<row>
<code>TO</code>
<value>NAMELINE_TO</value>
</row>
</group>
<group>
<row>
<code>NUMBER</code>
<value>003</value>
</row>
<row>
<code>FROM</code>
<value>NAMELINE_FROM</value>
</row>
</group>
<group>
<row>
<code>NUMBER</code>
<value>004</value>
</row>
<row>
<code>TO</code>
<value>NAMELINE</value>
</row>
<row>
<code>FROM</code>
<value>NAMELINE</value>
</row>
</group>
<row>
<code>EXCLUDE</code>
<value>VALUE1</value>
</row>
<row>
<code>EXCLUDEALSO</code>
<value>VALUE2</value>
</row>
</data>
The rules to be applied:
Some rows needs to be excluded from the grouping. These would be the rows that do not have the code NUMBER, FROM or TO.
When code NUMBER appears a new group starts
FROM and TO could be in different order
Rows that have to be exclude will always be in front and/or at the end of the to be grouped codes. It will never appear in between.
In XSLT 2.0 this would be easy and I have the solution, but in XSLT 1.0 I do not know where to start.
Here is a stylesheet:
<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="group" match="row[code = 'TO' or code = 'FROM']" use="generate-id(preceding-sibling::row[code = 'NUMBER'][1])"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="row[code = 'NUMBER']">
<group>
<xsl:copy-of select=". | key('group', generate-id())"/>
</group>
</xsl:template>
<xsl:template match="row[code = 'FROM' or code = 'TO']"/>
</xsl:stylesheet>

Xpath - Filter if group-by parameter exists as child of another variable

I applied a for-each-group to a set of XML to sort. Now I need to add a filter to not process records where the group paramater exists in another variable. I'm not sure what the correct XPath statement is. Example XML Input
Input 1 - $XMLRecords
<root>
<row>
<field1>Record 1-1</field1>
<field2>TEXT</field2>
<groupby>GroupByParameter1</groupby>
</row>
<row>
<field1>Record 1-2</field1>
<field2>TEXT</field2>
<groupby>GroupByParameter1</groupby>
</row>
<row>
<field1>Record 2-1</field1>
<field2>TEXT</field2>
<groupby>GroupByParameter2</groupby>
</row>
<row>
<field1>Record 2-2</field1>
<field2>TEXT</field2>
<groupby>GroupByParameter2</groupby>
</row>
<row>
<field1>Record 3-1</field1>
<field2>TEXT</field2>
<groupby>GroupByParameter3</groupby>
</row>
<row>
<field1>Record 3-2</field1>
<field2>TEXT</field2>
<groupby>GroupByParameter3</groupby>
</row>
<row>
<field1>Record 4-1</field1>
<field2>TEXT</field2>
<groupby>GroupByParameter3</groupby>
</row>
<row>
<field1>Record 4-2</field1>
<field2>TEXT</field2>
<groupby>GroupByParameter4</groupby>
</row>
</root>
Input 2 - $RecordsToSkip
<root>
<row>GroupByParamter2</row>
<row>GroupByParamter4</row>
</root>
Input 2 Schema
<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="row">
<xs:complexType>
<xs:sequence>
<xs:element type="xs:string" name="line" maxOccurs="unbounded" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
Output - Records needed to process
<root>
<row>
<field1>Record 1-1</field1>
<field2>TEXT</field2>
<groupby>GroupByParameter1</groupby>
</row>
<row>
<field1>Record 1-2</field1>
<field2>TEXT</field2>
<groupby>GroupByParameter1</groupby>
</row>
<row>
<field1>Record 3-1</field1>
<field2>TEXT</field2>
<groupby>GroupByParameter3</groupby>
</row>
<row>
<field1>Record 3-2</field1>
<field2>TEXT</field2>
<groupby>GroupByParameter3</groupby>
</row>
</root>
Here's the current XSLT
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:fn="http://www.w3.org/2005/xpath-functions" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:variable name="RecordsToSkip" select="bpws:getVariableData('recordsToSkip')"/>
<xsl:variable name="XMLRecords" select="bpws:getVariableData('xmlRecords')"/>
<xsl:template match="/">
<xsl:element name="root">
<xsl:for-each-group select="$XMLRecords/*:root/*:row" group-by="groupby">
<xsl:call-template name="sortData">
<xsl:with-param name="groupbyparameter" select="groupby"/>
</xsl:call-template>
</xsl:for-each-group>
</xsl:element>
</xsl:template>
<xsl:template name="sortData">
<xsl:param name="groupbyparameter"/>
<--! group specific logic -->
</xsl:template>
</xsl:stylesheet>
I guess I'm trying to place an "if groupby is not a child of RecordsToFilter variable" around the call-template statement
If you don't want rows to participate, then remove them from your population selection:
<xsl:for-each-group select="*/row[not(groupby=$RecordsToSkip)]" group-by="groupby">
That would be easier and faster than trying to skip the group after it is made.
BTW, I am assuming that $RecordsToSkip is a node set or sequence or other repeated construct. It would help maintaining your stylesheet if you added a constraint to the variable declaration that explicitly tells the maintainer about the organization of the variable.