tags:

views:

676

answers:

4

I'm trying to make a XSLT conversion that generates C code, the following XML should be converted:

<enum name="anenum">
  <enumValue name="a"/>
  <enumValue name="b"/>
  <enumValue name="c" data="10"/>
  <enumValue name="d" />
  <enumValue name="e" />
</enum>

It should convert to some C code as following:

enum anenum {
   a = 0,
   b = 1,
   c = 10,
   d = 11,
   e = 12
}

or alternatively (as the C preprocessor will handle the summation):

   enum anenum {
       a = 0,
       b = 1,
       c = 10,
       d = c+1,
       e = c+2
    }

The core of my XSLT looks like:

<xsl:for-each select="enumValue">
  <xsl:value-of select="name"/>
  <xsl:text> = </xsl:text>
  <xsl:choose>
    <xsl:when test="string-length(@data)&gt;0">
      <xsl:value-of select="@data"/>
    </xsl:when>
    <xsl:otherwise>
      <xsl:value-of select="position()-1"/>
    </xsl:otherwise>
  </xsl:choose>
  <xsl:text>,

(for simplicity I skip some of the 'no comma at the last element' code)

This example will not generate the correct values for d and e

I've been trying to get it working for the variable d and e, but so far I'm unsuccessful.

Using constructions like:

<xsl:when test="string-length(preceding-sibling::enumValue[1]/@datavalue)&gt;0">
    <xsl:value-of select="preceding-sibling::enumValue/@data + 1"/>
</xsl:when>

...only work for the first one after the specified value (in this case d).

Who can help me? I'm probably thinking too much in a procedural way...

A: 

You're quite close, I think, but you need the data value of the first preceding enumValue sibling with a data attribute, not the data value of the first preceding enumValue. Then add the number of preceding enumValue siblings of the current node, and subtract the number of preceding enumValue siblings of the node from which you took the data value.

Alohci
+1  A: 

You can't change "variables" in xsl but you can use recursion. Don't use preceding-sibling predicates unless absolutely urgent as they will kill your performance.

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format"&gt;
    <xsl:template match="/" >
     <xsl:call-template name="printEnum">
      <xsl:with-param name="value" select="0"/>
      <xsl:with-param name="position" select="1"/>
     </xsl:call-template>
    </xsl:template>

    <xsl:template name="printEnum">
     <xsl:param name="position"/> 
     <xsl:param name="value" select="0"/>
     <xsl:variable name="node" select="/enum/enumValue[$position]"/>
     <xsl:variable name="enumValue">
      <xsl:choose>
       <xsl:when test="$node/@data">
        <xsl:value-of select="$node/@data"/>
       </xsl:when>
       <xsl:otherwise>
        <xsl:value-of select="$value + 1"/>
       </xsl:otherwise>
      </xsl:choose>  
     </xsl:variable>  
     <xsl:value-of select="concat($node/@name, ' = ', $enumValue, ' , ')"/>
     <xsl:if test="/enum/enumValue[$position + 1]">
      <xsl:call-template name="printEnum">
       <xsl:with-param name="value" select="$enumValue"/>
       <xsl:with-param name="position" select="$position + 1"/>
      </xsl:call-template>
     </xsl:if>
    </xsl:template>
</xsl:stylesheet>
Goran
Thank you! However, you have to change in the last call-template, the line <xsl:with-param name="value" select="$value"/>into <xsl:with-param name="value" select="$enumValue"/> in order to let it work.
Roalt
Fixed :) - glad it works
Goran
+1  A: 

A nonrecursive solution, using keys:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
 <xsl:strip-space elements="*"/>
 <xsl:output method="text"/>

 <xsl:key name="koffsetEnums" match="enumValue[@data]"
  use="generate-id()"/>

    <xsl:template match="enum">
      enum <xsl:value-of select="@name"/> {
      <xsl:apply-templates select="enumValue"/>
      }
    </xsl:template>

    <xsl:template match="enumValue">
      <xsl:value-of select="concat(@name, ' = ')"/>

      <xsl:variable name="voffsetValueId" select=
       "generate-id((. | preceding-sibling::enumValue)
                                            [@data][last()]
                  )"/>

      <xsl:choose>
        <xsl:when test="not($voffsetValueId)">
          <xsl:value-of select="concat(position(),'&#xA;      ')"/>
        </xsl:when>
        <xsl:otherwise>
          <xsl:variable name="vinitOffset" select=
           "key('koffsetEnums', $voffsetValueId)/@data"
           />

           <xsl:value-of select=
            "$vinitOffset
            +
               count(preceding-sibling::enumValue)
             -
               count(key('koffsetEnums', $voffsetValueId)/preceding-sibling::enumValue)
            "
            />
           <xsl:text>&#xA;      </xsl:text>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:template>

</xsl:stylesheet>

When the above transformation is applied on the originally provided XML document:

<enum name="anenum">
    <enumValue name="a"/>
    <enumValue name="b"/>
    <enumValue name="c" data="10"/>
    <enumValue name="d" />
    <enumValue name="e" />
</enum>

the required result is produced:

enum anenum {
      a = 1
      b = 2
      c = 10
      d = 11
      e = 12

      }
Dimitre Novatchev
Really like this one!
Andrew Cowenhoven
Wow, this one is also nice! I've also considered keys but did not quite know how to manage them!
Roalt
@Roalt I have improved the solution and now it seems really good, see my new answer. :)
Dimitre Novatchev
+1  A: 

A better solution with keys, avoiding most use of the preceding-sibling axis:

This transformation:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
    <xsl:strip-space elements="*"/>
    <xsl:output method="text"/>
<!--                                              -->   
    <xsl:key name="ksimpleEnValues" match="enumValue[not(@data)]"
     use="generate-id(preceding-sibling::enumValue[@data][1])"/>
<!--                                              -->       
    <xsl:template match="enum">
    enum <xsl:value-of select="@name"/>
      {      
       <xsl:apply-templates select=
        "key('ksimpleEnValues', '')
        "/>
        <xsl:apply-templates select="enumValue[@data]"/>
      }
    </xsl:template>
<!--                                              -->
    <xsl:template match="enumValue">
      <xsl:param name="pOffset" select="0"/>
     <xsl:value-of select=
      "concat(@name, ' = ', position()+$pOffset,'&#xA;      ')"/>
    </xsl:template>
<!--                                              -->
    <xsl:template match="enumValue[@data]">
     <xsl:value-of select=
      "concat(@name, ' = ', @data,'&#xA;      ')"/>
<!--                                              -->
      <xsl:apply-templates select=
           "key('ksimpleEnValues', generate-id())">
       <xsl:with-param name="pOffset" select="@data"/>
      </xsl:apply-templates>
  </xsl:template>    
</xsl:stylesheet>

when applied on the originally-provided XML document:

<enum name="anenum">
    <enumValue name="a"/>
    <enumValue name="b"/>
    <enumValue name="c" data="10"/>
    <enumValue name="d" />
    <enumValue name="e" />
</enum>

Produces the wanted result:

enum anenum
 {      
  a = 1
  b = 2
  c = 10
  d = 11
  e = 12
 }

Explanation:

  1. The key named ksimpleEnValues indexes all enumValue elements that do not have the data attribute. The indexing is done by the generate-id() value of the first preceding enumValue element that has a data attribute.

  2. Thus key('ksimpleEnValues', someId) is the nodeset containing all enumValue elements following the enumValue that has its generate-id() equal to someId, and all these enumValue elements are preceding the next enumValue with a data attribute, if such exists.

  3. key('ksimpleEnValues', '') is the node-set of all enumValue elements that do not have a preceding enumValue element with a data attribute.

  4. The template that matches enumValue takes an optional parameter $pOffset, in which the value of the data attribute from the immediate preceding enumValue element with this attribute, will be passed, otherwise the default value for $pOffset is 0.

  5. The template matching enumValue elements that have a data attribute produces its enum-value (@name = @data) and then applies templates to all enumValue elements between itself and the next (if such exists) enumValue with a data attribute. The value of the data attribute is passed as the $pOffset parameter and it will be added to the relative position of each selected enumValue element when producing the output from its processing.

Dimitre Novatchev
If anybody can explain why this answer has become "community wiki", I would greatly appreciate it. ?!?
Dimitre Novatchev
I put the flag there when I accepted the first/initial answer.
Roalt
I think this improvement is probably better than your previous answer, but I use your previous answer because (1) it's implemented and it works and (2) the performance isn't an issue.
Roalt
@Roalt But if something is marked "community wiki" this prevents it for receiving further votes -- not a good thing.
Dimitre Novatchev
@Roalt But the last answer is also implemented. And it works. Or what do you mean by "but I use your previous answer because (1) it's implemented and it works "
Dimitre Novatchev
Ok, thanks for telling me on the community wiki, I won't do it again.
Roalt
@Dimitre The accepted answer was put in some other more complex XSLT code with some slightly different tag-names including namespaces. I don't need the extra performance of your improved solution.
Roalt