xmlxsltxslt-3.0accumulator

Mapping Values Using XSL 3.0 accumulators


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>

Solution

  • 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>