I need to transform following xml as Expected Output using xsl 3.0 accumulators.
Input XML:
<AggregatedData>
<Data>
<Entry>
<legacyID>ABC</legacyID>
<legacyLocation>Test_Loc1,Test_Loc2</legacyLocation>
<AssociateID>123</AssociateID>
</Entry>
<Entry>
<legacyID>ABC</legacyID>
<legacyLocation>Test_Loc3</legacyLocation>
<AssociateID>123</AssociateID>
</Entry>
<Entry>
<legacyID>CDE</legacyID>
<legacyLocation>Test_Loc4,Test_Loc5</legacyLocation>
<AssociateID>456</AssociateID>
</Entry>
</Data>
<root>
<row>
<legacyID>ABC</legacyID>
<legacyLocation>Test_Loc1</legacyLocation>
<company>Test Company 1</company>
<firstname>Test1</firstname>
</row>
<row>
<legacyID>CDE</legacyID>
<legacyLocation>Test_Loc5</legacyLocation>
<company>Test Company 2</company>
<firstname>Test2</firstname>
</row>
</root>
</AggregatedData>
Values under <Data>
can contain comma separated values for <legacyLocation>
and values under <root>
only contains one value for <legacyLocation>
. I need to map these values and get the output as the Expected Output below. Is there a way to map values using XSLT 3.0 accumulators using both legacyID and legacyLocation?
Expected Output:
<root>
<worker>
<row>
<AssociateID>123</AssociateID>
<legacyID>ABC</legacyID>
<legacyLocation>Test_Loc1</legacyLocation>
<company>Test Company 1</company>
<firstname>Test1</firstname>
</row>
<row>
<AssociateID>456</AssociateID>
<legacyID>CDE</legacyID>
<legacyLocation>Test_Loc5</legacyLocation>
<company>Test Company 2</company>
<firstname>Test2</firstname>
</row>
</worker>
</root>
I think you can use a key like
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all"
version="3.0">
<xsl:key name="assoc-id" match="Entry" use="(legacyLocation => tokenize(',') => sort()) ! (current()/legacyID || ',' || .)"/>
<xsl:mode on-no-match="shallow-copy"/>
<xsl:template match="/AggregatedData">
<xsl:apply-templates select="root"/>
</xsl:template>
<xsl:template match="row">
<xsl:copy>
<xsl:apply-templates select="key('assoc-id', (legacyID || ',' || legacyLocation))/AssociateID, node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
I don't see why an accumulator would perform better, I would mainly try to use it if you to need to cross-reference with streaming.
A non-streaming use of an accumulator would be
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
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"
version="3.0">
<xsl:accumulator name="assoc-id" as="map(xs:string, xs:string)" initial-value="map{}">
<xsl:accumulator-rule
match="Entry"
select="let $entry := .
return fold-left(legacyLocation => tokenize(',') => sort(), $value, function($a, $k) { map:put($a, $entry/legacyID || ',' || $k, $entry/AssociateID/string()) })"/>
</xsl:accumulator>
<xsl:mode on-no-match="shallow-copy" use-accumulators="assoc-id"/>
<xsl:template match="/AggregatedData">
<xsl:apply-templates select="root"/>
</xsl:template>
<xsl:template match="row">
<xsl:copy>
<AssociateID>{accumulator-before('assoc-id')(legacyID || ',' || legacyLocation)}</AssociateID>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>