Reputation: 29
I need to transform my xml to another xml based on parent and child relationship.
Below is my source xml
<FIXML>
<Header>
<RequestID>ReqID8942</RequestID>
</Header>
<Body>
<Data>
<LimitDetails>
<LimitRefNo>L1</LimitRefNo>
<LimitClassification>ROOT</LimitClassification>
<ParentLimitRefNo></ParentLimitRefNo>
<ApprovedLimit>100.0</ApprovedLimit>
</LimitDetails>
<LimitDetails>
<LimitRefNo>L2</LimitRefNo>
<LimitClassification>ClASSIFICATION1</LimitClassification>
<ParentLimitRefNo>L1</ParentLimitRefNo>
<ApprovedLimit>200.0</ApprovedLimit>
</LimitDetails>
<LimitDetails>
<LimitRefNo>L3</LimitRefNo>
<LimitClassification>CLASSIFICATION2</LimitClassification>
<ParentLimitRefNo>L2</ParentLimitRefNo>
<ApprovedLimit>300.0</ApprovedLimit>
</LimitDetails>
<LimitDetails>
<LimitRefNo>L4</LimitRefNo>
<LimitClassification>CLASSIFICATION3</LimitClassification>
<ParentLimitRefNo>L3</ParentLimitRefNo>
<ApprovedLimit>400.0</ApprovedLimit>
</LimitDetails>
</Data>
</Body>
</FIXML>
Here,Child limits refers Parent limits based on ParentLimitRefNo. Parent limit is the one which has ParentLimitRefNo as empty.
Below is the xml which i need to produce based on source xml.
<FIXML>
<Header>
<RequestID>ReqID8942</RequestID>
</Header>
<Body>
<Data>
<LimitDetails>
<Limit>
<LimitRefNo>L1</LimitRefNo>
<LimitClassification>ROOT</LimitClassification>
<ParentLimitRefNo></ParentLimitRefNo>
<ApprovedLimit>100.0</ApprovedLimit>
<SubLimit>
<LimitRefNo>L2</LimitRefNo>
<LimitClassification>ClASSIFICATION1</LimitClassification>
<ParentLimitRefNo>L1</ParentLimitRefNo>
<ApprovedLimit>200.0</ApprovedLimit>
<SubLimit>
<LimitRefNo>L3</LimitRefNo>
<LimitClassification>CLASSIFICATION2</LimitClassification>
<ParentLimitRefNo>L2</ParentLimitRefNo>
<ApprovedLimit>300.0</ApprovedLimit>
<SubLimit>
<LimitRefNo>L4</LimitRefNo>
<LimitClassification>CLASSIFICATION3</LimitClassification>
<ParentLimitRefNo>L3</ParentLimitRefNo>
<ApprovedLimit>400.0</ApprovedLimit>
</SubLimit>
</SubLimit>
</SubLimit>
</Limit>
</LimitDetails>
</Data>
</Body>
Thanks in advance.
Upvotes: 2
Views: 703
Reputation: 2869
The use of keys surely is more elegant (see Dimitre Novatchevs solution), but this one takes the wanted changes in the structure into account (e.g. rename <LimitDetails />
to <SubLimit />
and placing the <Limit />
tag.):
<?xml version="1.0" encoding="UTF-8"?>
<xsl:transform version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="@*|node()">
<xsl:copy><xsl:apply-templates select="@*|node()" /></xsl:copy>
</xsl:template>
<xsl:template match="Data">
<xsl:copy>
<LimitDetails>
<Limit>
<xsl:apply-templates select=".//LimitDetails[./ParentLimitRefNo='']" />
</Limit>
</LimitDetails>
</xsl:copy>
</xsl:template>
<xsl:template match="LimitDetails">
<xsl:variable name="LimitRefNo" select="./LimitRefNo" />
<xsl:apply-templates select="@*|node()" />
<xsl:if test="../LimitDetails[./ParentLimitRefNo = $LimitRefNo]">
<SubLimit>
<xsl:apply-templates select="../LimitDetails[./ParentLimitRefNo = $LimitRefNo]" />
</SubLimit>
</xsl:if>
</xsl:template>
</xsl:transform>
Or as a modification of Dimitre's solution using keys:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="kLD" match="LimitDetails" use="ParentLimitRefNo"/>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Data">
<Data>
<LimitDetails>
<Limit>
<xsl:apply-templates select="LimitDetails[not(ParentLimitRefNo/node())]"/>
</Limit>
</LimitDetails>
</Data>
</xsl:template>
<xsl:template match="LimitDetails">
<xsl:apply-templates />
<xsl:if test="key('kLD', LimitRefNo)">
<SubLimit>
<xsl:apply-templates select="key('kLD', LimitRefNo)"/>
</SubLimit>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Upvotes: 1
Reputation: 243569
This XSLT 2.0 transformation (easy to convert to XSLT 1.0):
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="kLD" match="LimitDetails" use="ParentLimitRefNo"/>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Data">
<Data>
<LimitDetails>
<xsl:apply-templates
select="LimitDetails[not(ParentLimitRefNo/node())]"/>
</LimitDetails>
</Data>
</xsl:template>
<xsl:template match="LimitDetails">
<xsl:variable name="vSuf" select=
"if(ParentLimitRefNo/text())
then 'Sub'
else ()
"/>
<xsl:element name="{$vSuf}Limit">
<xsl:apply-templates select="node()|key('kLD', LimitRefNo)"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document:
<FIXML>
<Header>
<RequestID>ReqID8942</RequestID>
</Header>
<Body>
<Data>
<LimitDetails>
<LimitRefNo>L1</LimitRefNo>
<LimitClassification>ROOT</LimitClassification>
<ParentLimitRefNo></ParentLimitRefNo>
<ApprovedLimit>100.0</ApprovedLimit>
</LimitDetails>
<LimitDetails>
<LimitRefNo>L2</LimitRefNo>
<LimitClassification>ClASSIFICATION1</LimitClassification>
<ParentLimitRefNo>L1</ParentLimitRefNo>
<ApprovedLimit>200.0</ApprovedLimit>
</LimitDetails>
<LimitDetails>
<LimitRefNo>L3</LimitRefNo>
<LimitClassification>CLASSIFICATION2</LimitClassification>
<ParentLimitRefNo>L2</ParentLimitRefNo>
<ApprovedLimit>300.0</ApprovedLimit>
</LimitDetails>
<LimitDetails>
<LimitRefNo>L4</LimitRefNo>
<LimitClassification>CLASSIFICATION3</LimitClassification>
<ParentLimitRefNo>L3</ParentLimitRefNo>
<ApprovedLimit>400.0</ApprovedLimit>
</LimitDetails>
</Data>
</Body>
</FIXML>
produces the wanted, correct result:
<FIXML>
<Header>
<RequestID>ReqID8942</RequestID>
</Header>
<Body>
<Data>
<LimitDetails>
<Limit>
<LimitRefNo>L1</LimitRefNo>
<LimitClassification>ROOT</LimitClassification>
<ParentLimitRefNo/>
<ApprovedLimit>100.0</ApprovedLimit>
<SubLimit>
<LimitRefNo>L2</LimitRefNo>
<LimitClassification>ClASSIFICATION1</LimitClassification>
<ParentLimitRefNo>L1</ParentLimitRefNo>
<ApprovedLimit>200.0</ApprovedLimit>
<SubLimit>
<LimitRefNo>L3</LimitRefNo>
<LimitClassification>CLASSIFICATION2</LimitClassification>
<ParentLimitRefNo>L2</ParentLimitRefNo>
<ApprovedLimit>300.0</ApprovedLimit>
<SubLimit>
<LimitRefNo>L4</LimitRefNo>
<LimitClassification>CLASSIFICATION3</LimitClassification>
<ParentLimitRefNo>L3</ParentLimitRefNo>
<ApprovedLimit>400.0</ApprovedLimit>
</SubLimit>
</SubLimit>
</SubLimit>
</Limit>
</LimitDetails>
</Data>
</Body>
</FIXML>
Explanation:
Using and modifying the identity rule.
Using a key to specify all logical children of a LimitDetails
from its LimitRefNo
.
II. XSLT 1.0 solution:
This is an almost mechanical translation of the above transformation into XSLT 1.0 -- only the definition of the variable $vSuf
is different:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="kLD" match="LimitDetails" use="ParentLimitRefNo"/>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Data">
<Data>
<LimitDetails>
<xsl:apply-templates
select="LimitDetails[not(ParentLimitRefNo/node())]"/>
</LimitDetails>
</Data>
</xsl:template>
<xsl:template match="LimitDetails">
<xsl:variable name="vSuf" select=
"concat('',
substring('Sub',1 div boolean(ParentLimitRefNo/text()))
)"/>
<xsl:element name="{$vSuf}Limit">
<xsl:apply-templates select="node()|key('kLD', LimitRefNo)"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
When applied to the same XML document (above), the same correct result is produced.
Upvotes: 1