How to apply for-each-group in XSLT with Apache Camel - xslt

I am trying to use group by in Apache Camel XSLT but when using for-each-group with sytlesheet version 2.0 ending up with exception "javax.xml.transform.TransformerException: "select" attribute is not allowed on the xsl:for-each-group element!".
Below is my updated code.
<?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" />
<xsl:template match="/">
<MESSAGES>
<xsl:for-each-group
select="/OrderStatusUpdate/OrderStatusEvents/OrderStatusEvent"
group-by="/OrderStatusUpdate/OrderStatusEvents/OrderStatusEvent/WebOrderId">
<COMMANDSTATUS>
<xsl:attribute name="ID">SHIPPED</xsl:attribute>
<xsl:attribute name="DESCRIPTION">Goods Shipped</xsl:attribute>
<ORDER>
<xsl:attribute name="O_ID"><xsl:value-of
select='distinct-values(/OrderStatusUpdate/OrderStatusEvents/OrderStatusEvent/WebOrderId)' /></xsl:attribute>
<xsl:choose>
<xsl:when
test="/OrderStatusUpdate/OrderStatusEvents/OrderStatusEvent/OrderSource = 'STORE-ORDER'">
<xsl:attribute name="TRACKING_URL">https://www.fedex.com/apps/fedextrack/?tracknumbers=<xsl:value-of
select='distinct-values(/OrderStatusUpdate/OrderStatusEvents/OrderStatusEvent/OrderShipmentDetails/OrderShipmentDetail/ShippingTrackingNumber)' /></xsl:attribute>
</xsl:when>
<xsl:when
test="/OrderStatusUpdate/OrderStatusEvents/OrderStatusEvent/OrderSource != 'STORE-ORDER'">
<xsl:attribute name="TRACKING_URL">http://wwwapps.<xsl:value-of
select='distinct-values(/OrderStatusUpdate/OrderStatusEvents/OrderStatusEvent/OrderShipmentDetails/OrderShipmentDetail/ShippingCarrier)' />.com/WebTracking/track?track=yes&trackNums=<xsl:value-of
select='distinct-values(/OrderStatusUpdate/OrderStatusEvents/OrderStatusEvent/OrderShipmentDetails/OrderShipmentDetail/ShippingTrackingNumber)' /></xsl:attribute>
</xsl:when>
</xsl:choose>
<xsl:for-each
select="/OrderStatusUpdate/OrderStatusEvents/OrderStatusEvent">
<ORDER_LINE>
<xsl:attribute name="OL_ID"><xsl:value-of
select='OrderLineId' /></xsl:attribute>
<xsl:choose>
<xsl:when test="substring(ItemId, 0, 6) = '88-00'">
<xsl:attribute name="SKU"><xsl:value-of
select='substring(ItemId, 6)' /></xsl:attribute>
</xsl:when>
<xsl:otherwise>
<xsl:attribute name="SKU"><xsl:value-of
select='ItemId' /></xsl:attribute>
</xsl:otherwise>
</xsl:choose>
<xsl:attribute name="QUANTITY"><xsl:value-of
select='distinct-values(/OrderStatusUpdate/OrderStatusEvents/OrderStatusEvent/OrderStatusDetails/OrderStatusDetail/Qty)' /></xsl:attribute>
</ORDER_LINE>
</xsl:for-each>
</ORDER>
</COMMANDSTATUS>
</xsl:for-each-group>
</MESSAGES>
</xsl:template>
</xsl:stylesheet>
Input XML:
<?xml version="1.0" encoding="utf-8"?>
<OrderStatusUpdate fileType="Order Status Update" fileStartTime="2021-10-13 06:20:18 " fileEndTime=" 2021-10-13 07:35:26">
<MessageHeader>
<Standard>eBay_Enterprise</Standard>
<HeaderVersion>EWS_eb2c_1.1</HeaderVersion>
<VersionReleaseNumber>EWS_eb2c_1.1</VersionReleaseNumber>
<SourceData>
<SourceId>OMS</SourceId>
<SourceType>OrderManagementSystem</SourceType>
</SourceData>
<DestinationData>
<DestinationId>EE_OrderRTStatusXML</DestinationId>
<DestinationType>MAILBOX</DestinationType>
</DestinationData>
<EventType>OrderStatus</EventType>
<MessageData>
<MessageId>20211013073625</MessageId>
<CorrelationId>0</CorrelationId>
</MessageData>
<CreateDateAndTime>2021-10-13 07:36:25</CreateDateAndTime>
</MessageHeader>
<OrderStatusEvents>
<OrderStatusEvent>
<StoreCode>R21_US</StoreCode>
<OrderId>101425120920</OrderId>
<ExternalOrderId/>
<WebOrderId>002010003694706_1</WebOrderId>
<OrderSource type=""/>
<OrderLineId>1</OrderLineId>
<OriginalOrderId/>
<OriginalWebOrderId/>
<OriginalOrderLineId/>
<ItemId>88-0028547859</ItemId>
<OrderStatusDetails>
<OrderStatusDetail>
<OrderStatusEventTimeStamp>2021-10-13 07:20:23</OrderStatusEventTimeStamp>
<StatusName>Fulfilled and Invoiced</StatusName>
<ReturnReason/>
<Qty>1</Qty>
<CancelReason/>
<CancelReasonText/>
</OrderStatusDetail>
</OrderStatusDetails>
<OrderShipmentDetails>
<OrderShipmentDetail>
<ShippingCarrier>UPS</ShippingCarrier>
<ShippingServiceLevel>PBX01</ShippingServiceLevel>
<ShippingTrackingNumber>1ZEW3567YW46706215</ShippingTrackingNumber>
<ShippingTimestamp>2021-10-13 07:12:00</ShippingTimestamp>
</OrderShipmentDetail>
</OrderShipmentDetails>
</OrderStatusEvent>
<OrderStatusEvent>
<StoreCode>R21_US</StoreCode>
<OrderId>101425120920</OrderId>
<ExternalOrderId/>
<WebOrderId>002010003694706_1</WebOrderId>
<OrderSource type=""/>
<OrderLineId>3</OrderLineId>
<OriginalOrderId/>
<OriginalWebOrderId/>
<OriginalOrderLineId/>
<ItemId>88-0028461473</ItemId>
<OrderStatusDetails>
<OrderStatusDetail>
<OrderStatusEventTimeStamp>2021-10-13 07:20:23</OrderStatusEventTimeStamp>
<StatusName>Fulfilled and Invoiced</StatusName>
<ReturnReason/>
<Qty>1</Qty>
<CancelReason/>
<CancelReasonText/>
</OrderStatusDetail>
</OrderStatusDetails>
<OrderShipmentDetails>
<OrderShipmentDetail>
<ShippingCarrier>UPS</ShippingCarrier>
<ShippingServiceLevel>PBX01</ShippingServiceLevel>
<ShippingTrackingNumber>1ZEW3567YW46706215</ShippingTrackingNumber>
<ShippingTimestamp>2021-10-13 07:12:00</ShippingTimestamp>
</OrderShipmentDetail>
</OrderShipmentDetails>
</OrderStatusEvent>
<OrderStatusEvent>
<StoreCode>R21_US</StoreCode>
<OrderId>101425254810</OrderId>
<ExternalOrderId/>
<WebOrderId>002010003698153_1</WebOrderId>
<OrderSource type=""/>
<OrderLineId>1</OrderLineId>
<OriginalOrderId/>
<OriginalWebOrderId/>
<OriginalOrderLineId/>
<ItemId>88-0028353647</ItemId>
<OrderStatusDetails>
<OrderStatusDetail>
<OrderStatusEventTimeStamp>2021-10-13 06:40:15</OrderStatusEventTimeStamp>
<StatusName>Fulfilled and Invoiced</StatusName>
<ReturnReason/>
<Qty>1</Qty>
<CancelReason/>
<CancelReasonText/>
</OrderStatusDetail>
</OrderStatusDetails>
<OrderShipmentDetails>
<OrderShipmentDetail>
<ShippingCarrier>UPS</ShippingCarrier>
<ShippingServiceLevel>PBX03</ShippingServiceLevel>
<ShippingTrackingNumber>1ZEW3571YW13958113</ShippingTrackingNumber>
<ShippingTimestamp>2021-10-13 06:30:00</ShippingTimestamp>
</OrderShipmentDetail>
</OrderShipmentDetails>
</OrderStatusEvent>
<OrderStatusEvent>
<StoreCode>R21_US</StoreCode>
<OrderId>101425254810</OrderId>
<ExternalOrderId/>
<WebOrderId>002010003698153_1</WebOrderId>
<OrderSource type=""/>
<OrderLineId>2</OrderLineId>
<OriginalOrderId/>
<OriginalWebOrderId/>
<OriginalOrderLineId/>
<ItemId>88-0028635431</ItemId>
<OrderStatusDetails>
<OrderStatusDetail>
<OrderStatusEventTimeStamp>2021-10-13 06:40:15</OrderStatusEventTimeStamp>
<StatusName>Fulfilled and Invoiced</StatusName>
<ReturnReason/>
<Qty>1</Qty>
<CancelReason/>
<CancelReasonText/>
</OrderStatusDetail>
</OrderStatusDetails>
<OrderShipmentDetails>
<OrderShipmentDetail>
<ShippingCarrier>UPS</ShippingCarrier>
<ShippingServiceLevel>PBX03</ShippingServiceLevel>
<ShippingTrackingNumber>1ZEW3571YW13958113</ShippingTrackingNumber>
<ShippingTimestamp>2021-10-13 06:30:00</ShippingTimestamp>
</OrderShipmentDetail>
</OrderShipmentDetails>
</OrderStatusEvent>
</OrderStatusEvents>
</OrderStatusUpdate>
Expected XML:
<?xml version='1.0' encoding='UTF-8'?>
<MESSAGES>
<COMMANDSTATUS ID="SHIPPED" DESCRIPTION="Goods Shipped">
<ORDER O_ID="002010003694706_1" TRACKING_URL="http://wwwapps.UPS.com/WebTracking/track?track=yes&trackNums=1ZEW3567YW46706215">
<ORDER_LINE OL_ID="1" SKU="28547859" QUANTITY="1"/>
<ORDER_LINE OL_ID="3" SKU="28461473" QUANTITY="1"/>
</ORDER>
</COMMANDSTATUS>
<COMMANDSTATUS ID="SHIPPED" DESCRIPTION="Goods Shipped">
<ORDER O_ID="002010003698153_1" TRACKING_URL="http://wwwapps.UPS.com/WebTracking/track?track=yes&trackNums=1ZEW3571YW13958113">
<ORDER_LINE OL_ID="1" SKU="28353647" QUANTITY="1"/>
<ORDER_LINE OL_ID="2" SKU="28635431" QUANTITY="1"/>
</ORDER>
</COMMANDSTATUS>
</MESSAGES>
I tried with 1.0 version as well but didn't get through, below is the code for the same.
<?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:key name="webOrderId"
match="/OrderStatusUpdate/OrderStatusEvents/OrderStatusEvent"
use="/OrderStatusUpdate/OrderStatusEvents/OrderStatusEvent/WebOrderId" />
<xsl:variable name="uppercase"
select="'ABCDEFGHIJKLMNOPQRSTUVWXYZ'" />
<xsl:variable name="lowercase"
select="'abcdefghijklmnopqrstuvwxyz'" />
<xsl:template match="/">
<MESSAGES>
<xsl:for-each
select="/OrderStatusUpdate/OrderStatusEvents/OrderStatusEvent[count(. | key('webOrderId', /OrderStatusUpdate/OrderStatusEvents/OrderStatusEvent/WebOrderId)[1]) = 1]">
<xsl:for-each select="key('webOrderId', /OrderStatusUpdate/OrderStatusEvents/OrderStatusEvent/WebOrderId)">
<COMMANDSTATUS>
<xsl:attribute name="ID">SHIPPED</xsl:attribute>
<xsl:attribute name="DESCRIPTION">Goods Shipped</xsl:attribute>
<ORDER>
<xsl:choose>
<xsl:when
test="substring(/OrderStatusUpdate/OrderStatusEvents/OrderStatusEvent/WebOrderId, 0, 6) = '00201'">
<xsl:attribute name="O_ID"><xsl:value-of
select='substring(/OrderStatusUpdate/OrderStatusEvents/OrderStatusEvent/WebOrderId, 6)' /></xsl:attribute>
</xsl:when>
<xsl:otherwise>
<xsl:attribute name="O_ID"><xsl:value-of
select='/OrderStatusUpdate/OrderStatusEvents/OrderStatusEvent/WebOrderId' /></xsl:attribute>
</xsl:otherwise>
</xsl:choose>
<xsl:choose>
<xsl:when
test="/OrderStatusUpdate/OrderStatusEvents/OrderStatusEvent/OrderSource = 'STORE-ORDER'">
<xsl:attribute name="TRACKING_URL">https://www.fedex.com/apps/fedextrack/?tracknumbers=<xsl:value-of
select='/OrderStatusUpdate/OrderStatusEvents/OrderStatusEvent/OrderShipmentDetails/OrderShipmentDetail/ShippingTrackingNumber' /></xsl:attribute>
</xsl:when>
<xsl:when
test="/OrderStatusUpdate/OrderStatusEvents/OrderStatusEvent/OrderSource != 'STORE-ORDER'">
<xsl:attribute name="TRACKING_URL">http://wwwapps.<xsl:value-of
select='/OrderStatusUpdate/OrderStatusEvents/OrderStatusEvent/OrderShipmentDetails/OrderShipmentDetail/ShippingCarrier' />.com/WebTracking/track?track=yes&trackNums=<xsl:value-of
select='/OrderStatusUpdate/OrderStatusEvents/OrderStatusEvent/OrderShipmentDetails/OrderShipmentDetail/ShippingTrackingNumber' /></xsl:attribute>
</xsl:when>
</xsl:choose>
<xsl:for-each
select="/OrderStatusUpdate/OrderStatusEvents/OrderStatusEvent">
<ORDER_LINE>
<xsl:attribute name="OL_ID"><xsl:value-of
select='OrderLineId' /></xsl:attribute>
<xsl:choose>
<xsl:when test="substring(ItemId, 0, 6) = '88-00'">
<xsl:attribute name="SKU"><xsl:value-of
select='substring(ItemId, 6)' /></xsl:attribute>
</xsl:when>
<xsl:otherwise>
<xsl:attribute name="SKU"><xsl:value-of
select='ItemId' /></xsl:attribute>
</xsl:otherwise>
</xsl:choose>
<xsl:attribute name="QUANTITY"><xsl:value-of
select='/OrderStatusUpdate/OrderStatusEvents/OrderStatusEvent/OrderStatusDetails/OrderStatusDetail/Qty' /></xsl:attribute>
</ORDER_LINE>
</xsl:for-each>
</ORDER>
</COMMANDSTATUS>
</xsl:for-each>
</xsl:for-each>
</MESSAGES>
</xsl:template>
</xsl:stylesheet>
Below is the output received with above code which is not expected.
<?xml version="1.0" encoding="UTF-8"?>
<MESSAGES>
<COMMANDSTATUS ID="SHIPPED" DESCRIPTION="Goods Shipped">
<ORDER O_ID="002010003694706_1 002010003698153_1"
TRACKING_URL="http://wwwapps.UPS.com/WebTracking/track?track=yes&trackNums=1ZEW3567YW46706215 1ZEW3571YW13958113">
<ORDER_LINE OL_ID="1" SKU="28547859" QUANTITY="1"/>
<ORDER_LINE OL_ID="3" SKU="28461473" QUANTITY="1"/>
<ORDER_LINE OL_ID="1" SKU="28353647" QUANTITY="1"/>
<ORDER_LINE OL_ID="2" SKU="28635431" QUANTITY="1"/>
</ORDER>
</COMMANDSTATUS>
<COMMANDSTATUS ID="SHIPPED" DESCRIPTION="Goods Shipped">
<ORDER O_ID="002010003694706_1 002010003698153_1"
TRACKING_URL="http://wwwapps.UPS.com/WebTracking/track?track=yes&trackNums=1ZEW3567YW46706215 1ZEW3571YW13958113">
<ORDER_LINE OL_ID="1" SKU="28547859" QUANTITY="1"/>
<ORDER_LINE OL_ID="3" SKU="28461473" QUANTITY="1"/>
<ORDER_LINE OL_ID="1" SKU="28353647" QUANTITY="1"/>
<ORDER_LINE OL_ID="2" SKU="28635431" QUANTITY="1"/>
</ORDER>
</COMMANDSTATUS>
</MESSAGES>

You don't show how your route code how the XSL is called, but I suppose it is executed with the standard Java XSL processor (who does not support XSL 2).
You need to explicitly tell Camel to use Saxon as XSL processor to be able to use XSL 2.
In Camel 3.x there is a dedicated Saxon component
to("xslt-saxon:path/to/yourStylesheet.xsl");
In Camel 2.x this was done with a URL option
.to("xslt:path/to/yourStylesheet.xsl?saxon=true")
Of course you also have to add Saxon as a dependency.

Here is the XSLT code which is working for my use case.
<?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" />
<xsl:template match="/">
<MESSAGES>
<xsl:for-each-group
select="/OrderStatusUpdate/OrderStatusEvents/OrderStatusEvent"
group-by="/OrderStatusUpdate/OrderStatusEvents/OrderStatusEvent/WebOrderId">
<COMMANDSTATUS>
<xsl:attribute name="ID">SHIPPED</xsl:attribute>
<xsl:attribute name="DESCRIPTION">Goods Shipped</xsl:attribute>
<ORDER>
<xsl:choose>
<xsl:when
test="substring(current-grouping-key(), 0, 6) = '00201'">
<xsl:attribute name="O_ID"><xsl:value-of
select='substring(current-grouping-key(), 6)' /></xsl:attribute>
</xsl:when>
<xsl:otherwise>
<xsl:attribute name="O_ID"><xsl:value-of
select='current-grouping-key()' /></xsl:attribute>
</xsl:otherwise>
</xsl:choose>
<xsl:for-each
select="current-group()[WebOrderId = current-grouping-key()]">
<xsl:choose>
<xsl:when
test="/OrderStatusUpdate/OrderStatusEvents/OrderStatusEvent/OrderSource = 'STORE-ORDER'">
<xsl:attribute name="TRACKING_URL">https://www.fedex.com/apps/fedextrack/?tracknumbers=<xsl:value-of
select='./OrderShipmentDetails/OrderShipmentDetail/ShippingTrackingNumber' /></xsl:attribute>
</xsl:when>
<xsl:when
test="/OrderStatusUpdate/OrderStatusEvents/OrderStatusEvent/OrderSource != 'STORE-ORDER'">
<xsl:attribute name="TRACKING_URL">http://wwwapps.<xsl:value-of
select='./OrderShipmentDetails/OrderShipmentDetail/ShippingCarrier' />.com/WebTracking/track?track=yes&trackNums=<xsl:value-of
select='./OrderShipmentDetails/OrderShipmentDetail/ShippingTrackingNumber' /></xsl:attribute>
</xsl:when>
</xsl:choose>
</xsl:for-each>
<xsl:for-each
select="current-group()[WebOrderId = current-grouping-key()]">
<ORDER_LINE>
<xsl:attribute name="OL_ID"><xsl:value-of
select='OrderLineId' /></xsl:attribute>
<xsl:choose>
<xsl:when test="substring(ItemId, 0, 6) = '88-00'">
<xsl:attribute name="SKU"><xsl:value-of
select='substring(ItemId, 6)' /></xsl:attribute>
</xsl:when>
<xsl:otherwise>
<xsl:attribute name="SKU"><xsl:value-of
select='ItemId' /></xsl:attribute>
</xsl:otherwise>
</xsl:choose>
<xsl:attribute name="QUANTITY"><xsl:value-of
select='./OrderStatusDetails/OrderStatusDetail/Qty' /></xsl:attribute>
</ORDER_LINE>
</xsl:for-each>
</ORDER>
</COMMANDSTATUS>
</xsl:for-each-group>
</MESSAGES>
</xsl:template>
</xsl:stylesheet>
Thanks!

Related

How do I copy text generated by my script to another section in my output file?

I need to copy the text generated by the code below to the <spine> area at the end of my output file. How can I accomplish that?
<xsl:attribute name="id">
<xsl:text>ppi</xsl:text>
<xsl:number format="0000" level="any"/>
</xsl:attribute>
This is my input:
<unit>
<chapter>
<exhibit path="chapter001/chapter01_reader01.html"/>
<exhibit path="chapter001/chapter01_reader02.html"/>
</chapter>
</unit>
This my desired output:
<manifest>
<item id="ppi0001" href="chapter001/chapter01_reader01.html" media-type="application/xhtml+xml"/>
<item id="ppi0002" href="chapter001/chapter01_reader02.html" media-type="application/xhtml+xml"/>
</manifest>
<spine>
<itemref idref="ppi0001" />
<itemref idref="ppi0002" />
</spine>
Here's my full script. I'm stuck at the line where it says <!--ID Generated in the "exhibit" template-->
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns="http://www.idpf.org/2007/opf">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/">
<xsl:element name="package" namespace="http://www.idpf.org/2007/opf">
<xsl:attribute name="unique-identifier">pub-id</xsl:attribute>
<xsl:attribute name="version">3.0</xsl:attribute>
<manifest>
<xsl:apply-templates select="//exhibit" />
</manifest>
<spine>
<xsl:element name="itemref">
<xsl:attribute name="idref">
<!--ID Generated in the "exhibit" template-->
</xsl:attribute>
</xsl:element>
</spine>
</xsl:element>
</xsl:template>
<xsl:template match="exhibit">
<xsl:element name="item">
<xsl:variable name="count" select="position()"/>
<xsl:attribute name="id">
<xsl:text>ppi</xsl:text>
<xsl:number format="0000" level="any"/>
</xsl:attribute>
<xsl:attribute name="href">
<xsl:value-of select="#path" />
</xsl:attribute>
<xsl:attribute name="media-type">
<xsl:text>application/xhtml+xml</xsl:text>
</xsl:attribute>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
Thanks!
Here's a way you could accomplish this :
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns="http://www.idpf.org/2007/opf">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/">
<xsl:element name="package" namespace="http://www.idpf.org/2007/opf">
<xsl:attribute name="unique-identifier">pub-id</xsl:attribute>
<xsl:attribute name="version">3.0</xsl:attribute>
<manifest>
<xsl:apply-templates select="//exhibit" />
</manifest>
<spine>
<xsl:apply-templates select="//exhibit" mode="spine"/>
</spine>
</xsl:element>
</xsl:template>
<xsl:template match="exhibit">
<xsl:element name="item">
<xsl:variable name="count" select="position()"/>
<xsl:attribute name="id">
<xsl:text>ppi</xsl:text>
<xsl:number format="0000" level="any"/>
</xsl:attribute>
<xsl:attribute name="href">
<xsl:value-of select="#path" />
</xsl:attribute>
<xsl:attribute name="media-type">
<xsl:text>application/xhtml+xml</xsl:text>
</xsl:attribute>
</xsl:element>
</xsl:template>
<xsl:template match="exhibit" mode="spine">
<xsl:element name="itemref">
<xsl:variable name="count" select="position()"/>
<xsl:attribute name="idref">
<xsl:text>ppi</xsl:text>
<xsl:number format="0000" level="any"/>
</xsl:attribute>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
See it working here : https://xsltfiddle.liberty-development.net/6qaHaRT
Since you are reusing the #id for the itemref's I would use this:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns="http://www.idpf.org/2007/opf"
version="1.0">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/">
<xsl:element name="package" namespace="http://www.idpf.org/2007/opf">
<xsl:attribute name="unique-identifier">pub-id</xsl:attribute>
<xsl:attribute name="version">3.0</xsl:attribute>
<!-- first store the items in a variable -->
<xsl:variable name="items">
<!-- if you know the xpath to the exhibit, it is better for performence to make that explicit -->
<xsl:apply-templates select="unit/chapter/exhibit" />
</xsl:variable>
<manifest>
<!-- then copy those $items into the output -->
<xsl:copy-of select="$items"/>
</manifest>
<spine>
<!-- then reuse the $items for creating then itemref's-->
<xsl:apply-templates select="$items/*/#id" mode="spine"/>
</spine>
</xsl:element>
</xsl:template>
<xsl:template match="exhibit">
<xsl:element name="item">
<xsl:variable name="count" select="position()"/>
<xsl:attribute name="id">
<xsl:text>ppi</xsl:text>
<xsl:number format="0000" level="any"/>
</xsl:attribute>
<xsl:attribute name="href">
<xsl:value-of select="#path" />
</xsl:attribute>
<xsl:attribute name="media-type">
<xsl:text>application/xhtml+xml</xsl:text>
</xsl:attribute>
</xsl:element>
</xsl:template>
<xsl:template match="#id" mode="spine">
<xsl:element name="itemref">
<xsl:attribute name="idref">
<xsl:value-of select="."/>
</xsl:attribute>
</xsl:element>
</xsl:template>
</xsl:stylesheet>

How to concatenate child elements under one parent node by | separator in XSLT?

I have an issue while concatenating all the the child elements under parent element.
Here is the source data
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<PurchaseOrder id="abc">
<attr attr-name="A">
<new-value>123</new-value>
</attr>
<attr attr-name="B">
<new-value>99</new-value>
</attr>
<attr attr-name="B">
<new-value>77</new-value>
</attr>
<attr attr-name="C">
<new-value>1</new-value>
<new-value>2</new-value>
<new-value>3</new-value>
<new-value>4</new-value>
<new-value>5</new-value>
<new-value>6</new-value>
<new-value>7</new-value>
<new-value>8</new-value>
</attr>
<attr attr-name="D">
<new-value>
<child1>567</child1>
<child2>2</child2>
</new-value>
</attr>
<attr attr-name="E">
<new-value>
<child3>890</child3>
<child4>3</child4>
</new-value>
</attr>
</PurchaseOrder>
XSLT Transformation used
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:ns0="urn:demo:PurchaseOrder">
<xsl:output method="xml" indent="yes" />
<xsl:strip-space elements="*" />
<xsl:key name="keyAttrName" match="attr" use="#attr-name" />
<xsl:template match="PurchaseOrder">
<ns0:PurchaseOrderMSG>>
<Orders>
<Order id="{#id}">
<xsl:for-each select="attr[generate-id() = generate-id(key('keyAttrName', #attr-name)[1])]">
<xsl:variable name="nodeName" select="#attr-name" />
<xsl:choose>
<xsl:when test="key('keyAttrName', #attr-name)/new-value/*/node()">
<xsl:for-each select="new-value">
<xsl:element name="{$nodeName}">
<xsl:copy-of select="*" />
</xsl:element>
</xsl:for-each>
</xsl:when>
<xsl:when test="key('keyAttrName', #attr-name)/new-value/materials/material">
<xsl:for-each select="key('keyAttrName', #attr-name)">
<xsl:element name="{$nodeName}">
<xsl:copy-of select="attr" />
</xsl:element>
</xsl:for-each>
</xsl:when>
<xsl:otherwise>
<xsl:for-each select="key('keyAttrName', #attr-name)">
<xsl:element name="{$nodeName}">
<xsl:if test="position()!=1">
<ns0:text>|</ns0:text>
</xsl:if>
<xsl:value-of select="."/>
</xsl:element>
</xsl:for-each>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</Order>
</Orders>
</ns0:PurchaseOrderMSG>
</xsl:template>
</xsl:stylesheet>
The expected Result should be
<?xml version="1.0" encoding="UTF-8"?>
<ns0:PurchaseOrderMSG xmlns:ns0="urn:demo:PurchaseOrder">
<Orders>
<Order>
<A>123</A>
<B>99</B>
<B>77</B>
<C>1|2|3|4|5|6|7|8</C>
<D>
<child1>567</child1>
<child2>2</child2>
</D>
<E>
<child3>890</child3>
<child4>3</child4>
</E>
</Order>
</Orders>
</ns0:PurchaseOrderMSG>
The output that is coming with the XSLT I have used is as below. Separator logic is not working.
<?xml version="1.0" encoding="UTF-8"?>
<ns0:PurchaseOrderMSG xmlns:ns0="urn:demo:PurchaseOrder">
<Orders>
<Order>
<A>123</A>
<B>99</B>
<B>77</B>
<C>12345678</C>
<D>
<child1>567</child1>
<child2>2</child2>
</D>
<E>
<child3>890</child3>
<child4>3</child4>
</E>
</Order>
</Orders>
</ns0:PurchaseOrderMSG>
I have tried logic mentioned in some blogs but no luck
XSLT merging/concatenating values of siblings nodes of same name into single node
Concatenate several child items into one child item using XSLT
You need to modify the <xsl:otherwise> condition to handle the values in <new-value> node as below. If the count of <new-value> child nodes is > 1, then perform the concatenation using the separator else just output the value as is.
<xsl:otherwise>
<xsl:for-each select="key('keyAttrName', #attr-name)">
<xsl:element name="{$nodeName}">
<xsl:choose>
<xsl:when test="count(new-value) > 1">
<xsl:for-each select="new-value">
<xsl:value-of select="." />
<xsl:if test="position() != last()">
<xsl:value-of select="'|'" />
</xsl:if>
</xsl:for-each>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="." />
</xsl:otherwise>
</xsl:choose>
</xsl:element>
</xsl:for-each>
</xsl:otherwise>
The complete XSLT and the output is as below.
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:ns0="urn:demo:PurchaseOrder">
<xsl:output method="xml" indent="yes" />
<xsl:strip-space elements="*" />
<xsl:key name="keyAttrName" match="attr" use="#attr-name" />
<xsl:template match="PurchaseOrder">
<ns0:PurchaseOrderMSG>
<Orders>
<Order id="{#id}">
<xsl:for-each select="attr[generate-id() = generate-id(key('keyAttrName', #attr-name)[1])]">
<xsl:variable name="nodeName" select="#attr-name" />
<xsl:choose>
<xsl:when test="key('keyAttrName', #attr-name)/new-value/*/node()">
<xsl:for-each select="new-value">
<xsl:element name="{$nodeName}">
<xsl:copy-of select="*" />
</xsl:element>
</xsl:for-each>
</xsl:when>
<xsl:when test="key('keyAttrName', #attr-name)/new-value/materials/material">
<xsl:for-each select="key('keyAttrName', #attr-name)">
<xsl:element name="{$nodeName}">
<xsl:copy-of select="attr" />
</xsl:element>
</xsl:for-each>
</xsl:when>
<xsl:otherwise>
<xsl:for-each select="key('keyAttrName', #attr-name)">
<xsl:element name="{$nodeName}">
<xsl:choose>
<xsl:when test="count(new-value) > 1">
<xsl:for-each select="new-value">
<xsl:value-of select="." />
<xsl:if test="position() != last()">
<xsl:value-of select="'|'" />
</xsl:if>
</xsl:for-each>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="." />
</xsl:otherwise>
</xsl:choose>
</xsl:element>
</xsl:for-each>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</Order>
</Orders>
</ns0:PurchaseOrderMSG>
</xsl:template>
</xsl:stylesheet>
Output
<ns0:PurchaseOrderMSG xmlns:ns0="urn:demo:PurchaseOrder">
<Orders>
<Order id="abc">
<A>123</A>
<B>99</B>
<B>77</B>
<C>1|2|3|4|5|6|7|8</C>
<D>
<child1>567</child1>
<child2>2</child2>
</D>
<E>
<child3>890</child3>
<child4>3</child4>
</E>
</Order>
</Orders>
</ns0:PurchaseOrderMSG>

Combine space-separated attributes in XSLT

The transform I'm working on mergers two templates that has attributes that are space-separated.
An example would be:
<document template_id="1">
<header class="class1 class2" />
</document>
<document template_id="2">
<header class="class3 class4" />
</document>
And after the transform I want it to be like this:
<document>
<header class="class1 class2 class3 class4" />
</document>
How to achieve this?
I have tried (writing from memory):
<xsl:template match="/">
<header>
<xsl:attribute name="class">
<xsl:for-each select=".//header">
<xsl:value-of select="#class"/>
</xsl:for-each>
</xsl:attribute>
</header>
</xsl:template>
But that appends them all together, but I need them separated... and would be awesome if uniqued as well.
Thank you
Try this template, which simply adds a space before all the headers, apart from the first
<xsl:template match="/">
<header>
<xsl:attribute name="class">
<xsl:for-each select=".//header">
<xsl:if test="position() > 1">
<xsl:text> </xsl:text>
</xsl:if>
<xsl:value-of select="#class"/>
</xsl:for-each>
</xsl:attribute>
</header>
</xsl:template>
If you want your classes without repetitions, then use the following XSLT:
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform 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" encoding="UTF-8" indent="yes" />
<xsl:template match="/">
<document>
<xsl:variable name="cls1">
<xsl:for-each select=".//header/#class">
<xsl:call-template name="tokenize">
<xsl:with-param name="txt" select="."/>
</xsl:call-template>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="cls" select="exsl:node-set($cls1)"/>
<header>
<xsl:attribute name="class">
<xsl:for-each select="$cls/*[not(preceding-sibling::* = .)]">
<xsl:value-of select="."/>
<xsl:if test="position() < last()">
<xsl:text> </xsl:text>
</xsl:if>
</xsl:for-each>
</xsl:attribute>
</header>
</document>
</xsl:template>
<xsl:template name="tokenize">
<xsl:param name="txt"/>
<xsl:if test="$txt">
<xsl:if test="contains($txt, ' ')">
<cls>
<xsl:value-of select="substring-before($txt, ' ')"/>
</cls>
<xsl:call-template name="tokenize">
<xsl:with-param name="txt" select="substring-after($txt, ' ')"/>
</xsl:call-template>
</xsl:if>
<xsl:if test="not(contains($txt, ' '))">
<cls>
<xsl:value-of select="$txt"/>
</cls>
</xsl:if>
</xsl:if>
</xsl:template>
</xsl:transform>
In XSLT 1.0 it is much more difficult than in XSLT 2.0, but as you see,
it is possible.
Edit
A revised version using Muenchian grouping (inspired by comment from Michael):
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform 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" encoding="UTF-8" indent="yes" />
<xsl:variable name="cls1">
<xsl:for-each select=".//header/#class">
<xsl:call-template name="tokenize">
<xsl:with-param name="txt" select="."/>
</xsl:call-template>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="cls2" select="exsl:node-set($cls1)"/>
<xsl:key name="classKey" match="cls" use="."/>
<xsl:template match="/">
<document>
<header>
<xsl:attribute name="class">
<xsl:for-each select="$cls2/*[generate-id() = generate-id(key('classKey', .)[1])]">
<xsl:value-of select="."/>
<xsl:if test="position() < last()">
<xsl:text> </xsl:text>
</xsl:if>
</xsl:for-each>
</xsl:attribute>
</header>
</document>
</xsl:template>
<xsl:template name="tokenize">
<xsl:param name="txt"/>
<xsl:if test="$txt">
<xsl:if test="contains($txt, ' ')">
<cls>
<xsl:value-of select="substring-before($txt, ' ')"/>
</cls>
<xsl:call-template name="tokenize">
<xsl:with-param name="txt" select="substring-after($txt, ' ')"/>
</xsl:call-template>
</xsl:if>
<xsl:if test="not(contains($txt, ' '))">
<cls>
<xsl:value-of select="$txt"/>
</cls>
</xsl:if>
</xsl:if>
</xsl:template>
</xsl:transform>

xslt code choose between maximum of 2 values

I have this xml file.
<?xml version="1.0" encoding="utf-8"?>
<racine>
<index>
<Parent nom="00000002" Name="" Address="" />
<Meter numSerie="00000002" />
<arrêté dateArrêté="28/02/2015 00:00:00">
<ValeurIndex Libelle="Val1">23.334</ValeurIndex>
<ValeurIndex Libelle="Val2">5.186</ValeurIndex>
<ValeurIndex Libelle="Val3">2.79</ValeurIndex>
</arrêté>
</index>
</racine>
and I would like to convert it in a txt file, where I need to add a new row with the value of max between Val1 and Val2, as the example below.
Val1,23.334
Val2,5.186
Val3,2.79
MaxVal1Vl2,23.334
What I created it was something like:
<xsl:choose>
<xsl:when test="#Libelle = 'Val1'">Val1</xsl:when>
<xsl:when test="#Libelle = 'Val2'">Val2</xsl:when>
<xsl:when test="#Libelle = 'Val3'">Val3</xsl:when>
</xsl:choose>
<xsl:text>,</xsl:text>
<xsl:value-of select="."/>
<xsl:text>
</xsl:text>
how can I add the 4 row being the max of Val1 and Val2?
Thanks
Try:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" encoding="utf-8"/>
<xsl:template match="/racine">
<xsl:for-each select="index/arrêté/ValeurIndex">
<xsl:value-of select="#Libelle"/>
<xsl:text>,</xsl:text>
<xsl:value-of select="."/>
<xsl:text>
</xsl:text>
</xsl:for-each>
<xsl:variable name="v1" select="index/arrêté/ValeurIndex[#Libelle='Val1']" />
<xsl:variable name="v2" select="index/arrêté/ValeurIndex[#Libelle='Val2']" />
<xsl:text>MaxVal1Vl2,</xsl:text>
<xsl:choose>
<xsl:when test="$v1 > $v2">
<xsl:value-of select="$v1"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$v2"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>

XSLT: How to change attribute names as well?

This code is able to change elements to attribute key/value pairs. I want to change the attribute names in the same logic. How to do that?
Input:
<TESTSEQUENCE>
<TESTCASESTARTTIME>2014-05-02 16:02:38</TESTCASESTARTTIME>
<TESTCASENAME>TRAFFIC_MIXED_COLD_TP-A15 </TESTCASENAME>
<SCRIPTFILENAME>/proj/cpptemp/dt/network_repo/398/int/IEXS_CRX901185_1/iov/cbm1-3_mct/tc/Traffic.tcl</SCRIPTFILENAME>
<TXTLOGFILENAME>TRAFFIC_MIXED_COLD_TP-A15/TRAFFIC_MIXED_COLD_TP-A15.txt</TXTLOGFILENAME>
<HTMLLOGFILENAME>TRAFFIC_MIXED_COLD_TP-A15.html</HTMLLOGFILENAME>
<XMLLOGFILENAME>TRAFFIC_MIXED_COLD_TP-A15.xml</XMLLOGFILENAME>
<STARTTIME>1399039358</STARTTIME>
<TOTALCMDS>27</TOTALCMDS>
<NOTOKIDS>27</NOTOKIDS>
<NOTOKCMDS>{CETP ABORT}</NOTOKCMDS>
<FAILURESTRINGS>1=Failed,</FAILURESTRINGS>
<RESULT>ABORTED</RESULT>
<FINISHTIME>1399039672</FINISHTIME>
</TESTSEQUENCE>
Stylesheet:
<xsl:template match="TESTSEQUENCE">
<testcase>
<xsl:for-each select="*">
<xsl:attribute name="{name()}">
<xsl:value-of select="text()"/>
</xsl:attribute>
</xsl:for-each>
</testcase>
</xsl:template>
Output:
<testcase time="2014-05-02 16:02:38" name="TRAFFIC_MIXED_COLD_TP-A15 " scriptfilename="mct/tc/Traffic.tcl" txtlogfilename="TRAFFIC_MIXED_COLD_TP-A15.txt" url="TRAFFIC_MIXED_COLD_TP-A15.html" xmllogfilename="TRAFFIC_MIXED_COLD_TP-A15.xml" starttime="1399039358" target_connect="" totalcmds="27" notokids="27" notokcmds="{CETP ABORT}" failurestrings="1=Failed," status="ABORTED" finishtime="1399039672">
</testcase>
Please enhance your code:
<xsl:template match="TESTSEQUENCE">
<testcase>
<xsl:for-each select="*">
<xsl:attribute name="{concat('ABC-',name())}">
<xsl:value-of select="text()"/>
</xsl:attribute>
</xsl:for-each>
</testcase>
</xsl:template>
OR
<xsl:template match="TESTSEQUENCE">
<testcase>
<xsl:for-each select="*">
<xsl:attribute name="{if (name()) = 'TESTCASESTARTTIME' then 'TESTCASES' else ()}">
<xsl:value-of select="text()"/>
</xsl:attribute>
</xsl:for-each>
</testcase>
</xsl:template>
TESTCASESTARTTIME to time TESTCASENAME to name HTMLLOGFILENAME to url
RESULT to status I need these 4 attributes to be changed
You need to exclude these 4 elements from the generic xsl:for-each applied to the other elements, and handle them explicitly:
XSLT 1.0
<?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" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/">
<xsl:for-each select="TESTSEQUENCE">
<testcase>
<xsl:for-each select="*[not (self::TESTCASESTARTTIME or self::TESTCASENAME or self::HTMLLOGFILENAME or self::RESULT)]">
<xsl:attribute name="{name()}">
<xsl:value-of select="."/>
</xsl:attribute>
</xsl:for-each>
<xsl:attribute name="time">
<xsl:value-of select="TESTCASESTARTTIME"/>
</xsl:attribute>
<xsl:attribute name="name">
<xsl:value-of select="TESTCASENAME"/>
</xsl:attribute>
<xsl:attribute name="url">
<xsl:value-of select="HTMLLOGFILENAME"/>
</xsl:attribute>
<xsl:attribute name="status">
<xsl:value-of select="RESULT"/>
</xsl:attribute>
</testcase>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Applied to your example input of:
<TESTSEQUENCE>
<TESTCASESTARTTIME>2014-05-02 16:02:38</TESTCASESTARTTIME>
<TESTCASENAME>TRAFFIC_MIXED_COLD_TP-A15</TESTCASENAME>
<SCRIPTFILENAME>/proj/cpptemp/dt/network_repo/398/int/IEXS_CRX901185_1/iov/cbm1-3_mct/tc/Traffic.tcl</SCRIPTFILENAME>
<TXTLOGFILENAME>TRAFFIC_MIXED_COLD_TP-A15/TRAFFIC_MIXED_COLD_TP-A15.txt</TXTLOGFILENAME>
<HTMLLOGFILENAME>TRAFFIC_MIXED_COLD_TP-A15.html</HTMLLOGFILENAME>
<XMLLOGFILENAME>TRAFFIC_MIXED_COLD_TP-A15.xml</XMLLOGFILENAME>
<STARTTIME>1399039358</STARTTIME>
<TOTALCMDS>27</TOTALCMDS>
<NOTOKIDS>27</NOTOKIDS>
<NOTOKCMDS>{CETP ABORT}</NOTOKCMDS>
<FAILURESTRINGS>1=Failed,</FAILURESTRINGS>
<RESULT>ABORTED</RESULT>
<FINISHTIME>1399039672</FINISHTIME>
</TESTSEQUENCE>
the result is:
<?xml version="1.0" encoding="UTF-8"?>
<testcase SCRIPTFILENAME="/proj/cpptemp/dt/network_repo/398/int/IEXS_CRX901185_1/iov/cbm1-3_mct/tc/Traffic.tcl" TXTLOGFILENAME="TRAFFIC_MIXED_COLD_TP-A15/TRAFFIC_MIXED_COLD_TP-A15.txt" XMLLOGFILENAME="TRAFFIC_MIXED_COLD_TP-A15.xml" STARTTIME="1399039358" TOTALCMDS="27" NOTOKIDS="27" NOTOKCMDS="{CETP ABORT}" FAILURESTRINGS="1=Failed," FINISHTIME="1399039672" time="2014-05-02 16:02:38" name="TRAFFIC_MIXED_COLD_TP-A15" url="TRAFFIC_MIXED_COLD_TP-A15.html" status="ABORTED"/>