Handling Multiple For Loop in XSLT - xslt

Dear Experts,
I have to use multiple for-loops in XSLT.
Presently with my XSLT I generate output with extra nodes at 'GroupDetail'.
Input Request
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
xmlns:v1="http://xmldefs. ag.com/Applications/eer/V1" xmlns:wsa="http://www.w3.org/2005/08/addressing">
<SOAP-ENV:Header>
<wsa:messageId>04383-34380-3439939</wsa:messageId>
</SOAP-ENV:Header>
<SOAP-ENV:Body>
<v1:ProcessDistr xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<v1:Group xmlns:ns2="http://xmldefs.ag.com/DD/Commons">
<v1:GroupID>437848</v1:GroupID>
<v1:GroupDetails>
<v1:GroupDetail>
<v1:language>De</v1:language>
<v1:description>Deutsch</v1:description>
</v1:GroupDetail>
<v1:GroupDetail>
<v1:language>En</v1:language>
<v1:description>English</v1:description>
</v1:GroupDetail>
</v1:GroupDetails>
<v1:Status>true</v1:Status>
<v1:Parent>45434554</v1:Parent>
</v1:Group>
<v1:Group xmlns:ns2="http://xmldefs.ag.com/DD/Commons">
<v1:GroupID>437849</v1:GroupID>
<v1:GroupDetails>
<v1:GroupDetail>
<v1:language>Tu</v1:language>
<v1:description>Turkish</v1:description>
</v1:GroupDetail>
<v1:GroupDetail>
<v1:language>Fr</v1:language>
<v1:description>French</v1:description>
</v1:GroupDetail>
</v1:GroupDetails>
<v1:Status>true</v1:Status>
<v1:Parent>45434555</v1:Parent>
</v1:Group>
</v1:ProcessDistr>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
Output received with another set of Group details and also 'messageId' is missing.
Output received:
<?xml version="1.0" encoding="UTF-8"?>
<ProcessDistr >
<Group >
<GroupID>437848</GroupID>
<GroupDetails>
<GroupDetail>
<language>De</language>
<description>Deutsch</description>
</GroupDetail>
<GroupDetail>
<language>En</language>
<description>English</description>
</GroupDetail>
<GroupDetail>
<language>Tu</language>
<description>Turkish</description>
</GroupDetail>
<GroupDetail>
<language>Fr</language>
<description>French</description>
</GroupDetail>
</GroupDetails>
<Status>true</Status>
<Parent>45434554</Parent>
</Group>
<Group >
<GroupID>437849</GroupID>
<GroupDetails>
<GroupDetail>
<language>De</language>
<description>Deutsch</description>
</GroupDetail>
<GroupDetail>
<language>En</language>
<description>English</description>
</GroupDetail>
<GroupDetail>
<language>Tu</language>
<description>Turkish</description>
</GroupDetail>
<GroupDetail>
<language>Fr</language>
<description>French</description>
</GroupDetail>
</GroupDetails>
<Status>true</Status>
<Parent>45434555</Parent>
</Group>
<messageId/>
</ProcessDistr>
This is XSLT code which I developed
Used XSLT code:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:prof="http://ixult.net/ProfileExchange"
xmlns:sap="http://www.sap.com/sapxsl" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsltc="http://xml.apache.org/xalan/xsltc"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:v1="http://xmldefs. ag.com/Applications/eer/V1"
xmlns:vwsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" xmlns:ns2="http://xmldefs.vag.com/DD/Commons"
exclude-result-prefixes="vwsu v1 ns2 xsi wsa" xmlns:wsa="http://www.w3.org/2005/08/addressing">
<!-- Output -->
<xsl:output encoding="UTF-8" indent="yes" method="xml" omit-xml-declaration="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<xsl:element name="ProcessDistr">
<xsl:for-each select="//soap:Body/v1:ProcessDistr/v1:Group">
<xsl:element name="Group">
<xsl:element name="GroupID"><xsl:value-of select="v1:GroupID"/></xsl:element>
<xsl:element name="GroupDetails">
<xsl:for-each select="//v1:GroupDetails/v1:GroupDetail">
<xsl:element name="GroupDetail">
<xsl:element name="language"><xsl:value-of select="v1:language"/></xsl:element>
<xsl:element name="Description">
<xsl:value-of select="v1:Description"/></xsl:element>
</xsl:element>
</xsl:for-each>
</xsl:element>
<xsl:element name="Status"><xsl:value-of select="v1:Status"/></xsl:element>
<xsl:element name="Parent"><xsl:value-of select="v1:Parent"/></xsl:element>
</xsl:element>
</xsl:for-each>
<xsl:element name="messageId"><xsl:value-of select="wsa:messageID"/>
</xsl:element>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
Output expected:
<?xml version="1.0" encoding="UTF-8"?>
<ProcessDistr >
<Group >
<GroupID>437848</GroupID>
<GroupDetails>
<GroupDetail>
<language>De</language>
<description>Deutsch</description>
</GroupDetail>
<GroupDetail>
<language>En</language>
<description>English</description>
</GroupDetail>
</GroupDetails>
<Status>true</Status>
<Parent>45434554</Parent>
</Group>
<Group >
<GroupID>437849</GroupID>
<GroupDetails>
<GroupDetail>
<language>Tu</language>
<description>Turkish</description>
</GroupDetail>
<GroupDetail>
<language>Fr</language>
<description>French</description>
</GroupDetail>
</GroupDetails>
<Status>true</Status>
<Parent>45434555</Parent>
</Group>
<messageId>04383-34380-3439939</messageId>
</ProcessDistr>
Please help me on this code
Thank you very much.
With Best Regards,
Sateesh N

The main problem of the provided code is not on line 20, although fixing this helps get the wanted output.
The main problem is that the code doesn't make use of the powerful XSLT processing model, using which the solution can be expressed without any <xsl:for-each> instructions. Also, the code can be shrunk and made more compact, understandable and maintainable.
Here is a starter solution (30 lines) that shows how this can be done:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:soap-env="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:v1="http://xmldefs. ag.com/Applications/eer/V1" xmlns:wsa="http://www.w3.org/2005/08/addressing"
exclude-result-prefixes="soap-env wsa v1">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/*">
<xsl:apply-templates select="soap-env:Body"/>
</xsl:template>
<xsl:template match="v1:ProcessDistr">
<ProcessDistr>
<xsl:apply-templates/>
<xsl:apply-templates select="/*/soap-env:Header/wsa:messageId"/>
</ProcessDistr>
</xsl:template>
<xsl:template match="v1:* | wsa:*">
<xsl:element name="{local-name()}">
<xsl:apply-templates select="node()|#*"/>
</xsl:element>
</xsl:template>
<xsl:template match="soap-env:*"><xsl:apply-templates/></xsl:template>
</xsl:stylesheet>
When this transformation is applied on the provided XML document:
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
xmlns:v1="http://xmldefs. ag.com/Applications/eer/V1" xmlns:wsa="http://www.w3.org/2005/08/addressing">
<SOAP-ENV:Header>
<wsa:messageId>04383-34380-3439939</wsa:messageId>
</SOAP-ENV:Header>
<SOAP-ENV:Body>
<v1:ProcessDistr xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<v1:Group xmlns:ns2="http://xmldefs.ag.com/DD/Commons">
<v1:GroupID>437848</v1:GroupID>
<v1:GroupDetails>
<v1:GroupDetail>
<v1:language>De</v1:language>
<v1:description>Deutsch</v1:description>
</v1:GroupDetail>
<v1:GroupDetail>
<v1:language>En</v1:language>
<v1:description>English</v1:description>
</v1:GroupDetail>
</v1:GroupDetails>
<v1:Status>true</v1:Status>
<v1:Parent>45434554</v1:Parent>
</v1:Group>
<v1:Group xmlns:ns2="http://xmldefs.ag.com/DD/Commons">
<v1:GroupID>437849</v1:GroupID>
<v1:GroupDetails>
<v1:GroupDetail>
<v1:language>Tu</v1:language>
<v1:description>Turkish</v1:description>
</v1:GroupDetail>
<v1:GroupDetail>
<v1:language>Fr</v1:language>
<v1:description>French</v1:description>
</v1:GroupDetail>
</v1:GroupDetails>
<v1:Status>true</v1:Status>
<v1:Parent>45434555</v1:Parent>
</v1:Group>
</v1:ProcessDistr>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
the wanted, correct result is produced:
<ProcessDistr>
<Group>
<GroupID>437848</GroupID>
<GroupDetails>
<GroupDetail>
<language>De</language>
<description>Deutsch</description>
</GroupDetail>
<GroupDetail>
<language>En</language>
<description>English</description>
</GroupDetail>
</GroupDetails>
<Status>true</Status>
<Parent>45434554</Parent>
</Group>
<Group>
<GroupID>437849</GroupID>
<GroupDetails>
<GroupDetail>
<language>Tu</language>
<description>Turkish</description>
</GroupDetail>
<GroupDetail>
<language>Fr</language>
<description>French</description>
</GroupDetail>
</GroupDetails>
<Status>true</Status>
<Parent>45434555</Parent>
</Group>
<messageId>04383-34380-3439939</messageId>
</ProcessDistr>
XSLT is a wonderful language and when its full power is used it gives us short and elegant solutions. There are good learning resources waiting to be uncovered.

The main problem with your XSLT is this (line 20):
<xsl:for-each select="//v1:GroupDetails/v1:GroupDetail">
A path that starts with // selects all descendants of the root node, regardless of the current context. You only want to process the descendants of the current v1:Group, so you need to change it to:
<xsl:for-each select="v1:GroupDetails/v1:GroupDetail">
Note also that XML is case-sensitive:
<xsl:value-of select="v1:Description"/>
will not return the value of an element named v1:description.
I would also recommend using literal result elements instead of the xsl:element instruction. Use xsl:element when the name of the element needs to be determined at runtime.

Related

XSLT 1.0: How to combine and sum the fields of children in records based on having the same id field?

I have the following:
<ns0:tXML>
<Message>
<Report>
<Page>
<PageID>01</PageID>
<PageDetail>
<PageName>11</PageName>
<Totals>
<Num>10</Num>
</Totals>
</PageDetail>
<PageDetail>
<PageName>11</PageName>
<Totals>
<Num>5</Num>
</Totals>
</PageDetail>
</Page>
<Page>
<PageID>02</PageID>
<PageDetail>
<PageName>12</PageName>
<Totals>
<Num>10</Num>
</Totals>
</PageDetail>
<PageDetail>
<PageName>12</PageName>
<Totals>
<Num>3</Num>
</Totals>
</PageDetail>
</Page>
</Report>
</Message>
</ns0:tXML>
I want to make the output so that PageDetails are combined for each Page as long as their PageName and PageID are the same, including summing the values of the combined.
Output Wanted:
<ns0:tXML>
<Message>
<Report>
<Page>
<PageID>01</PageID>
<PageDetail>
<PageName>11</PageName>
<Totals>
<Num>15</Num>
</Totals>
</PageDetail>
</Page>
<Page>
<PageID>02</PageID>
<PageDetail>
<PageName>12</PageName>
<Totals>
<Num>13</Num>
</Totals>
</PageDetail>
</Page>
</Report>
</Message>
</ns0:tXML>
How would I go about it? All efforts with using keys and playing with templates has led to cases where only one of the Pages got created, or it combined all the Pages no matter where they were on the xml, showing that I was likely trying to do an all apply to it rather than sticking to the current context.
Let's start from a little correction to your source. It should include
the namespace specification:
<ns0:tXML xmlns:ns0="urn.dummy.com">
otherwise there is reported the following error:
The prefix "ns0" for element "ns0:tXML" is not bound.
One of possible solutions is to use the following script:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:transform version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ns0="urn.dummy.com">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:key name="Pd" match="PageDetail" use="concat(../PageID, '|', PageName)"/>
<xsl:template match="Page">
<xsl:copy>
<xsl:copy-of select="PageID"/>
<xsl:for-each select="PageDetail[generate-id()=generate-id(key('Pd',
concat(../PageID,'|', PageName))[1])]">
<xsl:variable name="kk" select="concat(../PageID,'|', PageName)"/>
<xsl:copy>
<xsl:copy-of select="PageName"/>
<xsl:element name="Totals">
<xsl:element name="Num">
<xsl:value-of select="sum(key('Pd', $kk)/Totals/Num)"/>
</xsl:element>
</xsl:element>
</xsl:copy>
</xsl:for-each>
</xsl:copy>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy><xsl:apply-templates select="#*|node()"/></xsl:copy>
</xsl:template>
</xsl:transform>
For a working example, generating just your expected result,
see: http://xsltransform.net/93YRmgt

Not select a particular node child Elements in xslt using not

My XML is below. Is it possible to do this in same XSLT?
<response context="XXXX" type="abcd" errorCode="0" >
<output>
<Applicants>
<Applicant>
<IndividualEmployments/>
<Addresses/>
</Applicant>
</Applicants>
<Assets>
<Asset id="12345"></Asset>
</Assets>
<Liabilities>
<Liability id="8765"></Liability>
</Liabilities>
</output>
Desired output should be like below. I want two response nodes, one with Assets and the other with Liabilities.
<response context="XXXX" type="abcd" errorCode="0">
<output>
<Applicants>
<Applicant>
<IndividualEmployments/>
<Addresses/>
</Applicant>
</Applicants>
<Assets>
<Asset id="12345"></Asset>
</Assets>
</output>
<response context="XXXX" type="abcd" errorCode="0">
<output>
<Applicants>
<Applicant>
<IndividualEmployments/>
<Addresses/>
</Applicant>
</Applicants>
<Liabilities>
<Liability id="8765"></Liability>
</Liabilities>
</output>
You need to process the response element and output it twice, making sure the content is different, for instance by passing a parameter:
<?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:mode on-no-match="shallow-copy"/>
<xsl:template match="response">
<xsl:next-match>
<xsl:with-param name="exclude" tunnel="yes" select="descendant::Liabilities"/>
</xsl:next-match>
<xsl:next-match>
<xsl:with-param name="exclude" tunnel="yes" select="descendant::Assets"/>
</xsl:next-match>
</xsl:template>
<xsl:template match="output">
<xsl:param name="exclude" tunnel="yes"/>
<xsl:copy>
<xsl:apply-templates select="#*, node() except $exclude"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
If needed or wanted you can of course wrap each xsl:next-match I have in an xsl:result-document.

xsl not transforming to desired output

I am trying to replicate the example located here Click Here
However, I am unable to achieve the desired results.
The output is not at all getting generated as per desired.
The input I have used :
<?xml version="1.0" encoding="UTF-8" ?>
<Order xmlns="http://www.book.org">
<Bundle>
<authors>
<author>
<authorId>100</authorId>
<authorName>Kathisiera</authorName>
</author>
<author>
<authorId>200</authorId>
<authorName>Bates</authorName>
</author>
<author>
<authorId>300</authorId>
<authorName>Gavin King</authorName>
</author>
</authors>
<books>
<book>
<orderId>1111</orderId>
<bookName>Head First Java</bookName>
<bookAuthorId>100</bookAuthorId>
</book>
<book>
<orderId>5555</orderId>
<bookName>Head First Servlets</bookName>
<bookAuthorId>200</bookAuthorId>
</book>
<book>
<orderId>1111</orderId>
<bookName>Hibernate In Action</bookName>
<bookAuthorId>300</bookAuthorId>
</book>
</books>
</Bundle>
</Order>
**The Schema I have used in for my transformation**
<!-- begin snippet: js hide: false console: true babel: false -->
The XSLT I am using for my transformation :
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0"
xmlns:socket="http://www.oracle.com/XSL/Transform/java/oracle.tip.adapter.socket.ProtocolTranslator"
xmlns:oracle-xsl-mapper="http://www.oracle.com/xsl/mapper/schemas"
xmlns:dvm="http://www.oracle.com/XSL/Transform/java/oracle.tip.dvm.LookupValue"
xmlns:mhdr="http://www.oracle.com/XSL/Transform/java/oracle.tip.mediator.service.common.functions.MediatorExtnFunction"
xmlns:oraxsl="http://www.oracle.com/XSL/Transform/java"
xmlns:oraext="http://www.oracle.com/XSL/Transform/java/oracle.tip.pc.services.functions.ExtFunc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xp20="http://www.oracle.com/XSL/Transform/java/oracle.tip.pc.services.functions.Xpath20"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xref="http://www.oracle.com/XSL/Transform/java/oracle.tip.xref.xpath.XRefXPathFunctions"
xmlns:ns0="http://www.book.org"
exclude-result-prefixes="oracle-xsl-mapper xsi xsd xsl ns0 socket dvm mhdr oraxsl oraext xp20 xref">
<oracle-xsl-mapper:schema>
<!--SPECIFICATION OF MAP SOURCES AND TARGETS, DO NOT MODIFY.-->
<oracle-xsl-mapper:mapSources>
<oracle-xsl-mapper:source type="XSD">
<oracle-xsl-mapper:schema location="../Schemas/BooksOrder.xsd"/>
<oracle-xsl-mapper:rootElement name="Order" namespace="http://www.book.org"/>
</oracle-xsl-mapper:source>
</oracle-xsl-mapper:mapSources>
<oracle-xsl-mapper:mapTargets>
<oracle-xsl-mapper:target type="XSD">
<oracle-xsl-mapper:schema location="../Schemas/BooksOrder.xsd"/>
<oracle-xsl-mapper:rootElement name="Order" namespace="http://www.book.org"/>
</oracle-xsl-mapper:target>
</oracle-xsl-mapper:mapTargets>
<!--GENERATED BY ORACLE XSL MAPPER 12.2.1.2.0(XSLT Build 161003.0739.0018) AT [THU JAN 23 13:13:32 IST 2020].-->
</oracle-xsl-mapper:schema>
<!--User Editing allowed BELOW this line - DO NOT DELETE THIS LINE-->
<xsl:key name="k" match="ns0:Order/ns0:Bundle/ns0:books/ns0:book" use="ns0:orderId"/>
<xsl:key name="a" match="ns0:Order/ns0:Bundle/ns0:books/ns0:author" use="ns0:authorId"/>
<xsl:template match="/ns0:Order">
<xsl:copy>
<xsl:apply-templates select="//ns0:books"/>
</xsl:copy>
</xsl:template>
<xsl:template match="//ns0:books">
<xsl:apply-templates select="ns0:book[generate-id(.) = generate-id(key('k', ns0:orderId))]"/>
</xsl:template>
<xsl:template match="ns0:book">
<Bundle>
<authors>
<xsl:apply-templates select="key('a', string(key('k', ns0:orderId)/ns0:bookAuthorId ))" />
</authors>
<books>
<xsl:copy-of select="key('k', ns0:orderId)"/>
</books>
</Bundle>
</xsl:template>
<xsl:template match="ns0:author">
<xsl:copy-of select="."/>
</xsl:template>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
However the output I am getting is :
<?xml version = '1.0' encoding = 'UTF-8'?>
<Order xmlns="http://www.book.org" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.book.org file:/C:/JDeveloper/mywork/SOADevelopment/XsltLearner/SOA/Schemas/BooksOrder.xsd">
<Bundle>
<authors/>
<books>
<book>
<orderId>1111</orderId>
<bookName>Head First Java</bookName>
<bookAuthorId>100</bookAuthorId>
</book>
<book>
<orderId>1111</orderId>
<bookName>Hibernate In Action</bookName>
<bookAuthorId>300</bookAuthorId>
</book>
</books>
</Bundle>
<Bundle>
<authors/>
<books>
<book>
<orderId>5555</orderId>
<bookName>Head First Servlets</bookName>
<bookAuthorId>200</bookAuthorId>
</book>
</books>
</Bundle>
</Order>
The node is not getting populated.
I have tried using copy-of to see the output for the second key but it prints nothing.
Could you please suggest me where I am going wrong.
Any Idea what I am doing incorrect?
Please help
Thanks!
The pattern for the key would <xsl:key name="a" match="ns0:Order/ns0:Bundle/ns0:authors/ns0:author" use="ns0:authorId"/>, as far as I understand your posted document sample. I would also remove the string call in any use of the key function e.g. use <xsl:apply-templates select="key('a', key('k', ns0:orderId)/ns0:bookAuthorId )"/>.

XSLT3 Streaming for appending integer position of node

I have a large XML file to transform using XSLT to append the integer position of sibling node . I’m using XSLT3 streaming and accumulators. I did get desired output. However, my code looks so lengthy that I’m unable to simplify my code. I also need to group same sibling nodes as sibling nodes in the source xml is not grouped always. Could someone help me here please?
Requirement: Sibling nodes such as Positions, Payments etc.. need to be appended with their corresponding integer position such as <Locations1>, <Locations2>etc.<Payments1>,< Payments2> etc..
Now that I have declared two accumulators, each for each sibling nodes. However, my source XML has many sibling nodes.. I’m not sure if I need to use as many accumulators and template match as my sibling nodes.
Input XML
``
<?xml version="1.0" encoding="UTF-8"?>
<Members>
<Member>
<Name>
<fname>Fred</fname>
<id>1234</id>
</Name>
<Locations>
<name>Chicago</name>
<days>3</days>
<hours>24</hours>
</Locations>
<Locations>
<name>Chicago</name>
<days>3</days>
<hours>24</hours>
</Locations>
<Payments>
<amount>1000</amount>
<currency>USD</currency>
</Payments>
<Payments>
<amount>1000</amount>
<currency>USD</currency>
</Payments>
<Locations>
<name>New York</name>
<days>5</days>
<hours>40</hours>
</Locations>
<Locations>
<name>Boston</name>
<days>4</days>
<hours>32</hours>
</Locations>
</Member>
<Member>
<Name>
<fname>Jack</fname>
<id>4567</id>
</Name>
<Locations>
<name>New York</name>
<days>5</days>
<hours>30</hours>
</Locations>
<Locations>
<name>Chicago</name>
<days>3</days>
<hours>24</hours>
</Locations>
<Payments>
<amount>1500</amount>
<currency>USD</currency>
</Payments>
<Payments>
<amount>1800</amount>
<currency>USD</currency>
</Payments>
</Member>
</Members>
``
Expected Output
``
<?xml version="1.0" encoding="UTF-8"?>
<Members>
<Member>
<Name>
<fname>Fred</fname>
<id>1234</id>
</Name>
<Locations_1>
<name>Chicago</name>
<days>3</days>
<hours>24</hours>
</Locations_1>
<Locations_2>
<name>Chicago</name>
<days>3</days>
<hours>24</hours>
</Locations_2>
<Locations_3>
<name>New York</name>
<days>5</days>
<hours>40</hours>
</Locations_3>
<Locations_4>
<name>Boston</name>
<days>4</days>
<hours>32</hours>
</Locations_4>
<Payments_1>
<amount>1000</amount>
<currency>USD</currency>
</Payments_1>
<Payments_2>
<amount>1000</amount>
<currency>USD</currency>
</Payments_2>
</Member>
<Member>
<Name>
<fname>Jack</fname>
<id>4567</id>
</Name>
<Locations_1>
<name>New York</name>
<days>5</days>
<hours>30</hours>
</Locations_1>
<Locations_2>
<name>Chicago</name>
<days>3</days>
<hours>24</hours>
</Locations_2>
<Payments_1>
<amount>1500</amount>
<currency>USD</currency>
</Payments_1>
<Payments_2>
<amount>1800</amount>
<currency>USD</currency>
</Payments_2>
</Member>
</Members>
``
Current code
``
<?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="3.0">
<xsl:output method="xml" indent="yes"/>
<xsl:mode streamable="yes" on-no-match="shallow-copy" use-accumulators="#all"/>
<xsl:accumulator name="loc-count" as="xs:integer" initial-value="0" streamable="yes">
<xsl:accumulator-rule match="Member" select="0"/>
<xsl:accumulator-rule match="Member/Locations" select="$value + 1"/>
</xsl:accumulator>
<xsl:accumulator name="pay-count" as="xs:integer" initial-value="0" streamable="yes">
<xsl:accumulator-rule match="Member" select="0"/>
<xsl:accumulator-rule match="Member/Payments" select="$value + 1"/>
</xsl:accumulator>
<xsl:template match="Locations">
<xsl:element name="Locations_{accumulator-before('loc-count')}">
<xsl:copy-of select="#* | node()"/>
</xsl:element>
</xsl:template>
<xsl:template match="Payments">
<xsl:element name="Payments_{accumulator-before('pay-count')}">
<xsl:copy-of select="#* | node()"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
``
Current Output
<?xml version="1.0" encoding="UTF-8"?>
<Members>
<Member>
<Name>
<fname>Fred</fname>
<id>1234</id>
</Name>
<Locations_1>
<name>Chicago</name>
<days>3</days>
<hours>24</hours>
</Locations_1>
<Locations_2>
<name>Chicago</name>
<days>3</days>
<hours>24</hours>
</Locations_2>
<Payments_1>
<amount>1000</amount>
<currency>USD</currency>
</Payments_1>
<Payments_2>
<amount>1000</amount>
<currency>USD</currency>
</Payments_2>
<Locations_3>
<name>New York</name>
<days>5</days>
<hours>40</hours>
</Locations_3>
<Locations_4>
<name>Boston</name>
<days>4</days>
<hours>32</hours>
</Locations_4>
</Member>
<Member>
<Name>
<fname>Jack</fname>
<id>4567</id>
</Name>
<Locations_1>
<name>New York</name>
<days>5</days>
<hours>30</hours>
</Locations_1>
<Locations_2>
<name>Chicago</name>
<days>3</days>
<hours>24</hours>
</Locations_2>
<Payments_1>
<amount>1500</amount>
<currency>USD</currency>
</Payments_1>
<Payments_2>
<amount>1800</amount>
<currency>USD</currency>
</Payments_2>
</Member>
</Members>
If you want to group the Member child elements by node-name() then I think you need to wrap the xsl:for-each-group into xsl:fork:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:map="http://www.w3.org/2005/xpath-functions/map"
xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="#all" version="3.0">
<xsl:strip-space elements="*"/>
<xsl:output indent="yes"/>
<xsl:mode on-no-match="shallow-copy" streamable="yes" use-accumulators="counters"/>
<xsl:accumulator name="counters" as="map(xs:QName, xs:integer)" initial-value="map{}" streamable="yes">
<xsl:accumulator-rule match="Member" select="map{}"/>
<xsl:accumulator-rule match="Member/*"
select="map:put($value, node-name(), if (map:contains($value, node-name())) then map:get($value, node-name()) + 1 else 1)"/>
</xsl:accumulator>
<xsl:template match="Member">
<xsl:copy>
<xsl:fork>
<xsl:for-each-group select="*" group-by="node-name()">
<xsl:apply-templates select="current-group()"/>
</xsl:for-each-group>
</xsl:fork>
</xsl:copy>
</xsl:template>
<xsl:template match="Member/*">
<xsl:element name="{node-name()}_{accumulator-before('counters')(node-name())}">
<xsl:apply-templates/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
This approach only shows the grouping, it doesn't try to special case Name elements or some other way to not output an index if there is only one such element.
Firstly, my sympathy. XML that uses names like Payments_1 and Payments_2 is really bad news, someone is going to hate you for generating it like this. But if that's the kind of XML you've been told to produce, I guess it's not your job to question it.
As far as the requirements are concerned, you haven't made it clear whether the various kinds of sibling nodes are always grouped as in your example (all Locations, then all Payments, etc), or whether they can be interleaved.
One way you might be able to reduce the volume of code is by having a single accumulator holding a map. The map would use element names as the key and the current sibling count for that element as the value.
<accumulator name="counters" as="map(xs:QName, xs:integer)" initial-value="map{}">
<xsl:accumulator-rule match="Member" select="map{}"/>
<xsl:accumulator-rule match="Member/*" select="map:put($value, node-name(.), if (map:contains($value, node-name(.)) then map:get($value, node-name(.))+1 else 1"/>
</accumulator>
<xsl:template match="Members/*">
<xsl:element name="{name()}_{accumulator-before('counters')(node-name(.))}">
....
Another way to do the conditional map:put is
map:put($value, node-name(.), ($value(node-name(.)), 0)[1] + 1)

Modify node text within xsl copy-of

I have a xml document which looks like
<!-- language: lang-xml -->
<?xml version="1.0" encoding="UTF-8"?>
<Request xmlns="fst" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Header>
<AccountServer e-dtype="int">3</AccountServer>
</Header>
<Response>
<ResponseList e-dtype="list">
<Response>
<RequestId e-dtype="string">ServiceOrderGetRef</RequestId>
<RequestObjName e-dtype="string">ServiceOrder</RequestObjName>
<ServiceOrder>
<CreateDt e-dtype="dateTime">2014-03-01 00:00:00</CreateDt>
<CreateWho e-dtype="string">vc</CreateWho>
<WorkflowStartDt e-dtype="dateTime">2014-04-01 00:00:00</WorkflowStartDt>
</ServiceOrder>
</Response>
<Response>
<ComponentList e-dtype="list"/>
<Count e-dtype="int">0</Count>
<RequestId e-dtype="string">ComponentFindRef</RequestId>
<RequestObjName e-dtype="string">Component</RequestObjName>
<TotalCount e-dtype="int">0</TotalCount>
</Response>
<Response>
<Count e-dtype="int">0</Count>
<CustomerContractList e-dtype="list"/>
<RequestId e-dtype="string">CustomerContractRef</RequestId>
<RequestObjName e-dtype="string">CustomerContract</RequestObjName>
<TotalCount e-dtype="int">0</TotalCount>
</Response>
<Response>
<Count e-dtype="int">0</Count>
<ProductList e-dtype="list"/>
<RequestId e-dtype="string">ProductRef</RequestId>
<RequestObjName e-dtype="string">Product</RequestObjName>
<TotalCount e-dtype="int">0</TotalCount>
</Response>
<Response>
<Count e-dtype="int">0</Count>
<NrcList e-dtype="list"/>
<RequestId e-dtype="string">NrcFindRef</RequestId>
<RequestObjName e-dtype="string">Nrc</RequestObjName>
<TotalCount e-dtype="int">0</TotalCount>
</Response>
</ResponseList>
</Response>
</Request>
I am using copy-of function to copy node ServiceOrder within another xml document
I want to modify text of node WorkFlowStartDt and CreateDt and then do a copy-of. How can I do this?
My copied serviceorder node should look like this after modifing text. Below is the result xml
<?xml version="1.0" encoding="UTF-8"?>
<Request>
<Header>
<OperatorName e-dtype="string">ws</OperatorName>
<ApplicationName e-dtype="string">ws</ApplicationName>
</Header>
<CustomerUdtRequest>
<RequestList e-dtype="list">
<LogicalServiceOrder>
<RequestId e-dtype="string">MyExistingOrder</RequestId>
<LogicalServiceOrderPreProcess>
<Fetch e-dtype="boolean">true</Fetch>
<Order>
<AccountInternalId e-dtype="int">12345</AccountInternalId>
<Key>
<OrderId e-dtype="numeric">12345678</OrderId>
</Key>
</Order>
<ServiceOrderList e-dtype="list">
<ServiceOrder xmlns="fst" xmlns:xsi="http://www.w3.org/2001/XMLSchema-
instance">
<CreateDt e-dtype="dateTime">2014-03-02 00:00:00</CreateDt>
<CreateWho e-dtype="string">vc</CreateWho>
<WorkflowStartDt e-dtype="dateTime">2014-05-01 00:00:00</WorkflowStartDt>
</ServiceOrder>
</ServiceOrderList>
</LogicalServiceOrderPreProcess>
</LogicalServiceOrder>
</RequestList>
</CustomerUdtRequest>
</Request>
Below is my xslt processor file
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml"/>
<xsl:template match="/">
<Request>
<Header>
<OperatorName e-dtype="string">ws</OperatorName>
<ApplicationName e-dtype="string">ws</ApplicationName>
</Header>
<CustomerUdtRequest>
<RequestList e-dtype="list">
<LogicalServiceOrder>
<RequestId e-dtype="string">MyExistingOrder</RequestId>
<LogicalServiceOrderPreProcess> <Order> <AccountInternalId e-dtype="int">
<xsl:value-of
select="/Request/Response/ResponseList/Response/ServiceOrder/AccountInternalId"/>
</AccountInternalId> <Key> <OrderId e-dtype="numeric"> <xsl:value-of select="/Request/Response/ResponseList/Response/ServiceOrder/OrderId"/>
</OrderId>
</Key>
</Order>
<ServiceOrderList e-dtype="list">
<xsl:copy-of select="/Request/Response/ResponseList/Response/ServiceOrder"/>
</ServiceOrderList>
</LogicalServiceOrderPreProcess>
</LogicalServiceOrder>
</xsl:if>
</RequestList>
</CustomerUdtRequest>
</Request>
</xsl:template>
</xsl:stylesheet>
I want to modify text of node WorkFlowStartDt and CreateDt and then do
a copy-of.
That would be an unnecessary complication. You can modify nodes while you add them to the output tree.
Since you essentially want to copy everything "as is" except two nodes, it would be best to start with an identity transform template, then add an "exception" template for the two specific nodes that need modifying (one template for both, since the modification is identical).:
<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"/>
<!-- identity transformation -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="CreateDt | WorkFlowStartDt">
<xsl:copy>
<xsl:value-of select="concat('**', ., '**')"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Added:
In response to the edited question, try this stylesheet:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ns="fst"
exclude-result-prefixes="ns">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/">
<Request>
<Header>
<OperatorName e-dtype="string">ws</OperatorName>
<ApplicationName e-dtype="string">ws</ApplicationName>
</Header>
<CustomerUdtRequest>
<RequestList e-dtype="list">
<LogicalServiceOrder>
<RequestId e-dtype="string">MyExistingOrder</RequestId>
<LogicalServiceOrderPreProcess>
<Order>
<AccountInternalId e-dtype="int">
<!-- THIS DOESN'T POINT TO ANY EXISTING NODE!! -->
<xsl:value-of select="ns:Request/ns:Response/ns:ResponseList/ns:Response/ns:ServiceOrder/ns:AccountInternalId"/>
</AccountInternalId>
<Key>
<OrderId e-dtype="numeric">
<!-- THIS DOESN'T POINT TO ANY EXISTING NODE!! -->
<xsl:value-of select="ns:Request/ns:Response/ns:ResponseList/ns:Response/ns:ServiceOrder/ns:OrderId"/>
</OrderId>
</Key>
</Order>
<xsl:apply-templates select="ns:Request/ns:Response/ns:ResponseList/ns:Response/ns:ServiceOrder"/>
</LogicalServiceOrderPreProcess>
</LogicalServiceOrder>
</RequestList>
</CustomerUdtRequest>
</Request>
</xsl:template>
<xsl:template match="ns:ServiceOrder">
<xsl:copy>
<xsl:copy-of select="ns:CreateWho"/>
<xsl:apply-templates select="ns:CreateDt | ns:WorkflowStartDt"/>
</xsl:copy>
</xsl:template>
<xsl:template match="ns:CreateDt | ns:WorkflowStartDt">
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:value-of select="concat('**', ., '**')"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
When applied to your input (minus the illegal opening comment), the following result is obtained:
<?xml version="1.0" encoding="UTF-8"?>
<Request>
<Header>
<OperatorName e-dtype="string">ws</OperatorName>
<ApplicationName e-dtype="string">ws</ApplicationName>
</Header>
<CustomerUdtRequest>
<RequestList e-dtype="list">
<LogicalServiceOrder>
<RequestId e-dtype="string">MyExistingOrder</RequestId>
<LogicalServiceOrderPreProcess>
<Order>
<AccountInternalId e-dtype="int"/>
<Key>
<OrderId e-dtype="numeric"/>
</Key>
</Order>
<ServiceOrder xmlns="fst" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<CreateWho e-dtype="string">vc</CreateWho>
<CreateDt e-dtype="dateTime">**2014-03-01 00:00:00**</CreateDt>
<WorkflowStartDt e-dtype="dateTime">**2014-04-01 00:00:00**</WorkflowStartDt>
</ServiceOrder>
</LogicalServiceOrderPreProcess>
</LogicalServiceOrder>
</RequestList>
</CustomerUdtRequest>
</Request>