XSL Muenchian method on substring

前提是你 提交于 2019-12-11 03:21:15

问题


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


回答1:


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>


来源:https://stackoverflow.com/questions/11494904/xsl-muenchian-method-on-substring

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!