Search code examples
xsltnode-set

XSLT v1.0 Updating attribute in node-set based on previous node's attribute


I have the following XML file.

<Record 
  Name="My_Record"
  <Fields
    StartingBit="0"
    Size="3"
    Name="Field_1">
  </Fields>
  <Fields 
    StartingBit="1"
    Size="5"
    Name="Field_2">
  </Fields>
  <Fields
    StartingBit="2"
    Size="8"
    Name="Field_3">
  </Fields>
  <Fields
    StartingBit="3"
    Size="4"
    Name="Field_4"
  </Fields>
</Record>

And I would like to use XSLT to properly update the @StartingBit attribute from the previous node's @StartingBit + @Size - this will be the current node's @StartingBit value. The resulting XML should be as follows:

<Record 
  Name="My_Record"
  <Fields
    StartingBit="0"
    Size="3"
    Name="Field_1">
  </Fields>
  <Fields 
    StartingBit="3"
    Size="5"
    Name="Field_2">
  </Fields>
  <Fields
    StartingBit="8"
    Size="8"
    Name="Field_3">
  </Fields>
  <Fields
    StartingBit="16"
    Size="4"
    Name="Field_4"
  </Fields>
</Record>

So far, my latest attempts to my XSLT is as follows:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml" indent="yes"/>
  <xsl:template match="@* | node()">
    <xsl:copy>
      <xsl:apply-templates select="@* | node()"/>
    </xsl:copy>
  </xsl:template>
  <xsl:template match ="Fields/@StartingBit">
    <xsl:value-of select ="(preceding-sibling::Fields[1]/@StartingBit + preceding-sibling::Fields[1]/@Size)"/>
  </xsl:template>
</xsl:stylesheet>

The above transform does not generate what I would like - basically @StartingBit does not change. I am not proficicent in node navigation to get the results I like - can someone assist in my transform? Thank you in advance.

  • Lorentz

Solution

  • When this simple XSLT:

    <?xml version="1.0" encoding="utf-8"?>
    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    version="1.0">
      <xsl:output omit-xml-declaration="no" indent="yes" />
      <xsl:strip-space elements="*" />
    
      <xsl:variable name="vStartingBit" select="/*/Fields[1]/@StartingBit" />
    
      <xsl:template match="@*|node()">
        <xsl:copy>
          <xsl:apply-templates select="@*|node()" />
        </xsl:copy>
      </xsl:template>
    
      <xsl:template match="Fields[position() &gt; 1]">
        <xsl:copy>
          <xsl:attribute name="StartingBit">
            <xsl:value-of
              select="$vStartingBit + sum(preceding-sibling::Fields/@Size)" />
          </xsl:attribute>
          <xsl:apply-templates select="@*[not(name() = 'StartingBit')]" />
        </xsl:copy>
      </xsl:template>
    </xsl:stylesheet>
    

    ...is applied to the original XML (corrected to be well-formed):

    <?xml version="1.0" encoding="utf-8"?>
    <Record Name="My_Record">
      <Fields StartingBit="0" Size="3" Name="Field_1" />
      <Fields StartingBit="1" Size="5" Name="Field_2" />
      <Fields StartingBit="2" Size="8" Name="Field_3" />
      <Fields StartingBit="3" Size="4" Name="Field_4" />
    </Record>
    

    ...the expected result is produced:

    <?xml version="1.0"?>
    <Record Name="My_Record">
      <Fields StartingBit="0" Size="3" Name="Field_1" />
      <Fields StartingBit="3" Size="5" Name="Field_2" />
      <Fields StartingBit="8" Size="8" Name="Field_3" />
      <Fields StartingBit="16" Size="4" Name="Field_4" />
    </Record>
    

    Explanation:

    • The first template is the Identity Template. Its purpose is to copy all nodes and attributes from the source document to the result document as-is.
    • One template overrides the Identity Template. It's purpose is to add the original starting bit to the correct @Size attributes (those that precede the current <Fields> element), which forms the value of each subsequent <Fields> element's @StartingBit attribute.