Division of Nested XML Values with XSLT 1.0 on adjacent element match - xslt-1.0

I am attempting to divide two values from an xml where the ID and Date match but am not having luck referencing the second record's value. I have the below XML:
<Export>
<Record>
<ID>1000</ID>
<Date>2022-08-15</Date>
<Value>14.09059</Value>
</Record>
<Record>
<ID>1000</ID>
<Date>2022-08-15</Date>
<Value>259.394</Value>
</Record>
<Record>
<ID>2000</ID>
<Date>2022-08-08</Date>
<Value>32.01453</Value>
</Record>
<Record>
<ID>2000</ID>
<Date>2022-08-08</Date>
<Value>467.052</Value>
</Record>
</Export>
And am looking to achieve the below result:
<Export>
<Record>
<ID>1000</ID>
<Date>2022-08-15</Date>
<Value>18.409</Value>
</Record>
<Record>
<ID>2000</ID>
<Date>2022-08-08</Date>
<Value>14.589</Value>
</Record>
</Export>
Is this possible with XLST 1.0?

If they are adjacent, you could do simply:
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="/Export">
<xsl:copy>
<xsl:for-each select="Record[position() mod 2 = 1]">
<xsl:copy>
<xsl:copy-of select="ID | Date"/>
<Value>
<xsl:value-of select="following-sibling::Record[1]/Value div Value" />
</Value>
</xsl:copy>
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

Related

How to loop through all same name parent and child nodes

I have an XML file where I have same name nodes: Parent and child, for example:
<Root>
<Field name="ID">
<description>Test 1</description>
</Field>
<Field name="Period">
<description>Test 2</description>
<Field name="Name">
<description>Test 3</description>
</Field>
<Field name="Name2">
<description>Test 4</description>
<Field name="address">
<description>Test 5</description>
</Field>
<Field name="partyID">
<description>Test 6</description>
<Field name="E-ID">
<Field name="address">
<description>Test 7</description>
</Field>
</Field>
</Field>
</Field>
</Field>
</Root>
The problem is, I am not sure how deep we can have child elements with the same name:
I used template-match on top parent node:
<xsl:template match="Field">
<xsl:value-of select="description"/>
<xsl:for-each select="Field">
<xsl:value-of select="description"/>
</xsl:for-each>
</xsl:template>
This code is not providing me all the child node values. I am looking for a code which can loop through all same name nodes and provide value of description element.
I cannot add multiple for-each because as I said, this is unknown how many times we will have Field node inside another Field node.
Please help me to solve this problem.
You could use recursion to process all Field elements from the root of the tree to the leaves - for example (you did not post the expected result, so I made something up):
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:strip-space elements="*"/>
<xsl:template match="/Root">
<output>
<xsl:apply-templates select="Field"/>
</output>
</xsl:template>
<xsl:template match="Field">
<desc>
<xsl:value-of select="description"/>
</desc>
<xsl:apply-templates select="Field"/>
</xsl:template>
</xsl:stylesheet>
Alternatively, you could just select all Field elements regardless of their position in the tree hierarchy:
<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:template match="/Root">
<output>
<xsl:for-each select="//Field">
<desc>
<xsl:value-of select="description"/>
</desc>
</xsl:for-each>
</output>
</xsl:template>
</xsl:stylesheet>

Grouping similar items using xsl 1.0

I need to group the records based on OPERATION_CODE. Each record can have more than one Allowance in combination tag. If more than one record have same operation_code, I need all the allowance together. I am using xsl 1.0.
Using this xsl, I am getting only one Allowance per record(in the sample input, first allowance in the first record and first allowance in the second record). The expected output is all the allowances in the first record and second record, as operation_code is same.`
sample input:
<root xmlns="">
<records>
<record>
<OPERATION_CODE>123456</OPERATION_CODE>
<Combinations>
<allowance>
<WMI_CODE>MR0</WMI_CODE>
<VDS_CODE>1</VDS_CODE>
</allowance>
<allowance>
<WMI_CODE>MR1</WMI_CODE>
<VDS_CODE>2</VDS_CODE>
</allowance>
</Combinations>
</record>
<record>
<OPERATION_CODE>123456</OPERATION_CODE>
<Combinations>
<allowance>
<WMI_CODE>MR2</WMI_CODE>
<VDS_CODE>3</VDS_CODE>
</allowance>
</Combinations>
</record>
</records>
</root>
expected output :
<LaborOperationsDetail>
<LaborOperationID>123456</LaborOperationID>
<Combinations>
<Allowance>
<GroupID>MR0</star:GroupID>
<VID>1</star:VID>
</Allowance>
<Allowance>
<GroupID>MR1</star:GroupID>
<VID>2</star:VID>
</Allowance>
<Allowance>
<GroupID>MR2</star:GroupID>
<VID>3</star:VID>
</Allowance>
</Combinations>
<LaborOperationsDetail>
xsl used :
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" />
<xsl:key name="opcode" match="record" use="OPERATION_CODE" />
<xsl:template match="root/records">
<xsl:for-each select="record[generate-id() = generate-id(key('opcode', OPERATION_CODE)[1])]">
<LaborOperationsDetail>
<LaborOperationID><xsl:value-of select="OPERATION_CODE"/></LaborOperationID>
<Combinations>
<xsl:for-each select="key('opcode' ,OPERATION_CODE)">
<Allowance>
<GroupID><xsl:value-of select="Combinations/allowance/WMI_CODE" /></GroupID>
<VID><xsl:value-of select="Combinations/allowance/VDS_CODE" /></VID>
</Allowance>
</xsl:for-each >
</Combinations>
</LaborOperationsDetail>
</xsl:for-each >
</xsl:template>
</xsl:stylesheet>
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="opcode" match="record" use="OPERATION_CODE"/>
<xsl:template match="/root">
<xsl:for-each select="records/record[generate-id() = generate-id(key('opcode', OPERATION_CODE)[1])]">
<LaborOperationsDetail>
<LaborOperationID>
<xsl:value-of select="OPERATION_CODE"/>
</LaborOperationID>
<Combinations>
<xsl:for-each select="key('opcode' ,OPERATION_CODE)/Combinations/allowance">
<Allowance>
<GroupID>
<xsl:value-of select="WMI_CODE"/>
</GroupID>
<VID>
<xsl:value-of select="VDS_CODE"/>
</VID>
</Allowance>
</xsl:for-each>
</Combinations>
</LaborOperationsDetail>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
What you have now:
<xsl:for-each select="key('opcode' ,OPERATION_CODE)">
selects the two record nodes in the current group, and creates one Allowance node for each. Within these nodes, the values are retrieved from the first allowance in each record.

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>

Converting time presented in hours and minutes into decimal and calculate the sum in hours using xslt 1.0

I want to achieve this using XSLT 1.0. Hours is represented in Hours and minutes(for ex: 10:30 is basically 10 hours 30 minutes). I need to convert it into only hours(10.50) and then calculate the sum of all hours nodes.
Here is a source XML document:
<Record>
<hours>10:30</hours>
<hours>20:30</hours>
<hours>10:60</hours>
</Record>
Output:
<Record>
<TotalHours>42.0</TotalHours>
</Record>
Do note that the solution in Joepie's answer causes compile-time syntax error in any compliant XSLT 1.0 processor (it can only be executed without compilation errors if one uses an XSLT 2.0 processor):
SAXON 6.5.4 from Michael Kay
Java version 1.6.0_31
Error at xsl:value-of on line 11 of file:/(Untitled):
Error in expression sum(hours/number(substring-before(., ':'))) + sum(hours/number(substring-after(., ':'))) div 60 : Unexpected token [<function>] in path expression
Transformation failed: Failed to compile stylesheet. 1 error detected.
Press any key to continue . . .
Here is a truly working, true XSLT 1.0 solution:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="http://exslt.org/common" exclude-result-prefixes="ext">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/*">
<xsl:copy>
<xsl:variable name="vrtfTimes">
<xsl:for-each select="*">
<t>
<xsl:value-of select=
"substring-before(., ':') + substring-after(.,':') div 60"/>
</t>
</xsl:for-each>
</xsl:variable>
<TotalHours>
<xsl:value-of select="floor(sum(ext:node-set($vrtfTimes)/*))"/>
</TotalHours>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the provided XML document:
<Record>
<hours>10:30</hours>
<hours>20:30</hours>
<hours>10:60</hours>
</Record>
the wanted, correct result is produced:
<Record>
<TotalHours>42</TotalHours>
</Record>
Note:
In case you want to get not only the hours from the sum, but also the remaining minute -- as minutes:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="http://exslt.org/common" exclude-result-prefixes="ext">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/*">
<xsl:copy>
<xsl:variable name="vrtfTimes">
<xsl:for-each select="*">
<t>
<xsl:value-of select=
"substring-before(., ':') + substring-after(.,':') div 60"/>
</t>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="vSum" select="sum(ext:node-set($vrtfTimes)/*)"/>
<TotalHours>
<xsl:value-of select="floor($vSum)"/>
<xsl:text>:</xsl:text>
<xsl:value-of select="round(60* ($vSum - floor($vSum)))"/>
</TotalHours>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the following (slightly more complex) XML document:
<Record>
<hours>10:30</hours>
<hours>20:30</hours>
<hours>10:60</hours>
<hours>1:15</hours>
<hours>1:03</hours>
</Record>
the correct result is produced:
<Record>
<TotalHours>44:18</TotalHours>
</Record>
In XSLT 1.0:
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output indent="yes"/>
<xsl:template match="Record">
<Record>
<TotalHours>
<xsl:value-of select="
sum(hours/number(substring-before(., ':'))) +
sum(hours/number(substring-after(., ':'))) div 60
"/>
</TotalHours>
</Record>
</xsl:template>
</xsl:transform>
Working example
If you also want the minutes in stead of an fraction of an hour:
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output indent="yes"/>
<xsl:template match="Record">
<Record>
<TotalHours>
<xsl:value-of select="
concat(
sum(hours/number(substring-before(., ':'))) + floor(sum(hours/number(substring-after(., ':'))) div 60),
'.',
format-number(floor(sum(hours/number(substring-after(., ':'))) mod 60), '00')
)
"/>
</TotalHours>
</Record>
</xsl:template>
</xsl:transform>
**Working example

Create Empty element using XSLT 1.0

I am unable to figure it out how to build below XML using XSLT 1.0
<values>
<field name="abc"></field>
<field name="nch"></field>
</values>
there should not be any space between elements start and end tag.
Kindly help me as soon as possible.
Thanks.
In Saxon you have to change the output method to "html".
Example:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" indent="yes"/>
<xsl:template match="/">
<values>
<field name="abc"></field>
<field name="nch"></field>
</values>
</xsl:template>
</xsl:stylesheet>
HereĀ“s a workearound that works for vs2010.
Example 1:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
<values>
<field name="abc">
<xsl:value-of select="substring-before(' ',' ')"/>
</field>
<field name="nch">
<xsl:value-of select="substring-before(' ',' ')"/>
</field>
</values>
</xsl:template>
</xsl:stylesheet>
Example 2:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
<xsl:element name="values">
<xsl:element name="filed">
<xsl:attribute name="name">abc</xsl:attribute>
<xsl:value-of select="substring-before(' ',' ')"/>
</xsl:element>
<xsl:element name="filed">
<xsl:attribute name="name">nch</xsl:attribute>
<xsl:value-of select="substring-before(' ',' ')"/>
</xsl:element>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
Output:
<?xml version="1.0" encoding="utf-8"?>
<values>
<filed name="abc"></filed>
<filed name="nch"></filed>
</values>
The difference between <x></x> and <x/> is purely lexical and generally cannot be controlled by the XSLT processor, because the final output is performed by the Serializer.
With some XSLT processors (using their built-in serializers), it may be possible to output the full form of an empty element. However, with other processors, such as Saxon, this isn't (easily) possible.