tags:

views:

60

answers:

3

I have an XML document that has nodes in it that look like this:

<Variable name="var1" dataType="INT32"/>
<Variable name="var2" dataType="INT16"/>
<Variable name="var3" dataType="INT8"/>

I can loop over the variables and display the name and data type just fine, but I'd like to display the size of the variable, as well as the offset of it (first variable has an offset of zero, 2nd has an offset equal to the size of the first, 3rd has an offset equal to the size of the previous two). In the above example, var1 has a size of 4 and a offset of zero, var2 has a size of 2 and an offset of 4, var3 has a size of 1 and an offset of 6.

To print the size, this worked:

<xsl:variable name="fieldSize">
  <xsl:choose>
    <xsl:when test="contains(@dataType, 'INT8')">
        <xsl:value-of select="'1'"/>
    </xsl:when>
    <xsl:when test="contains(@dataType, 'INT16')">
        <xsl:value-of select="'2'"/>
    </xsl:when>
    <xsl:when test="contains(@dataType, 'INT32')">
        <xsl:value-of select="'4'"/>
    </xsl:when>
    <xsl:otherwise><xsl:value-of select="'unknown'"/></xsl:otherwise>
  </xsl:choose>
</xsl:variable>
<xsl:value-of select="$fieldSize"/>

However, I have no idea how to print the offset! If field size was an attribute, I could do something like:

<xsl:variable name="offset" select="sum(preceding-sibling::Variable/@fieldSize)"/>

Since it's a variable and not an attribute, I can't do a sum over preceding-siblings to calculate offset. My next idea is to try to make an expression that can evaluate to the size based on the @dataType attribute, and maybe I can feed that into the "sum()" expression (no idea if that would work, though).

I attempted to create a NodeSet for fieldSizes, so I can lookup the size based on the attribute:

<xsl:variable name="fieldSizes">
    <i ref="INT8">1</i>
    <i ref="INT16">2</i>
    <i ref="INT32">4</i>
</xsl:variable>

<xsl:value-of select="$fieldSizes[@ref=@dataType]"/>

However, the last line causes a Error during XSLT transformation: An XPath expression was expected to return a NodeSet. All of the below variants cause the same error:

<xsl:value-of select="$fieldSizes[@ref='INT8']"/>
<xsl:value-of select="$fieldSizes[@ref=INT8]"/>
<xsl:value-of select="$fieldSizes[1]"/>

How can I print the field size of the variable based on it's dataType? And once that works, how can I calculate the value of offset? Perhaps something like:

<xsl:variable name="offset" select="sum(preceding-sibling::Variable/$fieldSizes[@ref=@dataType])"/>
A: 

I don't think you can do this with a simple sum, but what you can do is create a template with parameters that takes in a list of nodes and the preceding offset. It calculates the offset of the first item in the list and then recursively calls itself with the new offset and the remaining items in the list of nodes.

You can see an example template with parameters at the w3schools web site. They're a reasonably good place to start looking for information on most web technologies.

Don Kirkby
What's a good website to learn about templates with parameters? I'm relatively clueless about XSLT.
KeyserSoze
I added a link to an example, @KeyserSoze.
Don Kirkby
+2  A: 

Two solutions: for a two pass transformation (so you can use fn:sum()) you will need the node-set() extension function; ussing modes.

This stylesheet:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:my="my">
    <my:s size="4" dataType="INT32"/>
    <my:s size="2" dataType="INT16"/>
    <my:s size="1" dataType="INT8"/>
    <xsl:template match="node()|@*" name="identity">
        <xsl:copy>
            <xsl:apply-templates select="node()|@*"/>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="Variable/@*[last()]">
        <xsl:call-template name="identity"/>
        <xsl:attribute name="size">
            <xsl:value-of select="document('')/*/my:s
                                     [@dataType = current()/../@dataType]
                                        /@size"/>
        </xsl:attribute>
        <xsl:attribute name="offset">
            <xsl:apply-templates select=".." mode="offset"/>
        </xsl:attribute>
    </xsl:template>
    <xsl:template match="Variable" mode="offset">
        <xsl:param name="pCounter" select="0"/>
        <xsl:variable name="vPrev" select="preceding-sibling::Variable[1]"/>
        <xsl:apply-templates select="$vPrev" mode="offset">
            <xsl:with-param name="pCounter"
                            select="$pCounter + document('')/*/my:s
                                                  [@dataType = $vPrev/@dataType]
                                                     /@size"/>
        </xsl:apply-templates>
        <xsl:if test="not($vPrev)">
            <xsl:value-of select="$pCounter"/>
        </xsl:if>
    </xsl:template>
</xsl:stylesheet>

With this input:

<root>
    <Variable name="var1" dataType="INT32"/>
    <Variable name="var2" dataType="INT16"/>
    <Variable name="var3" dataType="INT8"/>
</root>

Output:

<root>
    <Variable name="var1" dataType="INT32" size="4" offset="0"></Variable>
    <Variable name="var2" dataType="INT16" size="2" offset="4"></Variable>
    <Variable name="var3" dataType="INT8" size="1" offset="6"></Variable>
</root>

EDIT 3: Also this stylesheet (foward mode, better performance, borrowed Dimitre's calculation of size)

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
    <xsl:output indent="yes"/>
    <xsl:template match="*">
        <xsl:apply-templates select="*[1]|following-sibling::*[1]"/>
    </xsl:template>
    <xsl:template match="Variable">
        <xsl:param name="pOffset" select="0"/>
        <xsl:variable name="vSize" 
                      select="substring-after(@dataType,'INT') div 8"/>
        <xsl:copy>
            <xsl:copy-of select="@*"/>
            <xsl:attribute name="size">
                <xsl:value-of select="$vSize"/>
            </xsl:attribute>
            <xsl:attribute name="offset">
                <xsl:value-of select="$pOffset"/>
            </xsl:attribute>
        </xsl:copy>
        <xsl:apply-templates select="following-sibling::Variable[1]">
            <xsl:with-param name="pOffset" select="$pOffset + $vSize"/>
        </xsl:apply-templates>
    </xsl:template>
</xsl:stylesheet>

Output:

<Variable name="var1" dataType="INT32" size="4" offset="0" />
<Variable name="var2" dataType="INT16" size="2" offset="4" />
<Variable name="var3" dataType="INT8" size="1" offset="6" />
Alejandro
Can you show me how to use that method print the size (var1->4, var2->2, var3->1) and offset (var1->0, var2->4, var3->5) of each variable, instead of just the total size?
KeyserSoze
@KeyserSoze: Check my edit.
Alejandro
@Alejandro: Good, detailed solution, +1.
Dimitre Novatchev
+4  A: 

This transformation:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:msxsl="urn:schemas-microsoft-com:xslt"
 exclude-result-prefixes="msxsl" >
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:variable name="vDigits" select="'0123456789'"/>

 <xsl:template match="Variable">
  <xsl:variable name="vOffset">
     <xsl:call-template name="getOffset"/>
  </xsl:variable>

  <Variable name="{@name}" dataType="{@dataType}"
    size="{translate(@dataType, translate(@dataType,$vDigits,''),'') div 8}"
    offset="{$vOffset}"
  />
 </xsl:template>


 <xsl:template name="getOffset">
  <xsl:variable name="vrtfprevSizes">
   <xsl:for-each select="preceding-sibling::Variable">
     <v size="{translate(@dataType, translate(@dataType,$vDigits,''),'') div 8}"/>
   </xsl:for-each>
  </xsl:variable>

  <xsl:value-of select="sum(msxsl:node-set($vrtfprevSizes)/v/@size)"/>
 </xsl:template>
</xsl:stylesheet>

when applied on this XML document:

<t>
    <Variable name="var1" dataType="INT32"/>
    <Variable name="var2" dataType="INT16"/>
    <Variable name="var3" dataType="INT8"/>
</t>

produces the wanted, correct result:

<Variable name="var1" dataType="INT32" size="4" offset="0" />
<Variable name="var2" dataType="INT16" size="2" offset="4" />
<Variable name="var3" dataType="INT8" size="1" offset="6" />

Do note:

  1. There is no recursion.

  2. In XSLT 1.0 the xxx:node-set() is needed to convert an RTF into a regular node-set.

  3. With very large number of Variable elements this solution is slow, because the same partial sum is computed many times. However, this is guaranteed not to crash due to too-deep call stack.

Dimitre Novatchev
@Dimitre: +1 Good catch (div by 8)
Alejandro