XSL Muenchian method on substring

I'm trying to transform some XML output to a rating table (hydrology related) which consists of the hundredths values going across the top and the tenths values going down. The problem I'm running into is that I can't seem to sort for the unique values by using a substring on the ht attributes.

Here is a sample of the XML output:

<env:Envelope>
<env:Header/>
<env:Body>
<RatingTableUpdate>      
  <Gauge xmlns="" type="STORAGE" id="123">
    <Input input="0">
      <Conv ht="19.76" cap="5040" />
      <Conv ht="19.77" cap="5045" />
      <Conv ht="19.78" cap="5051" />
      <Conv ht="19.79" cap="5057" />
      <Conv ht="19.80" cap="5063" />
      <Conv ht="19.81" cap="5069" />
      <Conv ht="19.82" cap="5074" />
      <Conv ht="19.83" cap="5080" />
      <Conv ht="19.84" cap="5086" />
      <Conv ht="19.85" cap="5092" />
      <Conv ht="19.86" cap="5098" />
      <Conv ht="19.87" cap="5104" />
      <Conv ht="19.88" cap="5109" />
      <Conv ht="19.89" cap="5115" />
      <Conv ht="19.90" cap="5121" />
      <Conv ht="19.91" cap="5127" />
      <Conv ht="19.92" cap="5133" />
      <Conv ht="19.93" cap="5138" />
    </Input>
  </Gauge>
</RatingTableUpdate>
</env:Body>
</env:Envelope>

The output should look like this:

GH    0.00  0.01  0.02  0.03  0.04  0.05  0.06  0.07  0.08  0.09
19.7                                      5040  5045  5051  5057  
19.8  5063  5069  5074  5080  5086  5092  5098  5104  5109  5115
19.9  5121  5127  5133  5138

I'm able to get the cap attributes in the correct column, but can't get the GH column (ht attribute with hundredths values dropped) to only display once per row. I get something like this:

GH    0.00  0.01  0.02  0.03  0.04  0.05  0.06  0.07  0.08  0.09
19.7                                      5040  
                                                5045  
                                                      5051  
                                                            5057  
19.8  5063  
            5069  
                  5074  
                        5080  
                              5086  
                                    5092  
                                          5098  
                                                5104  
                                                      5109  
                                                            5115
19.9  5121  
            5127  
                  5133  
                        5138

Here's my section of xsl that formats this section. The xsl:choose seems to work well for the columns. I've tried the Muenchian method but can't get it quite right (having a hard time getting it to work with a substring key).

Is this the best method, or is there another way to get each row to display only once for the given substring (19.7 once for 19.76, 19.77, 19.78, 19.79 values)? The ratings commonly start at non-zero values like the example, so I can't just use the hundredth 0 values to start a group and 9 to end a group.

<xsl:for-each select="env:Envelope/env:Body/RatingTableUpdate/Gauge">

<table width="100%">
<tr class="site">                           
  <td width="150px">Site ID: <u><xsl:value-of select="@id" /></u></td>
  <td>Site Name: <u><xsl:value-of select="@idname" /></u></td>
  <td>Type: <u><xsl:value-of select="@type" /></u></td>
</tr>   
</table>        

<table width="100%">
  <tr class="tbldata">
    <th rowspan="1" class="tbldataheader">GH</th>
    <th rowspan="1" class="tbldataheader">0.00</th>
    <th rowspan="1" class="tbldataheader">0.01</th>
    <th rowspan="1" class="tbldataheader">0.02</th>
    <th rowspan="1" class="tbldataheader">0.03</th>
    <th rowspan="1" class="tbldataheader">0.04</th>
    <th rowspan="1" class="tbldataheader">0.05</th>
    <th rowspan="1" class="tbldataheader">0.06</th>
    <th rowspan="1" class="tbldataheader">0.07</th>
    <th rowspan="1" class="tbldataheader">0.08</th>
    <th rowspan="1" class="tbldataheader">0.09</th>
  </tr>  

  <xsl:for-each select="./Input/Conv">
    <tr class="tbldata">

      <xsl:choose>
        <xsl:when test="substring(@ht,string-length(@ht),1) = 0">
          <td class="ifrtext"><xsl:value-of select="substring(@ht,1,string-length(@ht)-1)" /></td>
        </xsl:when>
        <xsl:otherwise>
          <td class="ifrtext"></td>
        </xsl:otherwise>
      </xsl:choose>
       <xsl:choose>
        <xsl:when test="substring(@ht,string-length(@ht),1) = 0">
          <td class="ifrtext"><xsl:value-of select="@cap" /></td>
        </xsl:when>
        <xsl:otherwise>
          <td class="ifrtext"></td>
        </xsl:otherwise>
      </xsl:choose>
      <xsl:choose>
        <xsl:when test="substring(@ht,string-length(@ht),1) = 1">
          <td class="ifrtext"><xsl:value-of select="@cap" /></td>
        </xsl:when>
        <xsl:otherwise>
          <td class="ifrtext"></td>
        </xsl:otherwise>
      </xsl:choose>
      <xsl:choose>
        <xsl:when test="substring(@ht,string-length(@ht),1) = 2">
          <td class="ifrtext"><xsl:value-of select="@cap" /></td>
        </xsl:when>
        <xsl:otherwise>
          <td class="ifrtext"></td>
        </xsl:otherwise>
      </xsl:choose>
      <xsl:choose>
        <xsl:when test="substring(@ht,string-length(@ht),1) = 3">
          <td class="ifrtext"><xsl:value-of select="@cap" /></td>
        </xsl:when>
        <xsl:otherwise>
          <td class="ifrtext"></td>
        </xsl:otherwise>
      </xsl:choose>
      <xsl:choose>
        <xsl:when test="substring(@ht,string-length(@ht),1) = 4">
          <td class="ifrtext"><xsl:value-of select="@cap" /></td>
        </xsl:when>
        <xsl:otherwise>
          <td class="ifrtext"></td>
        </xsl:otherwise>
      </xsl:choose>
      <xsl:choose>
        <xsl:when test="substring(@ht,string-length(@ht),1) = 5">
          <td class="ifrtext"><xsl:value-of select="@cap" /></td>
        </xsl:when>
        <xsl:otherwise>
          <td class="ifrtext"></td>
        </xsl:otherwise>
      </xsl:choose>
      <xsl:choose>
        <xsl:when test="substring(@ht,string-length(@ht),1) = 6">
          <td class="ifrtext"><xsl:value-of select="@cap" /></td>
        </xsl:when>
        <xsl:otherwise>
          <td class="ifrtext"></td>
        </xsl:otherwise>
      </xsl:choose>
      <xsl:choose>
        <xsl:when test="substring(@ht,string-length(@ht),1) = 7">
          <td class="ifrtext"><xsl:value-of select="@cap" /></td>
        </xsl:when>
        <xsl:otherwise>
          <td class="ifrtext"></td>
        </xsl:otherwise>
      </xsl:choose>
      <xsl:choose>
        <xsl:when test="substring(@ht,string-length(@ht),1) = 8">
          <td class="ifrtext"><xsl:value-of select="@cap" /></td>
        </xsl:when>
        <xsl:otherwise>
          <td class="ifrtext"></td>
        </xsl:otherwise>
      </xsl:choose>
      <xsl:choose>
        <xsl:when test="substring(@ht,string-length(@ht),1) = 9">
          <td class="ifrtext"><xsl:value-of select="@cap" /></td>
        </xsl:when>
        <xsl:otherwise>
          <td class="ifrtext"></td>
        </xsl:otherwise>
      </xsl:choose>
    </tr>   
  </xsl:for-each>
</table>
</xsl:for-each>

Thanks, Matt

Answers


This transformation:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>

 <xsl:key name="kValByTenths" match="Conv"
      use="substring(@ht, 1, 4)"/>

 <xsl:key name="kCapFromHt" match="@cap"
      use="../@ht"/>

 <xsl:template match="Input">
  <table>
   <xsl:for-each select=
    "Conv[generate-id()
         =
          generate-id(key('kValByTenths',substring(@ht, 1, 4))[1])
          ]">
     <xsl:variable name="vKey" select="substring(@ht, 1, 4)"/>
     <tr>
      <td><xsl:value-of select="$vKey"/></td>

      <xsl:for-each select=
       "(//node() | //namespace::*)[not(position() > 10)]">
        <xsl:variable name="vPos" select="position()"/>
        <td>
         <xsl:value-of select="key('kCapFromHt', concat($vKey,$vPos -1))"/>
        </td>
      </xsl:for-each>
     </tr>
    </xsl:for-each>
   </table>
 </xsl:template>
</xsl:stylesheet>

when applied on the provided XML document (corrected to be made well-formed XML document):

<env:Envelope xmlns:env="some:env">
    <env:Header/>
    <env:Body>
        <RatingTableUpdate>
            <Gauge xmlns="" type="STORAGE" id="123">
                <Input input="0">
                    <Conv ht="19.76" cap="5040" />
                    <Conv ht="19.77" cap="5045" />
                    <Conv ht="19.78" cap="5051" />
                    <Conv ht="19.79" cap="5057" />
                    <Conv ht="19.80" cap="5063" />
                    <Conv ht="19.81" cap="5069" />
                    <Conv ht="19.82" cap="5074" />
                    <Conv ht="19.83" cap="5080" />
                    <Conv ht="19.84" cap="5086" />
                    <Conv ht="19.85" cap="5092" />
                    <Conv ht="19.86" cap="5098" />
                    <Conv ht="19.87" cap="5104" />
                    <Conv ht="19.88" cap="5109" />
                    <Conv ht="19.89" cap="5115" />
                    <Conv ht="19.90" cap="5121" />
                    <Conv ht="19.91" cap="5127" />
                    <Conv ht="19.92" cap="5133" />
                    <Conv ht="19.93" cap="5138" />
                </Input>
            </Gauge>
        </RatingTableUpdate>
    </env:Body>
</env:Envelope>

produces the wanted (without the decorations), correct result:

<table>
   <tr>
      <td>19.7</td>
      <td/>
      <td/>
      <td/>
      <td/>
      <td/>
      <td/>
      <td>5040</td>
      <td>5045</td>
      <td>5051</td>
      <td>5057</td>
   </tr>
   <tr>
      <td>19.8</td>
      <td>5063</td>
      <td>5069</td>
      <td>5074</td>
      <td>5080</td>
      <td>5086</td>
      <td>5092</td>
      <td>5098</td>
      <td>5104</td>
      <td>5109</td>
      <td>5115</td>
   </tr>
   <tr>
      <td>19.9</td>
      <td>5121</td>
      <td>5127</td>
      <td>5133</td>
      <td>5138</td>
      <td/>
      <td/>
      <td/>
      <td/>
      <td/>
      <td/>
   </tr>
</table>

Explanation:

  1. Proper use of the Muenchian grouping method where a Conv element is indexed by the first 4 characters of the string vaue of its ht attribute.

  2. Proper use of the Piez method for non-recursive iteration N times.

Update:

The OP has reported that due to a bug in MSXML3 he cannot get the correct result when using IE 8.

Here is a modified solution that also works OK with MSXML3 / IE 8:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

 <xsl:key name="kValByTenths" match="Conv"
      use="substring(@ht, 1, 4)"/>

 <xsl:variable name="vConvs" select="//Conv"/>

 <xsl:template match="Input">
  <table>
   <xsl:for-each select=
    "Conv[generate-id()
         =
          generate-id(key('kValByTenths',substring(@ht, 1, 4))[1])
          ]">
     <xsl:variable name="vKey" select="substring(@ht, 1, 4)"/>
     <tr>
      <td><xsl:value-of select="$vKey"/></td>

      <xsl:for-each select=
       "(//node() | //namespace::*)[not(position() > 10)]">
        <xsl:variable name="vPos" select="position()"/>

        <td>
         <xsl:value-of select=
         "$vConvs[substring-after(@ht, $vKey) = $vPos]/@cap"/>
        </td>
      </xsl:for-each>
     </tr>
    </xsl:for-each>
   </table>
 </xsl:template>
</xsl:stylesheet>

Need Your Help

saveFileDialog create new folder and save inside it

c# savefiledialog

i wanna create backup data in my application I used saveFileDialog, so i can place backup file anywhere i want (Dekstop, drive D, etc)

Unknown Index.dat and Storage.dat files in Visual Studio 2010 solution folder

c# visual-studio visual-studio-2010 index.dat

I have a Visual Studio 2010 solution and two files have mysteriously appeared in the root solution folder a week ago named Index.dat and Storage.dat. They are 28 Bytes and 512 Bytes in size respect...