XSLT 1.0: grouping positive and negative values with an xsl:key - xslt

I have this source XML and I have to group the <SpinRecord> elements by <IndirectCust> and <SalesVolume>:
<?xml version="1.0" encoding="UTF-8"?>
<SI_CSV_SPIN_OUT>
<Recordset>
<SpinRecord>
<IndirectCust>1080360</IndirectCust>
<SalesVolume>-2</SalesVolume>
</SpinRecord>
<SpinRecord>
<IndirectCust>1080360</IndirectCust>
<SalesVolume>-1</SalesVolume>
</SpinRecord>
<SpinRecord>
<IndirectCust>1080360</IndirectCust>
<SalesVolume>10</SalesVolume>
</SpinRecord>
<SpinRecord>
<IndirectCust>123</IndirectCust>
<SalesVolume>10</SalesVolume>
</SpinRecord>
<SpinRecord>
<IndirectCust>123</IndirectCust>
<SalesVolume>-9</SalesVolume>
</SpinRecord>
<SpinRecord>
<IndirectCust>123</IndirectCust>
<SalesVolume>8</SalesVolume>
</SpinRecord>
</Recordset>
</SI_CSV_SPIN_OUT>
I came up with this XSLT:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xd="http://www.oxygenxml.com/ns/doc/xsl"
exclude-result-prefixes="xd"
version="1.0">
<xsl:output indent="yes" method="xml"/>
<!-- define the key to define unique elements -->
<xsl:key name="keyCustGroup" match="SpinRecord" use="concat(IndirectCust, '|', self::*[SalesVolume > 0])"/>
<!-- define which elements are unique -->
<xsl:template match="/*/*">
<xsl:variable name="uniqueTransactions" select="SpinRecord[generate-id()=generate-id(key('keyCustGroup',concat(IndirectCust, '|', self::*[SalesVolume > 0]))[1])]"/>
<SI_CSV_SPIN_OUT>
<xsl:apply-templates select="$uniqueTransactions" mode="group"/>
</SI_CSV_SPIN_OUT>
</xsl:template>
<!-- create the unique groups -->
<xsl:template match="SpinRecord" mode="group">
<SpinRecords>
<xsl:apply-templates select="key('keyCustGroup',concat(IndirectCust, '|', self::*[SalesVolume > 0]))" mode="item" />
</SpinRecords>
</xsl:template>
<!-- write the item content into each group -->
<xsl:template match="SpinRecord" mode="item">
<SpinRecord>
<xsl:copy-of select="child::*"/>
</SpinRecord>
</xsl:template>
</xsl:stylesheet>
But it is not working 100%, I am getting this output and for <IndirectCust> 123 the positive values are not grouped together:
<?xml version="1.0" encoding="UTF-8"?>
<SI_CSV_SPIN_OUT>
<SpinRecords>
<SpinRecord>
<IndirectCust>1080360</IndirectCust>
<SalesVolume>-2</SalesVolume>
</SpinRecord>
<SpinRecord>
<IndirectCust>1080360</IndirectCust>
<SalesVolume>-1</SalesVolume>
</SpinRecord>
</SpinRecords>
<SpinRecords>
<SpinRecord>
<IndirectCust>1080360</IndirectCust>
<SalesVolume>10</SalesVolume>
</SpinRecord>
</SpinRecords>
<SpinRecords>
<SpinRecord>
<IndirectCust>123</IndirectCust>
<SalesVolume>10</SalesVolume>
</SpinRecord>
</SpinRecords>
<SpinRecords>
<SpinRecord>
<IndirectCust>123</IndirectCust>
<SalesVolume>-9</SalesVolume>
</SpinRecord>
</SpinRecords>
<SpinRecords>
<SpinRecord>
<IndirectCust>123</IndirectCust>
<SalesVolume>8</SalesVolume>
</SpinRecord>
</SpinRecords>
</SI_CSV_SPIN_OUT>
My expected output would be the following:
<?xml version="1.0" encoding="UTF-8"?>
<SI_CSV_SPIN_OUT>
<SpinRecords>
<SpinRecord>
<IndirectCust>1080360</IndirectCust>
<SalesVolume>-2</SalesVolume>
</SpinRecord>
<SpinRecord>
<IndirectCust>1080360</IndirectCust>
<SalesVolume>-1</SalesVolume>
</SpinRecord>
</SpinRecords>
<SpinRecords>
<SpinRecord>
<IndirectCust>1080360</IndirectCust>
<SalesVolume>10</SalesVolume>
</SpinRecord>
</SpinRecords>
<SpinRecords>
<SpinRecord>
<IndirectCust>123</IndirectCust>
<SalesVolume>10</SalesVolume>
</SpinRecord>
<SpinRecord>
<IndirectCust>123</IndirectCust>
<SalesVolume>8</SalesVolume>
</SpinRecord>
</SpinRecords>
<SpinRecords>
<SpinRecord>
<IndirectCust>123</IndirectCust>
<SalesVolume>-9</SalesVolume>
</SpinRecord>
</SpinRecords>
</SI_CSV_SPIN_OUT>
I am struggling with the <xsl:key>. Do you have any idea how to make this work? Do I have to use two keys, one for positive and one for negative <SalesVolume>?
Thank you, Peter

Could you not do simply:
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="k1" match="SpinRecord" use="concat(IndirectCust, '|', SalesVolume > 0)"/>
<xsl:template match="/SI_CSV_SPIN_OUT">
<xsl:copy>
<xsl:for-each select="Recordset/SpinRecord[count(. | key('k1', concat(IndirectCust, '|', SalesVolume > 0))[1]) = 1]">
<SpinRecords>
<xsl:copy-of select="key('k1', concat(IndirectCust, '|', SalesVolume > 0))"/>
</SpinRecords>
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

Related

How to handle JSON input with Saxon XSLT

Recent Saxon releases contain a command line argument "-json:myfile.json" to input a JSON file.
But how do I implement the XSLT to parse this JSON? I did not find any doc which handles this directly (without making use of "json-to-xml" or similar).
I only found this: how to convert json to xml with saxonjs?
But this does not help me, because my json starts with an array:
[
{
"eid": "2122.5",
"ecat": "show",
"day": "1629410400",
"spcat": "Bühne",
"time": "19:30",
"text": "Welle",
"remarks": "",
"location": ""
},
{
"eid": "2122.6",
"ecat": "show",
"day": "1629496800",
"spcat": "Bühne",
"time": "19:30",
"text": "Welle",
"remarks": "",
"location": ""
}
]
By writing an intermediate XSLT using the function "json-to-xml", I can convert this JSON to xml that looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<array xmlns="http://www.w3.org/2005/xpath-functions">
<map>
<string key="eid">2122.5</string>
<string key="ecat">show</string>
<string key="day">1629410400</string>
<string key="spcat">Bühne</string>
<string key="time">19:30</string>
<string key="text">Welle</string>
<string key="remarks"/>
<string key="location"/>
</map>
<map>
<string key="eid">2122.6</string>
<string key="ecat">show</string>
<string key="day">1629496800</string>
<string key="spcat">Bühne</string>
<string key="time">19:30</string>
<string key="text">Welle</string>
<string key="remarks"/>
<string key="location"/>
</map>
</array>
How can I create a template that matches the root item, and how can I call "apply-templates" to trigger another template that handles the items?
The JSON you have shown is an array so it will be mapped to the XPath/XSLT XDM type array(*), or, in your case, array(map(xs:string, xs:string)). To match on that use e.g. <xsl:template match=".[. instance of array(*)]">..</xsl:template> or e.g. <xsl:template match=".[. instance of array(map(xs:string, xs:string))]">..</xsl:template>.
More complete example would be online at the fiddle, doing:
<?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"
xmlns:map="http://www.w3.org/2005/xpath-functions/map"
exclude-result-prefixes="#all"
expand-text="yes">
<xsl:output method="xml" indent="yes"/>
<xsl:template match=".[. instance of array(map(xs:string, xs:string))]">
<items>
<xsl:apply-templates select="?*"/>
</items>
</xsl:template>
<xsl:template match=".[. instance of map(xs:string, xs:string)]">
<xsl:variable name="map" select="."/>
<item>
<xsl:iterate select="map:keys(.)">
<value key="{.}">{$map(.)}</value>
</xsl:iterate>
</item>
</xsl:template>
</xsl:stylesheet>
As for grouping:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="3.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:map="http://www.w3.org/2005/xpath-functions/map"
exclude-result-prefixes="#all"
expand-text="yes">
<xsl:output method="xml" indent="yes"/>
<xsl:template match=".[. instance of array(map(xs:string, xs:string))]">
<items>
<xsl:for-each-group select="?*" group-by="?text">
<group name="{current-grouping-key()}">
<xsl:apply-templates select="current-group()"/>
</group>
</xsl:for-each-group>
</items>
</xsl:template>
<xsl:template match=".[. instance of map(xs:string, xs:string)]">
<xsl:variable name="map" select="."/>
<item>
<xsl:iterate select="map:keys(.)[not(. = current-grouping-key())]">
<value key="{.}">{$map(.)}</value>
</xsl:iterate>
</item>
</xsl:template>
</xsl:stylesheet>

XSLT Tranformation of XML file containing CDATA

I have to transform an xml file using XSLT.
I had to get in the output the some structure of the input file file, expected some changes in some elements exsiting on a CDATA element.
<soapenv:Envelope xmlns:aa="http://example.com"
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Header/>
<soapenv:Body>
<aa:importOrder>
<aa:orderNumber>00501010000342</aa:orderNumber>
<aa:data>
<![CDATA[
<?xml version="1.0" encoding="UTF-8"?>
<OrderImport xmlns="http://test.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema">
<OrderNumber>00501010000342</OrderNumber>
<Application>
<Student>
<DataElement>
<Name>age</Name>
<Type>Int</Type>
<Value>13</Value>
</DataElement>
<DataElement>
<Name>firstName</Name>
<Type>String</Type>
<Value>taha</Value>
</DataElement>
</Student>
</Application>
</OrderImport>
]]>
</aa:data>
</aa:importOrder>
</soapenv:Body>
</soapenv:Envelope>
My expected output should be like this:
<soapenv:Envelope xmlns:aa="http://example.com"
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Header/>
<soapenv:Body>
<aa:importOrder>
<aa:orderNumber>00501010000342</aa:orderNumber>
<aa:data>
<![CDATA[
<?xml version="1.0" encoding="UTF-8"?>
<OrderImport xmlns="http://test.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema">
<OrderNumber>00501010000342</OrderNumber>
<Application>
<Student>
<DataElement>
<Name>age</Name>
<Type>Int</Type>
**<Value>Other Value</Value>**
</DataElement>
<DataElement>
<Name>firstName</Name>
<Type>String</Type>
**<Value>Other Value</Value>**
</DataElement>
</Student>
</Application>
</OrderImport>
]]>
</aa:data>
</aa:importOrder>
</soapenv:Body>
</soapenv:Envelope>
For that i used this xsl file:
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="DataElement">
<p>
<xsl:apply-template select="name"/>
</p>
</xsl:template>
<xsl:template match="Name | Type">
<xsl:value-of select="."/>
</xsl:template>
<xsl:template match="Value">
Other Value
</xsl:template>
but I didn't had the expected output.
Thanks in Advance
To parse the inner XML in the CDATA you can use parse-xml, then push the nodes through templates that change the element value, then reserialize and make sure to define the aa:data as a CDATA section 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"
xmlns:aa="http://example.com"
exclude-result-prefixes="#all"
version="3.0">
<xsl:mode on-no-match="shallow-copy"/>
<xsl:output method="xml" cdata-section-elements="aa:data"/>
<xsl:template match="aa:data">
<xsl:copy>
<xsl:variable name="transformed">
<xsl:apply-templates select="parse-xml(replace(., '^\s+', ''))/node()"/>
</xsl:variable>
<xsl:value-of select="serialize($transformed, map { 'method' : 'xml', 'omit-xml-declaration' : false() })"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Value" xpath-default-namespace="http://test.com">
<xsl:copy>Other Value</xsl:copy>
</xsl:template>
</xsl:stylesheet>

Grouping and Sum of XML nodes using xslt

I am pretty new in XSLT and I am stuck at once place.
I have one input xml file as below
<Roots>
<Root>
<Value1>Links1</Value1>
<Value2>notadmin</Value2>
<Count>1</Count>
</Root>
<Root>
<Value1>Links1</Value1>
<Value2>notadmin</Value2>
<Count>10</Count>
</Root>
<Root>
<Value1>Links2</Value1>
<Value2>userxyz</Value2>
<Count>10</Count>
</Root>
<Root>
<Value1>Links2</Value1>
<Value2>usermnp</Value2>
<Count>10</Count>
</Root>
<Root>
<Value1>Links3</Value1>
<Value2>user123</Value2>
<Count>5</Count>
</Root>
...
...
...
</Roots>
Now, I want to sum the count value for similar values of Value1 and Value2 so that it looks like below:
<Roots>
<Root>
<Value1>Links1</Value1>
<Value2>notadmin</Value2>
<Count>11</Count>
</Root>
<Root>
<Value1>Links2</Value1>
<Value2>userxyz</Value2>
<Count>20</Count>
</Root>
<Root>
<Value1>Links3</Value1>
<Value2>user123</Value2>
<Count>5</Count>
</Root>
</Roots>
I have been trying a lot to transform this xml and here is my code:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs" >
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Roots">
<Roots>
<Root>
<xsl:for-each-group select="Root" group-by=
"concat(Value1, '+', Value2)">
<xsl:copy-of select=
"current-group()[1]/*[starts-with(name(),'key')]"/>
<Count>
<xsl:value-of select="sum(current-group()/Count)"/>
</Count>
</xsl:for-each-group>
</Root>
</Roots>
</xsl:template>
</xsl:stylesheet>
I know I am doing wrong and could be lot easier to the expert but I really need some help and direction.
Thanks
Your XSLT was close, but move the <Root> element definition into the xsl:for-each-group. I couldn't make sense of your xsl:copy-of..., so I just reproduced the elements one-by-one. So use this simple XSLT-2.0 stylesheet:
<?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" omit-xml-declaration="yes" />
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="/Roots">
<xsl:copy>
<xsl:apply-templates select="#*" />
<xsl:for-each-group select="Root" group-by="Value1">
<Root>
<Value1><xsl:value-of select="current-grouping-key()" /></Value1>
<Value2><xsl:value-of select="current-group()[1]/Value2" /></Value2>
<Count><xsl:value-of select="sum(current-group()/Count)" /></Count>
</Root>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
It's output is:
<Roots>
<Root>
<Value1>Links1</Value1>
<Value2>notadmin</Value2>
<Count>11</Count>
</Root>
<Root>
<Value1>Links2</Value1>
<Value2>userxyz</Value2>
<Count>20</Count>
</Root>
<Root>
<Value1>Links3</Value1>
<Value2>user123</Value2>
<Count>5</Count>
</Root>
</Roots>
Right now, your grouping concat(Value1, '+', Value2) will not yield three nodes since nodes at Links2 maintains two different value2 node values. Also, your identity transform template is redundant since you rewrite the XML tree directly from root, Roots.
One Grouping (only Value1)
<?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" version="2.0" exclude-result-prefixes="xs">
<xsl:output omit-xml-declaration="yes" indent="yes" />
<xsl:template match="Roots">
<Roots>
<xsl:for-each-group select="Root" group-by="Value1">
<Root>
<xsl:copy-of select="Value1|Value2"/>
<Count>
<xsl:value-of select="sum(current-group()/Count)" />
</Count>
</Root>
</xsl:for-each-group>
</Roots>
</xsl:template>
</xsl:stylesheet>
Returns with first value2 returned:
<Roots>
<Root>
<Value1>Links1</Value1>
<Value2>notadmin</Value2>
<Count>11</Count>
</Root>
<Root>
<Value1>Links2</Value1>
<Value2>userxyz</Value2>
<Count>20</Count>
</Root>
<Root>
<Value1>Links3</Value1>
<Value2>user123</Value2>
<Count>5</Count>
</Root>
</Roots>
Two Groupings (Value1 and Value2)
Keeping original grouping:
<xsl:for-each-group select="Root" group-by="concat(Value1, '+', Value2)">
Returns four nodes
<Roots>
<Root>
<Value1>Links1</Value1>
<Value2>notadmin</Value2>
<Count>11</Count>
</Root>
<Root>
<Value1>Links2</Value1>
<Value2>userxyz</Value2>
<Count>10</Count>
</Root>
<Root>
<Value1>Links2</Value1>
<Value2>usermnp</Value2>
<Count>10</Count>
</Root>
<Root>
<Value1>Links3</Value1>
<Value2>user123</Value2>
<Count>5</Count>
</Root>
</Roots>

Removing empty element and attribute

I have an below incoming XML request i want to remove empty node like "altname"
Incoming XML:
<saml2:Assertion Version="2.0" ID="SAML-727af5f8-6bd0-45f8-b9c0-c95e3c5da9f5" IssueInstant="2016-01-31T13:27:28Z" xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion">
<saml2:Issuer>ssodp</saml2:Issuer>
<saml2:AttributeStatement>
<saml2:Attribute Name="altname" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
<saml2:AttributeValue/>
</saml2:Attribute>
<saml2:Attribute Name="ID" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
<saml2:AttributeValue>5345435345</saml2:AttributeValue>
</saml2:Attribute>
</saml2:AttributeStatement>
</saml2:Assertion>
I have written below XSLT:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*[ not(descendant-or-self::node()[normalize-space()]) and
not(descendant-or-self::*/#*[normalize-space()] and not(count(descendant-or-self::*/#*) = count(descendant-or-self::*/#xsi:nil)) ) ]">
<xsl:if test="local-name(.) = 'Envelope' or local-name(.) = 'Body' or local-name(.) = 'payload'">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
It's not working as expected.
I need the below XML output:
Desired XML Output:
<saml2:Assertion Version="2.0" ID="SAML-727af5f8-6bd0-45f8-b9c0-c95e3c5da9f5" IssueInstant="2016-01-31T13:27:28Z" xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion">
<saml2:Issuer>ssodp</saml2:Issuer>
<saml2:AttributeStatement>
<saml2:Attribute Name="ID" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
<saml2:AttributeValue>5345435345</saml2:AttributeValue>
</saml2:Attribute>
</saml2:AttributeStatement>
</saml2:Assertion>
Please let me know how to do this.
You were right in using the identity template.
Just filtering out the nodes you don't want will give you your desired result.
So just ignoring the nodes which have an empty saml2:AttributeValue/text()-node with a simple empty template is sufficient:
<xsl:template match="saml2:Attribute[normalize-space(saml2:AttributeValue/text()) = '']" />
So use this XSLT - which has only minor modifications compared to yours:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion">
<!-- identity template -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<!-- filter out the nodes with an empty 'saml2:AttributeValue' child -->
<xsl:template match="saml2:Attribute[normalize-space(saml2:AttributeValue/text()) = '']" />
</xsl:stylesheet>
Result:
<?xml version="1.0"?>
<saml2:Assertion xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion" Version="2.0" ID="SAML-727af5f8-6bd0-45f8-b9c0-c95e3c5da9f5" IssueInstant="2016-01-31T13:27:28Z">
<saml2:Issuer>ssodp</saml2:Issuer>
<saml2:AttributeStatement>
<saml2:Attribute Name="ID" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
<saml2:AttributeValue>5345435345</saml2:AttributeValue>
</saml2:Attribute>
</saml2:AttributeStatement>
</saml2:Assertion>

how to modify one xml input using variable from second xml input in XSLT?

My question is how to change values in <color> in sample1.xml based on the same <id> in sample2.xml
sample1.xml
<?xml version="1.0" encoding="UTF-8"?>
<root>
<item>
<id>1</id>
<color>red</color>
</item>
<item>
<id>2</id>
<color>blue</color>
</item>
<item>
<id>3</id>
<color>green></color>
</item>
</root>
sample 2.xml
<?xml version="1.0" encoding="UTF-8"?>
<root>
<item>
<id>1</id>
<color>yellow</color>
</item>
<item>
<id>3</id>
<color>white</color>
</item>
</root>
expected output
<?xml version="1.0" encoding="UTF-8"?>
<root>
<item>
<id>1</id>
<color>yellow</color>
</item>
<item>
<id>2</id>
<color>blue</color>
</item>
<item>
<id>3</id>
<color>white></color>
</item>
</root>
I only know how to copy entire sample1.xml to output, but I don't know how to remember ids from sample2.xml, and by that values make changes to sample1.
Don't know if is possible, but probably I must use variables on some unknown way.
Here is my 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" xmlns:saxon="http://saxon.sf.net/" extension-element-prefixes="saxon"
version="2.0">
<xsl:output method="xml" indent="yes" media-type="text/xml" />
<xsl:param name="sample1"/>
<xsl:param name="sample1_xml" select="saxon:parse($sample1)"/>
<xsl:param name="sample2"/>
<xsl:param name="sample2_xml" select="saxon:parse($sample2)"/>
<xsl:template match="/" name="initial">
<xsl:apply-templates select="$sample1_xml/node()"/> <!-- this is only for copying entire sample1 file -->
</xsl:template>
<!-- copy all nodes and values -->
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
I really don't have idea what is the right way to do that, because I am new to XSLT 2.0. Any help will be much appreciated.
I was beaten to this by Martin Honnen, but here's my solution:
<?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" xmlns:saxon="http://saxon.sf.net/" extension-element-prefixes="saxon"
version="2.0">
<xsl:output method="xml" indent="yes" media-type="text/xml"/>
<xsl:param name="sample1"/>
<xsl:param name="sample1_xml" select="saxon:parse($sample1)"/>
<xsl:param name="sample2"/>
<xsl:param name="sample2_xml" select="saxon:parse($sample2)"/>
<xsl:template match="/" name="initial">
<xsl:apply-templates select="$sample1_xml/node()"/>
<!-- this is only for copying entire sample1 file -->
</xsl:template>
<!-- copy all nodes and values -->
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/root/item/color">
<xsl:param name="id" select="parent::item/id/text()"/>
<xsl:copy>
<xsl:choose>
<xsl:when test="$sample2_xml/root/item[id=$id]/color">
<xsl:value-of select="$sample2_xml/root/item[id=$id]/color/text()"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="text()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:copy>
</xsl:template>
Define a key <xsl:key name="id" match="item" use="id"/> and then add a template
<xsl:template match="item[key('id', id, $sample2_xml)]/color">
<xsl:copy-of select="key('id', ../id, $sample2_xml)/color"/>
</xsl:template>
So the complete sample is
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs saxon"
xmlns:saxon="http://saxon.sf.net/"
version="2.0">
<xsl:output method="xml" indent="yes" media-type="text/xml" />
<xsl:param name="sample1" as="xs:string"><![CDATA[<root>
<item>
<id>1</id>
<color>red</color>
</item>
<item>
<id>2</id>
<color>blue</color>
</item>
<item>
<id>3</id>
<color>green></color>
</item>
</root>]]></xsl:param>
<xsl:param name="sample1_xml" select="saxon:parse($sample1)"/>
<xsl:param name="sample2" as="xs:string"><![CDATA[<?xml version="1.0" encoding="UTF-8"?>
<root>
<item>
<id>1</id>
<color>yellow</color>
</item>
<item>
<id>3</id>
<color>white</color>
</item>
</root>]]></xsl:param>
<xsl:param name="sample2_xml" select="saxon:parse($sample2)"/>
<xsl:key name="id" match="item" use="id"/>
<xsl:template match="/" name="initial">
<xsl:apply-templates select="$sample1_xml/node()"/> <!-- this is only for copying entire sample1 file -->
</xsl:template>
<!-- copy all nodes and values -->
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="item[key('id', id, $sample2_xml)]/color">
<xsl:copy-of select="key('id', ../id, $sample2_xml)/color"/>
</xsl:template>
</xsl:stylesheet>