Home > Net >  Mapping Values Using XSL 3.0 accumulators
Mapping Values Using XSL 3.0 accumulators

Time:06-10

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>

NEW XML

<AggregatedData>
   <wd:Data>
      <wd:Entry>
         <wd:legacyID>ABC</wd:legacyID>
         <wd:legacyLocation>Test_Loc1,Test_Loc2</wd:legacyLocation>
         <wd:AssociateID>123</wd:AssociateID>
      </wd:Entry>
      <wd:Entry>
         <wd:legacyID>ABC</wd:legacyID>
         <wd:legacyLocation>Test_Loc3</wd:legacyLocation>
         <wd:AssociateID>123</wd:AssociateID>
      </wd:Entry>
      <wd:Entry>
         <wd:legacyID>CDE</wd:legacyID>
         <wd:legacyLocation>Test_Loc4,Test_Loc5</wd:legacyLocation>
         <wd:AssociateID>456</wd:AssociateID>
      </wd:Entry>
   </wd: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>

CodePudding user response:

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