XSLT Merging two nodes into one - xslt

I'm looking to merge two nodes (or more) into one. I've worked with xslt for a little while, but mainly doing pretty simple stuff. I've done much searching, but solutions have been over my head so I haven't been able to adapt to my own problem. Closest thing I've found is an answer by Martin Honnen using a function he built called "eliminate-deep-equal-duplicates".
My problem is I can have two or more <Coverage> nodes that have "CoverageCd=ADDRL" and I need to combine these nodes only, no other Coverage nodes with other CoverageCd values. So I want to merge the ADDRL nodes but keep the unique "Addr" child node for all ADDRL iterations.
One other caveat is that I need to have the count of merged ADDRL nodes and place in the "OptionValue" element. So In my example where I have two ADDRL Coverage nodes my OptionValue needs to be 2. My xslt currently almost gives me what I need, but duplicates the MiscParty/GeneralPartyInfo which I don't want. And while I have the variable AddrlCount that gives me the correct value to place in my OptionValue,
I'm not quite sure how to incorporate that into the current xslt. I know my main problem is that I'm not exactly sure what the "eliminate-deep-equal-duplicates" function is doing. Any help anyone could provide would be greatly appreciated.
Input XML
<ACORD>
<InsuranceSvcRq>
<HomePolicyQuoteInqRq>
<HomeLineBusiness>
<Dwell LocationRef="000b3c6b-264f-83b7-1b80-006a3ce1f40e">
<PolicyTypeCd>06</PolicyTypeCd>
<PurchaseDt>2011-05-10</PurchaseDt>
<Construction>
<ConstructionCd>F</ConstructionCd>
<com.ormutual_recontype>Standard</com.ormutual_recontype>
<YearBuilt>1988</YearBuilt>
<BldgArea>
<NumUnits>1200</NumUnits>
<UnitMeasurementCd>Square Foot</UnitMeasurementCd>
</BldgArea>
</Construction>
<Coverage>
<CoverageCd>MEDPM</CoverageCd>
<Limit>
<FormatInteger>1000</FormatInteger>
</Limit>
</Coverage>
<Coverage>
<CoverageCd>LAC</CoverageCd>
<Limit>
<FormatInteger>50000</FormatInteger>
</Limit>
</Coverage>
<Coverage>
<CoverageCd>ADDRL</CoverageCd>
<Option>
<OptionTypeCd>Num1</OptionTypeCd>
<OptionValue>1</OptionValue>
</Option>
<MiscParty>
<GeneralPartyInfo>
<Addr>
<AddrTypeCd>StreetAddress</AddrTypeCd>
<Addr1>9325 SW CAMILLE TER</Addr1>
<City>PORTLAND</City>
<StateProvCd>OR</StateProvCd>
<PostalCode>97223</PostalCode>
<County>WASHINGTON</County>
</Addr>
</GeneralPartyInfo>
</MiscParty>
</Coverage>
<Coverage>
<CoverageCd>ADDRL</CoverageCd>
<Option>
<OptionTypeCd>Num1</OptionTypeCd>
<OptionValue>1</OptionValue>
</Option>
<MiscParty>
<GeneralPartyInfo>
<Addr>
<AddrTypeCd>StreetAddress</AddrTypeCd>
<Addr1>2222 ANDREW AVE NW</Addr1>
<City>SALEM</City>
<StateProvCd>OR</StateProvCd>
<PostalCode>97304</PostalCode>
<County>POLK</County>
</Addr>
</GeneralPartyInfo>
</MiscParty>
</Coverage>
</Dwell>
</HomeLineBusiness>
</HomePolicyQuoteInqRq>
</InsuranceSvcRq>
</ACORD>
Desired Output
<ACORD>
<InsuranceSvcRq>
<HomePolicyQuoteInqRq>
<HomeLineBusiness>
<Dwell LocationRef="000b3c6b-264f-83b7-1b80-006a3ce1f40e">
<PolicyTypeCd>06</PolicyTypeCd>
<PurchaseDt>2011-05-10</PurchaseDt>
<Construction>
<ConstructionCd>F</ConstructionCd>
<com.ormutual_recontype>Standard</com.ormutual_recontype>
<YearBuilt>1988</YearBuilt>
<BldgArea>
<NumUnits>1200</NumUnits>
<UnitMeasurementCd>Square Foot</UnitMeasurementCd>
</BldgArea>
</Construction>
<Coverage>
<CoverageCd>MEDPM</CoverageCd>
<Limit>
<FormatInteger>1000</FormatInteger>
</Limit>
</Coverage>
<Coverage>
<CoverageCd>LAC</CoverageCd>
<Limit>
<FormatInteger>50000</FormatInteger>
</Limit>
</Coverage>
<Coverage>
<CoverageCd>ADDRL</CoverageCd>
<Option>
<OptionTypeCd>Num1</OptionTypeCd>
<OptionValue>2</OptionValue>
</Option>
<MiscParty>
<GeneralPartyInfo>
<Addr>
<AddrTypeCd>StreetAddress</AddrTypeCd>
<Addr1>9325 SW CAMILLE TER</Addr1>
<City>PORTLAND</City>
<StateProvCd>OR</StateProvCd>
<PostalCode>97223</PostalCode>
<County>WASHINGTON</County>
</Addr>
<Addr>
<AddrTypeCd>StreetAddress</AddrTypeCd>
<Addr1>2222 ANDREW AVE NW</Addr1>
<City>SALEM</City>
<StateProvCd>OR</StateProvCd>
<PostalCode>97304</PostalCode>
<County>POLK</County>
</Addr>
</GeneralPartyInfo>
</MiscParty>
</Coverage>
</Dwell>
</HomeLineBusiness>
</HomePolicyQuoteInqRq>
</InsuranceSvcRq>
</ACORD>
My attempt at the xslt
<?xml version="1.0"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:functx="http://www.functx.com" xmlns:mf="http://example.com/mf" exclude-result-prefixes="xs functx mf">
<xsl:output indent="yes" method="xml" omit-xml-declaration="yes"/>
<xsl:function name="functx:index-of-node" as="xs:integer*">
<xsl:param name="nodes" as="node()*"/>
<xsl:param name="nodeToFind" as="node()"/>
<xsl:sequence select=" for $seq in (1 to count($nodes)) return $seq[$nodes[$seq] is $nodeToFind] "/>
</xsl:function>
<xsl:function name="mf:eliminate-deep-equal-duplicates" as="node()*">
<xsl:param name="nodes"/>
<xsl:sequence select="for $node in $nodes return $node[not(some $preceding-node in $nodes[position() lt functx:index-of-node($nodes, $node)] satisfies deep-equal($node, $preceding-node))]"/>
</xsl:function>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:variable name="AddrlCount" select="count(ACORD/InsuranceSvcRq/HomePolicyQuoteInqRq/HomeLineBusiness/Dwell/Coverage[./CoverageCd='ADDRL'])"/>
<xsl:template match="ACORD/InsuranceSvcRq/HomePolicyQuoteInqRq/HomeLineBusiness/Dwell">
<xsl:copy>
<xsl:for-each-group select="Coverage" group-by="CoverageCd">
<xsl:copy>
<xsl:apply-templates select="Coverage[./CoverageCd='ADDRL'],
mf:eliminate-deep-equal-duplicates(current-group()/(* except (Addr))),
Addr"/>
</xsl:copy>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

You are grouping Coverage elements by CoverageCd values
<xsl:for-each-group select="Coverage" group-by="CoverageCd">
So, at this point you are positioned on a Coverage element (ones with the first occurrence of a CoverageCd value). But you then do this...
<xsl:apply-templates select="Coverage[./CoverageCd='ADDRL'],
This means you are looking for a Coverage element that is a child of a covering Coverage element, which would return element. Similarly, where you do this....
mf:eliminate-deep-equal-duplicates(current-group()/(* except (Addr)))
current-group() returns all Coverage elements with the same code, none of which have Addr elements, so this selects all elements.
What you can possibly do, is just replace the statement with this:
<xsl:apply-templates select="mf:eliminate-deep-equal-duplicates(current-group()/*)" />
Then you can have templates for matching elements you wish to change.
Try this XSLT:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:functx="http://www.functx.com" xmlns:mf="http://example.com/mf" exclude-result-prefixes="xs functx mf">
<xsl:output indent="yes" method="xml" omit-xml-declaration="yes"/>
<xsl:function name="functx:index-of-node" as="xs:integer*">
<xsl:param name="nodes" as="node()*"/>
<xsl:param name="nodeToFind" as="node()"/>
<xsl:sequence select=" for $seq in (1 to count($nodes)) return $seq[$nodes[$seq] is $nodeToFind] "/>
</xsl:function>
<xsl:function name="mf:eliminate-deep-equal-duplicates" as="node()*">
<xsl:param name="nodes"/>
<xsl:sequence select="for $node in $nodes return $node[not(some $preceding-node in $nodes[position() lt functx:index-of-node($nodes, $node)] satisfies deep-equal($node, $preceding-node))]"/>
</xsl:function>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="ACORD/InsuranceSvcRq/HomePolicyQuoteInqRq/HomeLineBusiness/Dwell">
<xsl:copy>
<xsl:apply-templates select="#*|node() except Coverage" />
<xsl:for-each-group select="Coverage" group-by="CoverageCd">
<xsl:copy>
<xsl:apply-templates select="mf:eliminate-deep-equal-duplicates(current-group()/*)" />
</xsl:copy>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
<xsl:template match="OptionValue">
<xsl:copy>
<xsl:value-of select="count(mf:eliminate-deep-equal-duplicates(current-group()/MiscParty/GeneralPartyInfo/Addr))" />
</xsl:copy>
</xsl:template>
<xsl:template match="GeneralPartyInfo">
<xsl:copy>
<xsl:apply-templates select="mf:eliminate-deep-equal-duplicates(current-group()/MiscParty/GeneralPartyInfo/Addr)" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
To be honest, I don't think you even need the mf:eliminate-deep-equal-duplicates in this particular case. This XSLT also produces the result you need
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:functx="http://www.functx.com" xmlns:mf="http://example.com/mf" exclude-result-prefixes="xs functx mf">
<xsl:output indent="yes" method="xml" omit-xml-declaration="yes"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="ACORD/InsuranceSvcRq/HomePolicyQuoteInqRq/HomeLineBusiness/Dwell">
<xsl:copy>
<xsl:apply-templates select="#*|node() except Coverage" />
<xsl:for-each-group select="Coverage" group-by="CoverageCd">
<xsl:copy>
<xsl:apply-templates />
</xsl:copy>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
<xsl:template match="OptionValue">
<xsl:copy>
<xsl:value-of select="count(current-group()/MiscParty/GeneralPartyInfo/Addr)" />
</xsl:copy>
</xsl:template>
<xsl:template match="GeneralPartyInfo">
<xsl:copy>
<xsl:apply-templates select="current-group()/MiscParty/GeneralPartyInfo/Addr" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

Related

How to remove looping elements using XSLT Transformations where some tag have no values

<DETAILS>
<PUT>
<RECORD>ABC_PQRST0123456-001_1</RECORD>
<NUMBER>4</NUMBER>
<INST>1,2</INST>
</PUT>
<PUT>
<RECORD>ABC_PQRST0123456-001_2</RECORD>
<NUMBER>1</NUMBER>
</PUT>
</DETAILS>
How to remove the other loop elements from where INST don't have values.Can someone help me with xslt Transformation code to sort this
<PUT>
<RECORD>ABC_PQRST0123456-001_2</RECORD>
<NUMBER>1</NUMBER>
</PUT>
This does what you describe:
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="/">
<xsl:apply-templates select="*" />
</xsl:template>
<xsl:template match="PUT[not(INST)]">
<xsl:copy>
<xsl:apply-templates select="#*|node()" />
</xsl:copy>
</xsl:template>
<!-- match but dont do anything -->
<xsl:template match="PUT">
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

XSLT merge multiple nodes under same parent

Input XML is
<Operations>
<ID>10</ID>
<UserArea>
<AdditionalPhantomInformation>
<PhantomItem>
<ItemCode>41341288</ItemCode>
<SubComponent>40241289</SubComponent>
<Position>1</Position>
</PhantomItem>
</AdditionalPhantomInformation>
</UserArea>
<ConsumedItem>
<LineNumber>3</LineNumber>
<ParentItem>40241288</ParentItem>
</ConsumedItem>
<UserArea>
<AdditionalPhantomInformation>
<PhantomItem>
<ItemCode>41341288</ItemCode>
<SubComponent>40241302</SubComponent>
<Position>5</Position>
</PhantomItem>
</AdditionalPhantomInformation>
</UserArea>
</Operations>
And my expected output is
<Operations>
<ID>10</ID>
<UserArea>
<AdditionalPhantomInformation>
<PhantomItem>
<ItemCode>41341288</ItemCode>
<SubComponent>40241289</SubComponent>
<Position>1</Position>
</PhantomItem>
<PhantomItem>
<ItemCode>41341288</ItemCode>
<SubComponent>40241302</SubComponent>
<Position>5</Position>
</PhantomItem>
</AdditionalPhantomInformation>
</UserArea>
<ConsumedItem>
<LineNumber>3</LineNumber>
<ParentItem>40241288</ParentItem>
</ConsumedItem>
</Operations>
I have searched various sources and tried, but i'm unable to get the xslt right. I don't know how to use xsl:for-each-group. Please help. I'm using XSLT 2.0.
You don't necessarily need xsl:for-each-group here, as all you are doing is combining specific nodes into one.
Start off with the identity template...
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
Or, if you could use XSLT 3.0....
<xsl:mode on-no-match="shallow-copy"/>
Then have a template matching the first UserArea which does the combining...
<xsl:template match="UserArea[1]">
<xsl:copy>
<xsl:apply-templates select="node()|following-sibling::UserArea/node()" />
</xsl:copy>
</xsl:template>
You would then just need another template to ensure the other UserArea elements are not output in their original position.
<xsl:template match="UserArea" />
Try this XSLT
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="xml" indent="yes" />
<xsl:strip-space elements="*" />
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="UserArea[1]">
<xsl:copy>
<xsl:apply-templates select="node()|following-sibling::UserArea/node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="UserArea" />
</xsl:stylesheet>
Note, if you did want to use xsl:for-each-group you would do it this way
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="xml" indent="yes" />
<xsl:strip-space elements="*" />
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Operations">
<xsl:copy>
<xsl:for-each-group select="*" group-by="name()">
<xsl:choose>
<xsl:when test="current-grouping-key() = 'UserArea'">
<xsl:copy>
<xsl:apply-templates select="current-group()/node()" />
</xsl:copy>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="current-group()" />
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
This has the advantage of being easily extended if you had nodes other than UserArea you wished to combine.

Removal of XML empty tags using XSLT [duplicate]

I am trying to recursively remove 'empty' elements (no children, no attribute or empty attribute) from the xml. This is the XSLT I have
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*[not(*) and
string-length(.)=0 and
(not(#*) or #*[string-length(.)=0])]">
<xsl:apply-templates/>
</xsl:template>
This is the input XML. I expect this XML to be transformed to a empty string
<world>
<country>
<state>
<city>
<suburb1></suburb1>
<suburb2></suburb2>
</city>
</state>
</country>
</world>
But instead I am getting
<world>
<country>
<state/>
</country>
</world>
Can anyone help? I've researched many threads in the forum but still no luck.
The condition not(*) is false for any element that has children - regardless of what the children contain.
If you want to "prune" the tree of any branches that do not carry "fruit", try:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="*[descendant::text() or descendant-or-self::*/#*[string()]]">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="#*[string()]">
<xsl:copy/>
</xsl:template>
</xsl:stylesheet>
So, fighting whitespace, try this:
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*[not(*) and
string-length(normalize-space(.))=0 and
(not(#*) or #*[string-length(normalize-space(.))=0])]">
<xsl:apply-templates/>
</xsl:template>

Remove duplicate elements based on attributes and values

There are many questions about how to remove duplicate elements when you can group those elements by a certain attribute or value, however, in my case the attributes are being dynamically generated in the XSLT already and I don't want to have to program in every attribute for every element to use as a grouping key.
How do you remove duplicate elements without knowing in advance their attributes? So far, I've tried using generate-id() on each element and grouping by that, but the problem is generate-id isn't generating the same ID for elements with the same attributes:
<xsl:template match="root">
<xsl:variable name="tempIds">
<xsl:for-each select="./*>
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:attribute name="tempID">
<xsl:value-of select="generate-id(.)"/>
</xsl:attribute>
<xsl:copy-of select="node()"/>
</xsl:copy>
</xsl:for-each>
</xsl:variable>
<xsl:for-each-group select="$tempIds" group-by="#tempID">
<xsl:sequence select="."/>
</xsl:for-each-group>
</xsl:template>
Test data:
<root>
<child1>
<etc/>
</child1>
<dynamicElement1 a="2" b="3"/>
<dynamicElement2 c="3" d="4"/>
<dynamicElement2 c="3" d="5"/>
<dynamicElement1 a="2" b="3"/>
</root>
With the end result being only one of the two dynamicElement1 elements remaining:
<root>
<child1>
<etc/>
</child1>
<dynamicElement1 a="2" b="3"/>
<dynamicElement2 c="3" d="4"/>
<dynamicElement2 c="3" d="5"/>
</root>
In XSLT 3 as shown in https://xsltfiddle.liberty-development.net/pPqsHTi you can use a composite key of all attributes with e.g.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="3.0">
<xsl:mode on-no-match="shallow-copy"/>
<xsl:output indent="yes"/>
<xsl:template match="root">
<xsl:copy>
<xsl:for-each-group select="*" composite="yes" group-by="#*">
<xsl:sequence select="."/>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Note that technically attributes are not ordered so it might be safer to group by a sort of the attributes by node-name() or similar, as done with XSLT 3 without higher-order functions in https://xsltfiddle.liberty-development.net/pPqsHTi/2
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:mf="http://example.com/mf"
version="3.0">
<xsl:mode on-no-match="shallow-copy"/>
<xsl:output indent="yes"/>
<xsl:function name="mf:node-sort" as="node()*">
<xsl:param name="input-nodes" as="node()*"/>
<xsl:perform-sort select="$input-nodes">
<xsl:sort select="namespace-uri()"/>
<xsl:sort select="local-name()"/>
</xsl:perform-sort>
</xsl:function>
<xsl:template match="root">
<xsl:copy>
<xsl:for-each-group select="*" composite="yes" group-by="mf:node-sort(#*)">
<xsl:sequence select="."/>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
or as you could do with Saxon EE simply with
<xsl:template match="root">
<xsl:copy>
<xsl:for-each-group select="*" composite="yes" group-by="sort(#*, (), function($att) { namespace-uri($att), local-name($att) })">
<xsl:sequence select="."/>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="root/*[#a= following-sibling::*/#a]|root/*[#c= following-sibling::*/#c and #d= following-sibling::*/#d]"/>
You may try this

XSLT 2.0 - passing a node set as a parameter doesn't seem to work

I have a document that I need to transform such that most elements are copied as-is, with some exceptions: for certain specified nodes, child elements need to be appended, and some of these child elements need to reference back to specific elements in the source document. A separate "model/crosswalk" xml file contains the elements to add. The elements in the crosswalk that need to refer back to the source document have "source" attributes that point them to specific elements. Of course, my actual source docs (& the actual crosswalk) are more complex and varied than these examples.
Here is a source document example:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<album>
<artist>Frank Sinatra</artist>
<title>Greatest Hits</title>
</album>
<album>
<artist>Miles Davis</artist>
<title>Kind Of Blue</title>
</album>
<movie>
<title>ET</title>
<director>Steven Spielberg</director>
</movie>
<movie>
<title>Blues Brothers</title>
<director>John Landis</director>
</movie>
</root>
Here is the "crosswalk" (crswlk.xml):
<?xml version="1.0" encoding="UTF-8"?>
<root>
<album>
<artist-info>
<artist2 source="artist"/>
</artist-info>
</album>
<movie>
<director-info>
<director2 source="director"/>
</director-info>
</movie>
</root>
And here is the desired output:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<album>
<artist>Frank Sinatra</artist>
<title>Greatest Hits</title>
<artist-info>
<artist2>Frank Sinatra</artist2>
</artist-info>
</album>
<album>
<artist>Miles Davis</artist>
<title>Kind Of Blue</title>
<artist-info>
<artist2>Miles Davis</artist2>
</artist-info>
</album>
<movie>
<title>ET</title>
<director>Steven Spielberg</director>
<director-info>
<director2>Steven Spielberg</director2>
</director-info>
</movie>
<movie>
<title>Blues Brothers</title>
<director>John Landis</director>
<director-info>
<director2>John Landis</director2>
</director-info>
</movie>
</root>
Here's my xslt:
<?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="crosswalk" select="document('crswlk.xml')"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*/album|movie">
<xsl:variable name="theNode" select="."/>
<xsl:variable name="nodeName" select="name()"/>
<xsl:element name="{$nodeName}">
<xsl:apply-templates select="#* | node()"/>
<xsl:apply-templates select="$crosswalk//*[name()=$nodeName]/*">
<xsl:with-param name="curNode" select="$theNode"/>
</xsl:apply-templates>
</xsl:element>
</xsl:template>
<xsl:template match="*[#source]">
<xsl:param name="curNode" />
<xsl:variable name="sourceNodeName" select="#source"/>
<xsl:element name="{name()}">
<xsl:value-of select="$curNode//*[name()=$sourceNodeName]"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
This stops processing once that last template tries to access $curNode for the first time. When I run the xslt in Eclipse (Xalan 2.7.1) it throws this error: " java.lang.ClassCastException: org.apache.xpath.objects.XString cannot be cast to org.apache.xpath.objects.XNodeSet".
If I pass a similar nodeset as a parameter to a template that matches nodes from the source document, it works as expected - the nodeset is accessible. However, passing the nodeset to the last template above doesn't work. Is it because the template matches nodes from the external document? I sure don't know. Any help much appreciated, it took me a while just to get to this point. Thanks!
Looks like you need to change 2 things:
add tunnel="yes" to xsl:with-param and xsl:param
remove the apostrophes from '$sourceNodeName' in the predicate of the xsl:value-of
Updated XSLT:
<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="crosswalk" select="document('crswlk.xml')"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*/album|movie">
<xsl:variable name="theNode" select="."/>
<xsl:variable name="nodeName" select="name()"/>
<xsl:element name="{$nodeName}">
<xsl:apply-templates select="#* | node()"/>
<xsl:apply-templates select="$crosswalk//*[name()=$nodeName]/*">
<xsl:with-param name="curNode" select="$theNode" tunnel="yes"/>
</xsl:apply-templates>
</xsl:element>
</xsl:template>
<xsl:template match="*[#source]">
<xsl:param name="curNode" tunnel="yes"/>
<xsl:variable name="sourceNodeName" select="#source"/>
<xsl:element name="{name()}">
<xsl:value-of select="$curNode//*[name()=$sourceNodeName]"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
Also, you can remove a few of those extra xsl:variables...
<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="crosswalk" select="document('crswlk.xml')"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="album|movie">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
<xsl:apply-templates select="$crosswalk/*/*[name()=current()/name()]/*">
<xsl:with-param name="curNode" select="." tunnel="yes"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
<xsl:template match="*[#source]">
<xsl:param name="curNode" tunnel="yes"/>
<xsl:copy>
<xsl:value-of select="$curNode//*[name()=current()/#source]"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
If you are using XSLT 2.0, then Daniel Haley's answer with tunnelling is surely the way to go. If, however, you are actually using Xalan, and therefore only XSLT 1.0, you need to take a different approach.
The problems start on this line:
<xsl:apply-templates select="$crosswalk//*[name()=$nodeName]/*">
This will select either artist-info or director-info in your cross walk document, but you have no specific template matching these, so the generic identity template you are using will match them
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
But this does not take any parameter, not does it pass any parameters on. Therefore, when your last template <xsl:template match="*[#source]"> is matched, the curNode will be empty (an empty string), which is not a node-set, and that saddens Xalan.
So, to solve this in XSLT1.0, just add the parameter to the identity template, and pass it on:
<xsl:template match="node()|#*">
<xsl:param name="curNode" />
<xsl:copy>
<xsl:apply-templates select="#* | node()">
<xsl:with-param name="curNode" select="$curNode" />
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
If the template is ever matched when there is no parameter being passed, it will just pass on an empty parameter without any issue.
Here is the full XSLT (also with the correction of apostrophes being removed from the xsl:value-of, as mentioned in Daniel's answer).
<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="crosswalk" select="document('crswlk.xml')"/>
<xsl:template match="node()|#*">
<xsl:param name="curNode" />
<xsl:copy>
<xsl:apply-templates select="#* | node()">
<xsl:with-param name="curNode" select="$curNode" />
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
<xsl:template match="*/album|movie">
<xsl:variable name="theNode" select="."/>
<xsl:variable name="nodeName" select="name()"/>
<xsl:element name="{$nodeName}">
<xsl:apply-templates select="#* | node()"/>
<xsl:apply-templates select="$crosswalk//*[name()=$nodeName]/*">
<xsl:with-param name="curNode" select="$theNode"/>
</xsl:apply-templates>
</xsl:element>
</xsl:template>
<xsl:template match="*[#source]">
<xsl:param name="curNode" />
<xsl:variable name="sourceNodeName" select="#source"/>
<xsl:element name="{name()}">
<xsl:value-of select="$curNode//*[name()=$sourceNodeName]"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
When applied to your XML documents, the following is output
<root>
<album>
<artist>Frank Sinatra</artist>
<title>Greatest Hits</title>
<artist-info>
<artist2>Frank Sinatra</artist2>
</artist-info>
</album>
<album>
<artist>Miles Davis</artist>
<title>Kind Of Blue</title>
<artist-info>
<artist2>Miles Davis</artist2>
</artist-info>
</album>
<movie>
<title>ET</title>
<director>Steven Spielberg</director>
<director-info>
<director2>Steven Spielberg</director2>
</director-info>
</movie>
<movie>
<title>Blues Brothers</title>
<director>John Landis</director>
<director-info>
<director2>John Landis</director2>
</director-info>
</movie>
</root>