For each loop in xslt 1.0 - xslt

How to write a for-each loop in xslt 1.0 which only consider Subbranch elements with ID=11 and 12, ignoring 13 and 14.
<root>
<branch ID='1'>
<subbranch ID='11'>
<Values DataType='String'>
<Value StringLength='3'>abc</Value>
</Values>
</subbranch>
<subbranch ID='12'>
<Values DataType='String'>
<Value StringLength='3'>def</Value>
</Values>
</subbranch>
<subbranch ID='13'>
<Values DataType='String'>
<Value StringLength='3'>uvw</Value>
</Values>
</subbranch>
<subbranch ID='14'>
<Values DataType='String'>
<Value StringLength='3'>xyz</Value>
</Values>
</subbranch>
</branch>
</root>

You can use xsl:for-each with an XPath expression that only matches the first two subbranches:
<xsl:template match="root">
<xsl:for-each select="branch/subbranch[#ID='11' or #ID='12']">
<!-- Emit something... -->
</xsl:for-each>
</xsl:template>
Or, alternately:
<xsl:template match="root">
<xsl:for-each select="branch/subbranch[position() < 3]">
<!-- Emit something... -->
</xsl:for-each>
</xsl:template>

It is recommended to avoid for-each constructions except when dealing with <xsl:key>.
You should use a match-template and apply-templates construct and just not consider the unwanted values.
This XSLT applied to your source:
<?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" method="xml"/>
<xsl:template match="node() | #*">
<xsl:copy>
<xsl:apply-templates select="node() | #*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="subbranch[#ID='13']"/>
<xsl:template match="subbranch[#ID='14']"/>
</xsl:stylesheet>
gives this output:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<branch ID="1">
<subbranch ID="11">
<Values DataType="String">
<Value StringLength="3">abc</Value>
</Values>
</subbranch>
<subbranch ID="12">
<Values DataType="String">
<Value StringLength="3">def</Value>
</Values>
</subbranch>
</branch>
</root>
By matching the unwanted values in empty templates you avoid those parts of the XML to get processed, e.g. <xsl:template match="subbranch[#ID='13']"/>

Related

Split large XML to smaller chunks by node count using XSLT

I have a requirement where we are getting a large XML file and I need to transform on small chunks
below is the XML sample with 4 records, I have to transform the XML so I am able to group them in chunks of 2.
<!-- Original XML-->
<EmpDetails>
<Records>
<EmpID>1</EmpID>
<Age>20</Age>
</Records>
<Records>
<EmpID>2</EmpID>
<Age>21</Age>
</Records>
<Records>
<EmpID>3</EmpID>
<Age>22</Age>
</Records>
<Records>
<EmpID>4</EmpID>
<Age>23</Age>
</Records>
</EmpDetails>
<!-- Expected XML-->
<EmpDetails>
<Split>
<Records>
<EmpID>1</EmpID>
<Age>20</Age>
</Records>
<Records>
<EmpID>2</EmpID>
<Age>21</Age>
</Records>
</Split>
<Split>
<Records>
<EmpID>3</EmpID>
<Age>22</Age>
</Records>
<Records>
<EmpID>4</EmpID>
<Age>23</Age>
</Records>
</Split>
</EmpDetails>
I tried few things including below without success.
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<EmpDetails>
<xsl:for-each select="/EmpDetails/Records">
<Split>
<Records>
<EmpID>
<xsl:value-of select="EmpID"/>
</EmpID>
<Age>
<xsl:value-of select="Age"/>
</Age>
</Records>
</Split>
</xsl:for-each>
</EmpDetails>
</xsl:template>
</xsl:stylesheet>
Thanks
Yatan
group them in chunks of 2.
This could be done simply by:
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="/EmpDetails">
<xsl:copy>
<xsl:for-each select="Records[position() mod 2 = 1]">
<Split>
<xsl:copy-of select=". | following-sibling::Records[1]"/>
</Split>
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Added:
To divide the records into groups of 200, you can do:
...
<xsl:for-each select="Records[position() mod 200 = 1]">
<Split>
<xsl:copy-of select=". | following-sibling::Records[position() < 200]"/>
</Split>
</xsl:for-each>
...
In XSLT 2.0 you could do:
<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:template match="/EmpDetails">
<xsl:copy>
<xsl:for-each-group select="Records" group-adjacent="(position() - 1) idiv 200">
<Split>
<xsl:copy-of select="current-group()"/>
</Split>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
use this code:
<xsl:for-each select="Records[position() mod 2 = 0]">
instead of this
<xsl:for-each select="Records[position() mod 2 = 1]">

Grouping and Sum of XML nodes using xslt

I am pretty new in XSLT and I am stuck at once place.
I have one input xml file as below
<Roots>
<Root>
<Value1>Links1</Value1>
<Value2>notadmin</Value2>
<Count>1</Count>
</Root>
<Root>
<Value1>Links1</Value1>
<Value2>notadmin</Value2>
<Count>10</Count>
</Root>
<Root>
<Value1>Links2</Value1>
<Value2>userxyz</Value2>
<Count>10</Count>
</Root>
<Root>
<Value1>Links2</Value1>
<Value2>usermnp</Value2>
<Count>10</Count>
</Root>
<Root>
<Value1>Links3</Value1>
<Value2>user123</Value2>
<Count>5</Count>
</Root>
...
...
...
</Roots>
Now, I want to sum the count value for similar values of Value1 and Value2 so that it looks like below:
<Roots>
<Root>
<Value1>Links1</Value1>
<Value2>notadmin</Value2>
<Count>11</Count>
</Root>
<Root>
<Value1>Links2</Value1>
<Value2>userxyz</Value2>
<Count>20</Count>
</Root>
<Root>
<Value1>Links3</Value1>
<Value2>user123</Value2>
<Count>5</Count>
</Root>
</Roots>
I have been trying a lot to transform this xml and here is my code:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs" >
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Roots">
<Roots>
<Root>
<xsl:for-each-group select="Root" group-by=
"concat(Value1, '+', Value2)">
<xsl:copy-of select=
"current-group()[1]/*[starts-with(name(),'key')]"/>
<Count>
<xsl:value-of select="sum(current-group()/Count)"/>
</Count>
</xsl:for-each-group>
</Root>
</Roots>
</xsl:template>
</xsl:stylesheet>
I know I am doing wrong and could be lot easier to the expert but I really need some help and direction.
Thanks
Your XSLT was close, but move the <Root> element definition into the xsl:for-each-group. I couldn't make sense of your xsl:copy-of..., so I just reproduced the elements one-by-one. So use this simple XSLT-2.0 stylesheet:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes" />
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="/Roots">
<xsl:copy>
<xsl:apply-templates select="#*" />
<xsl:for-each-group select="Root" group-by="Value1">
<Root>
<Value1><xsl:value-of select="current-grouping-key()" /></Value1>
<Value2><xsl:value-of select="current-group()[1]/Value2" /></Value2>
<Count><xsl:value-of select="sum(current-group()/Count)" /></Count>
</Root>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
It's output is:
<Roots>
<Root>
<Value1>Links1</Value1>
<Value2>notadmin</Value2>
<Count>11</Count>
</Root>
<Root>
<Value1>Links2</Value1>
<Value2>userxyz</Value2>
<Count>20</Count>
</Root>
<Root>
<Value1>Links3</Value1>
<Value2>user123</Value2>
<Count>5</Count>
</Root>
</Roots>
Right now, your grouping concat(Value1, '+', Value2) will not yield three nodes since nodes at Links2 maintains two different value2 node values. Also, your identity transform template is redundant since you rewrite the XML tree directly from root, Roots.
One Grouping (only Value1)
<?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" version="2.0" exclude-result-prefixes="xs">
<xsl:output omit-xml-declaration="yes" indent="yes" />
<xsl:template match="Roots">
<Roots>
<xsl:for-each-group select="Root" group-by="Value1">
<Root>
<xsl:copy-of select="Value1|Value2"/>
<Count>
<xsl:value-of select="sum(current-group()/Count)" />
</Count>
</Root>
</xsl:for-each-group>
</Roots>
</xsl:template>
</xsl:stylesheet>
Returns with first value2 returned:
<Roots>
<Root>
<Value1>Links1</Value1>
<Value2>notadmin</Value2>
<Count>11</Count>
</Root>
<Root>
<Value1>Links2</Value1>
<Value2>userxyz</Value2>
<Count>20</Count>
</Root>
<Root>
<Value1>Links3</Value1>
<Value2>user123</Value2>
<Count>5</Count>
</Root>
</Roots>
Two Groupings (Value1 and Value2)
Keeping original grouping:
<xsl:for-each-group select="Root" group-by="concat(Value1, '+', Value2)">
Returns four nodes
<Roots>
<Root>
<Value1>Links1</Value1>
<Value2>notadmin</Value2>
<Count>11</Count>
</Root>
<Root>
<Value1>Links2</Value1>
<Value2>userxyz</Value2>
<Count>10</Count>
</Root>
<Root>
<Value1>Links2</Value1>
<Value2>usermnp</Value2>
<Count>10</Count>
</Root>
<Root>
<Value1>Links3</Value1>
<Value2>user123</Value2>
<Count>5</Count>
</Root>
</Roots>

Replace the XML element's name with the value of an attribute anywhere

I have a working XSLT that replaces an element name with an attribute value only when the attribute is named "AttributeID". It saves the original element name in a new attribute called StepClass but it only works at one level.
This XML:
<Products>
<Product>
<Values>
<Value AttributeID="One">1</Value>
<MultiValue AttributeID="Multi1">
<Value>111</Value>
</MultiValue>
</Values>
</Product>
</Products>
Becomes this XML:
<Products>
<Product>
<Values>
<One StepClass="Value">1</One>
<Multi1 StepClass="MultiValue">
<Value>111</Value>
</Multi1>
</Values>
</Product>
</Products>
Using this XSLT:
<?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" indent="yes"/>
<xsl:strip-space elements="*"/>
<!-- identity transform -->
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<!-- Change Values/Value value of AttributeID -->
<!-- <xsl:template match="Values/Value|MultiValue|MetaData/Value"> This was working with 1 level -->
<xsl:template match="*[#AttributeID]">
<xsl:element name="{#AttributeID}">
<xsl:attribute name="StepClass">
<xsl:value-of select="name()" />
</xsl:attribute>
<xsl:apply-templates select="#*|node()"/>
</xsl:element>
</xsl:template>
<!--empty template suppresses this attribute-->
<xsl:template match="#AttributeID" />
</xsl:stylesheet>
But fails as soon as the source XML has nested products. I would like the substitution to work at any level. What am I doing wrong here?
Nested XML:
<Products>
<Product>
<Values>
<Value AttributeID="One">1</Value>
<MultiValue AttributeID="Multi1">
<Value>111</Value>
</MultiValue>
</Values>
<Product>
<Values>
<Value AttributeID="Two">2</Value>
<MultiValue AttributeID="Multi2">
<Value>222</Value>
</MultiValue>
</Values>
</Product>
</Product>
</Products>
XSLT as depicted in the post works as expected.

XSLT performance issue with large data

having performance issues with my xslt code:
this is my input file:
<?xml version="1.0" encoding="UTF-8"?>
<Products>
<Product ID="111111" Type="Item" ParentID="7402">
<Name>ABC</Name>
<Values>
<Value AttributeID="11">8.00</Value>
<Value AttributeID="12">8.00</Value>
<Value AttributeID="13">0.18</Value>
</Values>
<Product ID="B582B65D" Type="UID" ParentID="111111">
<Values>
<Value AttributeID="11">8.00</Value>
<Value AttributeID="12">8.00</Value>
<Value AttributeID="13">0.18</Value>
<Value AttributeID="14">0.18</Value>
</Values>
</Product>
</Product>
<Product ID="222222" Type="Item" ParentID="7402">
<Name>XYZ</Name>
<Values>
<Value AttributeID="12">8.00</Value>
<Value AttributeID="13">8.00</Value>
<Value AttributeID="15">0.18</Value>
</Values>
<Product ID="B582B65D" Type="UID" ParentID="111111">
<Values>
<Value AttributeID="11">8.00</Value>
<Value AttributeID="12">8.00</Value>
<Value AttributeID="16">0.18</Value>
<Value AttributeID="18">0.18</Value>
</Values>
</Product>
</Product>
</Products>
and this is my transformation code:
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:math="http://exslt.org/math"
extension-element-prefixes="math">
<xsl:output method="xml" indent="yes" />
<xsl:param name="file2" select="document('Mapping.xml')" />
<xsl:template match="/Products">
<Products>
<xsl:for-each select="Product">
<xsl:call-template name="item" />
</xsl:for-each>
</Products>
</xsl:template>
<xsl:template name="item">
<Product type="{./#Type}" ID="{./#ID}">
<xsl:for-each select="./Values/Value">
<xsl:variable name="Idval" select="#AttributeID" />
<xsl:element name="{$file2//Groups/AttributeID[#ID=$Idval]/#group}">
<xsl:element name="{$file2//Groups/AttributeID[#ID=$Idval]}">
<xsl:attribute name="ID"><xsl:value-of select="$Idval"/></xsl:attribute>
<xsl:value-of select="." />
</xsl:element>
</xsl:element>
</xsl:for-each>
<xsl:call-template name="uid" />
</Product>
</xsl:template>
<xsl:template name="uid">
<Product type="{./Product/#Type}" ParentId="{./Product/#ParentID}">
<xsl:for-each select="./Product/Values/Value">
<xsl:variable name="Idval" select="#AttributeID" />
<xsl:element name="{$file2//Groups/AttributeID[#ID=$Idval]/#group}">
<xsl:element name="{$file2//Groups/AttributeID[#ID=$Idval]}">
<xsl:attribute name="ID"><xsl:value-of select="$Idval"/></xsl:attribute>
<xsl:value-of select="." />
</xsl:element>
</xsl:element>
</xsl:for-each>
</Product>
</xsl:template>
</xsl:stylesheet>
above xslt is using below xml file for mapping attribute id to corresponding name and group
Mapping.xml
<?xml version="1.0" encoding="UTF-8"?>
<Groups>
<AttributeID ID="11" group="Pack1">Height</AttributeID>
<AttributeID ID="12" group="Pack2">Width</AttributeID>
<AttributeID ID="13" group="Pack1">Depth</AttributeID>
<AttributeID ID="14" group="Pack3">Length</AttributeID>
<AttributeID ID="15" group="Pack3">Lbs</AttributeID>
<AttributeID ID="16" group="Pack4">Litre</AttributeID>
</Groups>
Replace the use of expressions like
select="$file2//Groups/AttributeID[#ID=$Idval]"
with a key:
<xsl:key name="ID" match="Groups/AttributeID" use="#ID"/>
and then
select="key('ID', $IDval, $file)"/>
Alternatively, Saxon-EE will do this optimization for you automatically.
The key() function with 3 arguments is XSLT 2.0 syntax. If you have the misfortune to be using XSLT 1.0, you have to write a dummy xsl:for-each that makes $file the context item, because key() will only select within the document containing the context item.
Define a key for the cross document lookup: <xsl:key name="by-id" match="Groups/AttributeID" use="#ID"/>, then (assuming an XSLT 2.0 processor) you can simplify expressions like <xsl:element name="{$file2//Groups/AttributeID[#ID=$Idval]/#group}"> to <xsl:element name="{key('by-id', #AttributeID, $file2)/#group">. Make the same change for the other cross references you have, i.e. all those $file2//Groups/AttributeID[#ID=$Idval] expressions should use the key lookup.
Making the simplified assumption that your second file isn't too big, you want to fold the values there into your template. It would work with XSLT 1.0 too. Something like this:
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:math="http://exslt.org/math"
extension-element-prefixes="math">
<xsl:output method="xml" indent="yes" />
<xsl:template match="/">
<Products>
<xsl:apply-templates select="/Products/Product" />
</Products>
</xsl:template>
<xsl:template match="Product">
<xsl:element name="Product">
<xsl:apply-templates select="#*" />
<xsl:apply-templates />
</xsl:element>
</xsl:template>
<xsl:template match="Name">
<Name>
<xsl:value-of select="." />
</Name>
</xsl:template>
<xsl:template match="#*">
<xsl:attribute name="{name()}">
<xsl:value-of select="." />
</xsl:attribute>
</xsl:template>
<xsl:template match="Values">
<Values>
<xsl:apply-templates />
</Values>
</xsl:template>
<!-- Templates for individual AttributeIDs, only when there are few -->
<xsl:template match="Value[#AttributeID='11']">
<Pack1>
<xsl:element name="Height">
<xsl:attribute name="ID">
<xsl:value-of select="#AttributeID" />
</xsl:attribute>
<xsl:value-of select="." />
</xsl:element>
</Pack1>
</xsl:template>
<!-- Repeat for the other AttributeID values -->
</xsl:stylesheet>
(Typed off my head, will contain typos)
Of course if it is big Michael's advice is the best course of action.

How do I combine or merge grouped nodes?

Using the XSL:
<?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 method="xml"/>
<xsl:template match="/">
<records>
<record>
<!-- Group record by bigID, for further processing -->
<xsl:for-each-group select="records/record" group-by="bigID">
<xsl:sort select="bigID"/>
<xsl:for-each select="current-group()">
<!-- Create new combined record -->
<bigID>
<!-- <xsl:value-of select="."/> -->
<xsl:for-each select=".">
<xsl:value-of select="bigID"/>
</xsl:for-each>
</bigID>
<text>
<xsl:value-of select="text"/>
</text>
</xsl:for-each>
</xsl:for-each-group>
</record>
</records>
</xsl:template>
</xsl:stylesheet>
I'm trying to change:
<?xml version="1.0" encoding="UTF-8"?>
<records>
<record>
<bigID>123</bigID>
<text>Contains text for 123</text>
<bigID>456</bigID>
<text>Some 456 text</text>
<bigID>123</bigID>
<text>More 123 text</text>
<bigID>123</bigID>
<text>Yet more 123 text</text>
</record>
</records>
into:
<?xml version="1.0" encoding="UTF-8"?>
<records>
<record>
<bigID>123
<text>Contains text for 123</text>
<text>More 123 text</text>
<text>Yet more 123 text</text>
</bigID>
<bigID>456
<text>Some 456 text</text>
</bigID>
</record>
</records>
Right now, I'm just listing the grouped <bigID>s, individually. I'm missing the step after grouping, where I combine the grouped <bigID> nodes. My suspicion is that I need to use the "key" function somehow, but I'm not sure.
Thanks for any help.
Here is the wanted XSLT 2.0 transformation:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:key name="kTextforId" match="text"
use="preceding-sibling::bigID[1]"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="record">
<record>
<xsl:for-each-group select="bigID" group-by=".">
<bigID>
<xsl:sequence select="current-grouping-key()"/>
<xsl:copy-of select=
"key('kTextforId', current-grouping-key())"/>
</bigID>
</xsl:for-each-group>
</record>
</xsl:template>
</xsl:stylesheet>
When performed on the provided XML document, the wanted result is produced.
In XSLT 2.0 you don't need keys for grouping.
Since you are just copying the text elements in the group, the inner for-each can be removed.
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" />
<xsl:template match="/">
<records>
<record>
<xsl:for-each-group select="records/record/bigID" group-by=".">
<xsl:sort select="." data-type="number" />
<bigID>
<xsl:value-of select="." />
<xsl:copy-of select="current-group()/following-sibling::text[1]" />
</bigID>
</xsl:for-each-group>
</record>
</records>
</xsl:template>
</xsl:stylesheet>
If you instead wanted to output the bigID elements followed by their text elements, then my loop would be replaced by the following.
<xsl:for-each-group select="records/record/bigID" group-by=".">
<xsl:sort select="." data-type="number" />
<xsl:copy-of select="." />
<xsl:copy-of select="current-group()/following-sibling::text[1]" />
</xsl:for-each-group>