问题
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:
Proper use of the Muenchian grouping method where a
Conv
element is indexed by the first 4 characters of the string vaue of itsht
attribute.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