XSL group by node value - xslt

I have a case of XSL key grouping. The goal is to replace a value based on an ID match.
Input:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<message-in>
<actions>
<stop>
<action id="33632">
<text>DefaultComment</text>
<planning-status>finished</planning-status>
<realised-times>
<starttime>2017-03-13T12:43:54</starttime>
<finishtime>2017-03-13T13:15:21</finishtime>
</realised-times>
</action>
</stop>
<stop>
<action id="33635">
<planning-status>started</planning-status>
<realised-times>
<starttime>2017-03-13T13:15:21</starttime>
</realised-times>
</action>
</stop>
</actions>
</message-in>
<output_getquerydata>
<queries>
<query name="fat">
<parameters>
<parameter name="id">33632</parameter>
</parameters>
<queryErrors/>
<queryResults>
<record id="1">
<column name="interfaceAction_externalId">33633OREA</column>
</record>
</queryResults>
</query>
</queries>
</output_getquerydata>
<output_getquerydata>
<queries>
<query name="fat">
<parameters>
<parameter name="id">33635</parameter>
</parameters>
<queryErrors/>
<queryResults>
<record id="1">
<column name="interfaceAction_externalId">536313OREA</column>
</record>
</queryResults>
</query>
</queries>
</output_getquerydata>
</root>
XSL:
<?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" omit-xml-declaration="no" encoding="utf-8"/>
<xsl:strip-space elements="*"/>
<xsl:key name="actionKey" match="stop" use="action/#id"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/">
<root>
<xsl:copy-of select="//message-in"/>
</root>
</xsl:template>
<xsl:template match="action/#id">
<xsl:attribute name="id"><xsl:value-of select="substring-before(//output_getquerydata/queries/query/parameters/parameter[key('actionKey', #id)]/queryResults/record/column[#name='interfaceAction_externalId'],'OREA')"/></xsl:attribute>
</xsl:template>
</xsl:stylesheet>
I have multiple "action" nodes that have matching "query" nodes.
The goal is that for every action ID we need to replace the ID value from the "action" tag with the corresponding "interfaceAction_externalId" value.
So for action ID 33632 we'll copy and replace with the value of "33633" (because we have a match in parameneter/name/#id 33632 = action/#id ).
The copy works well I get all the information I need, but it seems that action/#id doesn't get replaced.
I thought that I'll use a key to save the values of action/#id then use that in the template match to replace with the value from interfaceAction_externalId, but it doesn't work and I'm not sure what I'm doing wrong.
Thanks for your help!

The whole approach only makes sense if you use <xsl:apply-templates select="//message-in"/> instead of <xsl:copy-of select="//message-in"/>.
As for using a key, I think you want
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" omit-xml-declaration="no" encoding="utf-8"/>
<xsl:strip-space elements="*"/>
<xsl:key name="ref-query"
match="output_getquerydata/queries/query/queryResults/record/column[#name='interfaceAction_externalId']"
use="ancestor::query/parameters/parameter[#name = 'id']"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/">
<root>
<xsl:apply-templates select="//message-in"/>
</root>
</xsl:template>
<xsl:template match="action/#id">
<xsl:attribute name="id"><xsl:value-of select="substring-before(key('ref-query', .),'OREA')"/></xsl:attribute>
</xsl:template>
</xsl:stylesheet>

Related

How to copy values by matching nodes in XSLT

In my XML I want to copy values into one node from another node if both nodes have matching values in some other nodes.
<XML>
<ORG>
<ITM>
<NUM>1</NUM>
<SEQ>10</SEQ>
<VAL>X</VAL>
</ITM>
<ITM>
<NUM>2</NUM>
<SEQ>20</SEQ>
<VAL>Y</VAL>
</ITM>
</ORG>
<NEW>
<ITM>
<NUM>1</NUM>
<SEQ>10</SEQ>
<VAL>QQQ</VAL>
</ITM>
</NEW>
</XML>
This works, but for large documents it might be slow, is there a better way?
<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="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="ORG/ITM/VAL">
<xsl:variable name="NUM" select="../NUM"/>
<xsl:variable name="SEQ" select="../SEQ"/>
<VAL>
<xsl:value-of select="../../../NEW/ITM/VAL[../NUM=$NUM and ../SEQ=$SEQ]"/>
</VAL>
</xsl:template>
</xsl:stylesheet>
The result has the value "QQQ" in the ORG/ITM/VAL node since ORG/ITM/NUM and ORD/ITM/SEQ match with NEW/ITM/NUM and NEW/ITM/SEQ
<?xml version="1.0"?>
<XML>
<ORG>
<ITM>
<NUM>1</NUM>
<SEQ>10</SEQ>
<VAL>QQQ</VAL>
</ITM>
<ITM>
<NUM>2</NUM>
<SEQ>20</SEQ>
<VAL/>
</ITM>
</ORG>
<NEW>
<ITM>
<NUM>1</NUM>
<SEQ>10</SEQ>
<VAL>QQQ</VAL>
</ITM>
</NEW>
</XML>
The preferred method to lookup values is to define a key and use it. In the given example, this could be:
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="new-item" match="NEW/ITM" use="concat(NUM, '|', SEQ)" />
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="ORG/ITM/VAL">
<xsl:copy>
<xsl:value-of select="key('new-item', concat(../NUM, '|', ../SEQ))/VAL"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
using 1 variable is more faster.
<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="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="ORG/ITM/VAL">
<xsl:variable name="NUMSEQ" select="concat(string(../NUM), string(../SEQ))"/>
<VAL>
<xsl:value-of select="../../../NEW/ITM/VAL[concat(string(../NUM), string(../SEQ))=$NUMSEQ]"/>
</VAL>
</xsl:template>
</xsl:stylesheet>

XSLT copy without segment name

I have the following XML:
<segment>
<personal_information>
<birth_name>xxx</birth_name>
<created_by>yyy</created_by>
<created_on_timestamp>2018-08-06T06:41:07.000Z</created_on_timestamp>
</personal_information>
<segment>
I want to copy the entire personal_information segment with all elements and sub-segments while adding a new field. I tried this with:
<segment>
<personal_information>
<action>DELETE</action>
<xsl:copy>
<xsl:apply-templates select="child::node()"/>
</xsl:copy>
</personal_information>
</segment>
But this results in the following:
<segment>
<personal_information>
<action>DELETE</action>
<personal_information>
<birth_name>xxx</birth_name>
<created_by>yyy</created_by>
<created_on_timestamp>2018-08-06T06:41:07.000Z</created_on_timestamp>
</personal_information>
</personal_information>
</segment>
Would would be the XSLT code to achieve this as a result:
<segment>
<personal_information>
<action>DELETE</action>
<birth_name>xxx</birth_name>
<created_by>yyy</created_by>
<created_on_timestamp>2018-08-06T06:41:07.000Z</created_on_timestamp>
</personal_information>
</segment>
I do not want to copy all fields one by one.
Instead of using <xsl:copy>, use <xsl:copy-of>, which accepts an XPath expression for the nodes to be included in the copy; in this case only the inner childnodes.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="segment">
<segment>
<personal_information>
<action>DELETE</action>
<xsl:copy-of select="personal_information/*" />
</personal_information>
</segment>
</xsl:template>
</xsl:stylesheet>
You need a identity template and one personal_information with adding one action element:
<?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="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="personal_information">
<xsl:copy>
<action>DELETE</action>
<xsl:apply-templates select="node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Updated as per new requirement :
<?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="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="personal_information">
<action>DELETE</action>
<xsl:apply-templates select="node()"/>
</xsl:template>
</xsl:stylesheet>

Moving xml nodes to other nodes if some of their node values are equal(using XSLT)

I have the following xml file:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<cases>
<case>
<key>123</key>
</case>
<case>
<key>456</key>
</case>
</cases>
<issues>
<issue>
<issueid>issue1</issueid>
<casekey>123</casekey>
</issue>
<issue>
<issueid>issue2</issueid>
<casekey>123</casekey>
</issue>
<issue>
<issueid>issue3</issueid>
<casekey>456</casekey>
</issue>
</issues>
</root>
I want to move all the <issue> nodes to the <case> whose <casekey> value equal to the <key> value of <case>.
In other words, if the <casekey> value of an <issue> is equal to to the <key> value of <case>, then move that issue under the case.
My final xml should look like below:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<cases>
<case>
<key>123</key>
<issue>
<issueid>issue1</issueid>
<casekey>123</casekey>
</issue>
<issue>
<issueid>issue2</issueid>
<casekey>123</casekey>
</issue>
</case>
<case>
<key>456</key>
<issue>
<issueid>issue3</issueid>
<casekey>456</casekey>
</issue>
</case>
</cases>
</root>
XSLT has a built-in key mechanism for resolving cross-references - it's best to use it:
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:key name="issues" match="issue" use="casekey" />
<xsl:template match="/root">
<root>
<cases>
<xsl:for-each select="cases/case">
<case>
<xsl:copy-of select="key"/>
<xsl:copy-of select="key('issues', key)"/>
</case>
</xsl:for-each>
</cases>
</root>
</xsl:template>
</xsl:stylesheet>
P.S. Do not confuse a reference to the key element in your XML with the xsl:key instruction and the key() function.
The main point is a template matching case, including:
Something similar to the identity template.
But before closing xsl:copy there is another xsl:apply-templates,
to process respective issues.
The final thing to add is to block "normal" processing of issues tag.
Below you have a working solution:
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="xml" encoding="UTF-8" indent="yes" />
<xsl:strip-space elements="*"/>
<xsl:template match="case">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
<xsl:variable name="kk" select="key"/>
<xsl:apply-templates select="../../issues/issue[casekey=$kk]"/>
</xsl:copy>
</xsl:template>
<xsl:template match="issues"/>
<xsl:template match="#*|node()">
<xsl:copy><xsl:apply-templates select="#*|node()"/></xsl:copy>
</xsl:template>
</xsl:transform>
<xsl:output method="xml" indent="yes"/>
<xsl:template match="root">
<root>
<xsl:apply-templates select="cases"/>
</root>
</xsl:template>
<xsl:template match="case">
<xsl:variable name="keyvalue" select="normalize-space(key/text())"/>
<case>
<xsl:copy-of select="key"/>
<xsl:if test="ancestor::cases/following-sibling::issues/descendant::casekey = $keyvalue">
<xsl:copy-of
select="ancestor::cases/following-sibling::issues/issue[casekey = $keyvalue]"/>
</xsl:if>
</case>
</xsl:template>
<xsl:template match="issue[child::casekey = preceding::key]"/>
<xsl:template match="node() | #*">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>

XSLT remove duplicate nodes based on element value

I am newbie to XSLT.can you help me for xslt to achieve below output. In my input xml have duplicate nodes and i have to remove based on element (CustAccId) value should not be re-peat.
Inputxml :
<Main>
<Request>
<TypeInd>I</TypeInd>
<CustAcctID>505665599</CustAcctID>
<ServiceOrderID>1452653</ServiceOrderID>
</Request>
<Request>
<TypeInd>O</TypeInd>
<CustAcctID>2011395</CustAcctID>
<ServiceOrderID>1452652</ServiceOrderID>
</Request>
<Request>
<TypeInd>I</TypeInd>
<CustAcctID>505665599</CustAcctID>
<ServiceOrderID>1452653</ServiceOrderID>
</Request>
</Main>
Output XML :
<Main>
<Request>
<TypeInd>I</TypeInd>
<CustAcctID>505665599</CustAcctID>
<ServiceOrderID>1452653</ServiceOrderID>
</Request>
<Request>
<TypeInd>O</TypeInd>
<CustAcctID>2011395</CustAcctID>
<ServiceOrderID>1452652</ServiceOrderID>
</Request>
Here is XSLt i tried but didn't work like it retrun duplicate request node
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format">
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="/">
<xsl:if test="not(following::Request[CustAcctID=current()])">
<xsl:copy>
<xsl:apply-templates select="#*|node()" />
</xsl:copy>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
You can remove the duplicate with following XSLT:
<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="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="Request[CustAcctID = following::Request/CustAcctID]"/>
</xsl:stylesheet>
Output XML:
<?xml version="1.0" encoding="UTF-8"?>
<Main>
<Request>
<TypeInd>O</TypeInd>
<CustAcctID>2011395</CustAcctID>
<ServiceOrderID>1452652</ServiceOrderID>
</Request>
<Request>
<TypeInd>I</TypeInd>
<CustAcctID>505665599</CustAcctID>
<ServiceOrderID>1452653</ServiceOrderID>
</Request>
</Main>
The template matching all Request nodes where the CustAcctID matches the CustAcctID of the following Request will not produce any output for the matching Request, so the duplicates will not be written.
Update for the advice in the comment by michael.hor257k: Another approach is to use Muenchian grouping:
<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:key name="x" match="ServiceOrderID" use="." />
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="Request">
<xsl:for-each select=".">
<xsl:if test="generate-id(ServiceOrderID) =
generate-id(key('x', ServiceOrderID)[1])">
<xsl:copy>
<xsl:apply-templates select="#*|node()" />
</xsl:copy>
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
which produces the same output XML but can be more efficient as you'll find described in detail in the article by Jeni Tennison http://www.jenitennison.com/xslt/grouping/muenchian.xml that michael.hor257k already recommended.
As additional reference for XSLT grouping you can have a look at http://www.dpawson.co.uk/xsl/sect2/N4486.html

xsl copy if attribute of current node equals to attribute of another node

I have a sample XML File:
<?xml version="1.0" encoding="UTF-8"?>
<XML>
<MetaData>
<Ref MDID='ID'></Ref>
</MetaData>
<MetaData2>
<Ref MDID='ID2'></Ref>
</MetaData2>
<Items ID='ID'>
<Item OID='haveit'></Item>
<Item OID='ornot'></Item>
</Items>
<Items ID= ID2'>
<Item OID='ornot'></Item>
<Item OID='ornot'></Item>
</Items>
</XML>
I have to transform it so that I receive the following result.
<?xml version="1.0" encoding="UTF-8"?>
<XML>
<MetaData>
<Ref MDID='ID'></Ref>
</MetaData>
<Items ID='ID'>
<Item OID='haveit'></Item>
</Items>
</XML>
So first I have to check if the Item 'haveit' exists. Then I copy the corresponding parent "Item". Then I need to copy the MetaData where the MDID equals to the Items ID (in this case 'ID', but I don't know the exact value in my real example)
What I have so far:
<?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" version="1.0" encoding="UTF-8" indent="yes" />
<xsl:strip-space elements="*" />
<xsl:variable name="Item" select = "'haveit'"/>
<xsl:template match="XML">
<xsl:copy>
<xsl:apply-templates />
</xsl:copy>
</xsl:template>
<xsl:template match="Items">
<xsl:if test="child::Item[#OID = $Item]">
<xsl:copy>
<xsl:copy-of select="#*" /> <!-- copy attributes -->
</xsl:copy>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
With this code I'm able to copy the Item I want and the corresponding Items Element. Now I don't know how to get the right MetaData element. How can I check if the MDID of Ref hase the same value as the Items ID?
How about:
<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:param name="oid" select="'haveit'"/>
<xsl:key name="meta" match="*" use="Ref/#MDID" />
<xsl:template match="/XML">
<xsl:variable name="matching-items" select="Items[Item/#OID=$oid]" />
<xsl:copy>
<xsl:copy-of select="key('meta', $matching-items/#ID)"/>
<xsl:apply-templates select="$matching-items"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Items">
<xsl:copy>
<xsl:copy-of select="#*" />
<xsl:copy-of select="Item[#OID=$oid]" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>