xsltxslt-2.0xslt-3.0

XSLT to reassign Sequence Number based on Key Field Value


I have a requirement to reassign the sequence number /MainHeader/Record/POSN with 8001,8002.... when we have two segments which has same value in the field /VAR, If /Child -- > segment exists under /Record then same sequence number must be copied to the field /POSN inside /Child .. my XSLT is by default generating sequence number for all records.

Sample input and output are attached.

Input:

<?xml version='1.0' encoding='UTF-8'?>
<MainHeader>
    <Record SEGMENT="1">
        <POSN>1000</POSN>
        <VAR>1234</VAR>
        <field>41</field>
        <Subsegment SEGMENT="1">
          <Qua>value1</Qua>
        </Subsegment>
        <Subsegment SEGMENT="1">
          <Qua>Value2</Qua>
        </Subsegment>
      </Record>
      <Record SEGMENT="1">
        <POSN>2000</POSN>
        <VAR>45678</VAR>
        <field>41</field>
        <Child SEGMENT="1">
          <POSN>2000</POSN>
          <Union>UN</Union>
         </Child>
        <Subsegment SEGMENT="1">
          <Qua>value1</Qua>
        </Subsegment>
        <Subsegment SEGMENT="1">
          <Qua>Value2</Qua>
        </Subsegment>
      </Record>
      <Record SEGMENT="1">
        <POSN>2000</POSN>
        <VAR>45678</VAR>
        <field>42</field>
        <Child SEGMENT="1">
          <POSN>2000</POSN>
          <Union>UN</Union>
         </Child>
        <Subsegment SEGMENT="1">
          <Qua>value1</Qua>
        </Subsegment>
        <Subsegment SEGMENT="1">
          <Qua>Value2</Qua>
        </Subsegment>
      </Record>
        <Record SEGMENT="1">
        <POSN>3000</POSN>
        <VAR>778899</VAR>
        <field>43</field>
        <Child SEGMENT="1">
          <POSN>3000</POSN>
          <Union>UN</Union>
         </Child>
        <Subsegment SEGMENT="1">
          <Qua>value1</Qua>
        </Subsegment>
        <Subsegment SEGMENT="1">
          <Qua>Value2</Qua>
        </Subsegment>
      </Record>
          <Record SEGMENT="1">
        <POSN>4000</POSN>
        <VAR>778899</VAR>
        <field>44</field>
         <Subsegment SEGMENT="1">
          <Qua>value1</Qua>
        </Subsegment>
        <Subsegment SEGMENT="1">
          <Qua>Value2</Qua>
        </Subsegment>
      </Record>
          <Record SEGMENT="1">
        <POSN>5000</POSN>
        <VAR>118899</VAR>
        <field>45</field>
         <Subsegment SEGMENT="1">
          <Qua>value1</Qua>
        </Subsegment>
        <Subsegment SEGMENT="1">
          <Qua>Value2</Qua>
        </Subsegment>
      </Record>
      
      
</MainHeader>

** Desired Output:**

<?xml version='1.0' encoding='UTF-8'?>
<MainHeader>
    <Record SEGMENT="1">
        <POSN>1000</POSN>
        <VAR>1234</VAR>
        <field>41</field>
        <Subsegment SEGMENT="1">
          <Qua>value1</Qua>
        </Subsegment>
        <Subsegment SEGMENT="1">
          <Qua>Value2</Qua>
        </Subsegment>
      </Record>
      <Record SEGMENT="1">
        <POSN>8001</POSN>
        <VAR>45678</VAR>
        <field>41</field>
        <Child SEGMENT="1">
          <POSN>8001</POSN>
          <Union>UN</Union>
         </Child>
        <Subsegment SEGMENT="1">
          <Qua>value1</Qua>
        </Subsegment>
        <Subsegment SEGMENT="1">
          <Qua>Value2</Qua>
        </Subsegment>
      </Record>
      <Record SEGMENT="1">
        <POSN>8002</POSN>
        <VAR>45678</VAR>
        <field>42</field>
        <Child SEGMENT="1">
          <POSN>8002</POSN>
          <Union>UN</Union>
         </Child>
        <Subsegment SEGMENT="1">
          <Qua>value1</Qua>
        </Subsegment>
        <Subsegment SEGMENT="1">
          <Qua>Value2</Qua>
        </Subsegment>
      </Record>
        <Record SEGMENT="1">
        <POSN>8003</POSN>
        <VAR>778899</VAR>
        <field>43</field>
        <Child SEGMENT="1">
          <POSN>8003</POSN>
          <Union>UN</Union>
         </Child>
        <Subsegment SEGMENT="1">
          <Qua>value1</Qua>
        </Subsegment>
        <Subsegment SEGMENT="1">
          <Qua>Value2</Qua>
        </Subsegment>
      </Record>
          <Record SEGMENT="1">
        <POSN>8004</POSN>
        <VAR>778899</VAR>
        <field>44</field>
         <Subsegment SEGMENT="1">
          <Qua>value1</Qua>
        </Subsegment>
        <Subsegment SEGMENT="1">
          <Qua>Value2</Qua>
        </Subsegment>
      </Record>
          <Record SEGMENT="1">
        <POSN>5000</POSN>
        <VAR>118899</VAR>
        <field>45</field>
         <Subsegment SEGMENT="1">
          <Qua>value1</Qua>
        </Subsegment>
        <Subsegment SEGMENT="1">
          <Qua>Value2</Qua>
        </Subsegment>
      </Record>
      
      
</MainHeader>

** XSLT I used is below:**

<xsl:stylesheet version="2.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

    <xsl:key name="segments-by-field" match="Record" use="POSN"/>

  <xsl:template match="/">
    <MainHeader>
      <xsl:for-each-group select="MainHeader/Record" group-by="POSN">
        <xsl:apply-templates select="current-group()"/>
      </xsl:for-each-group>
    </MainHeader>
  </xsl:template>

    <xsl:template match="Record">
   
    <Record>
        <POSN><xsl:value-of select="8000 + position()"/></POSN>
      <xsl:copy-of select="node()"/>
    </Record>
  </xsl:template>

</xsl:stylesheet>

Please review it once.


Solution

  • This is not a trivial task and I doubt there is a simple solution. Grouping the records does not help if you want the numbering to be sequential across the groups.

    Try perhaps something like:

    XSLT 2.0

    <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:key name="rec-by-var" match="Record" use="VAR" />
    <xsl:variable name="rec-ids" select="//Record[count(key('rec-by-var', VAR)) > 1]/generate-id()" />
    
    <!-- identity transform -->
    <xsl:template match="@*|node()" mode="#all">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()" mode="#current"/>
        </xsl:copy>
    </xsl:template>
    
    <xsl:template match="Record[generate-id()=$rec-ids]">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()" mode="multi">
                <xsl:with-param name="new-posn" select="8000 + index-of($rec-ids, generate-id())" tunnel="yes"/>
            </xsl:apply-templates>  
        </xsl:copy>
    </xsl:template>
    
    <xsl:template match="POSN" mode="multi">
        <xsl:param name="new-posn" tunnel="yes"/>
        <xsl:copy>
            <xsl:value-of select="$new-posn"/>
        </xsl:copy>
    </xsl:template>
    
    </xsl:stylesheet>