来自XSLT的HTML表,每个“列”的节点可能不存在于每个“行”中

时间:2010-01-19 09:34:16

标签: xml xslt

我有这个XML:

<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="xsl.xsl"?>
<Response>
    <Result>
        <Date Unix="1263340800">13 Jan 2010 00:00:00</Date>
        <Column>
            <Name>Small</Name>
            <Value>100</Value>
        </Column>
    </Result>
    <Result>
        <Date Unix="1263427200">14 Jan 2010 00:00:00</Date>
        <Column>
            <Name>Small</Name>
            <Value>100</Value>
        </Column>
    </Result>
    <Result>
        <Date Unix="1263485232">14 Jan 2010 16:07:12</Date>
        <Column>
            <Name>Normal</Name>
            <Value>36.170537</Value>
        </Column>
    </Result>
    <Result>
        <Date Unix="1263513600">15 Jan 2010 00:00:00</Date>
        <Column>
            <Name>Small</Name>
            <Value>100</Value>
        </Column>
    </Result>
</Response>

我想以这种方式呈现数据:

<table>
    <tr>
        <th>Time</th>
        <th>Small</th>
        <th>Normal</th>
    </tr>
    <tr>
        <td>13 Jan 2010 00:00:00</td>
        <td class="class_Small">100</td>
        <td class="class_Normal"></td>
    </tr>
    <tr>
        <td>14 Jan 2010 00:00:00</td>
        <td class="class_Small">100</td>
        <td class="class_Normal"></td>
    </tr>
    <tr>
        <td>14 Jan 2010 16:07:12</td>
        <td class="class_Small"></td>
        <td class="class_Normal">39.301737</td>
    </tr>
    <tr>
        <td>15 Jan 2010 00:00:00</td>
        <td class="class_Small">100</td>
        <td class="class_Normal"></td>
    </tr>
</table>

这是我目前的XSL:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> 

   <xsl:key name="Friendlies" match="Name" use="."/> 
   <xsl:template match="/"> 

    <table> 
    <tr> 
        <th>Time</th> 
        <!-- Output the Column Names --> 
        <xsl:for-each select="//Column"> 

            <!-- Only ouput the Name if it is the first occurence of this value --> 
            <xsl:if test="generate-id(Name) = generate-id(key('Friendlies',Name)[1])">             

            <th> 
            <xsl:value-of select="Name"/> 
            </th>

            </xsl:if>                
        </xsl:for-each>
         </tr>
    <!-- Loop through all Results --> 
    <xsl:for-each select="//Result"> 
        <xsl:variable name="UnixTimestamp" select="Date/@Unix"/>
        <tr> 
            <td> 
                <xsl:value-of select="Date"/>
            </td> 

            <xsl:for-each select="//Column">
                <xsl:if test="generate-id(Name) = generate-id(key('Friendlies',Name)[1])">      

                <xsl:element name="td">
                    <xsl:attribute name="class">class_<xsl:value-of select="Name"/></xsl:attribute>
                    <xsl:value-of select="[Date/@Unix=$UnixTimestamp]/Value"/>
                </xsl:element>

                </xsl:if>
            </xsl:for-each> 

               </tr>
    </xsl:for-each> 
    </table>
   </xsl:template> 
</xsl:stylesheet> 

HTML结构出来了。唯一的问题是值不会出现在单元格中。我意识到<xsl:value-of select="[Date/@Unix=$UnixTimestamp]/Value"/>不是正确的引用,因为Date元素实际上与Column元素处于同一级别,而不在其中。我想不出应该怎么做。

另外,我不确定我是否正在使用最好的方法,因为看起来很少有输出会发生很多循环。随着XML变得更大(更多结果,更多列),我想知道是否会对性能产生影响。

原谅我,我是XSL的新手。

提前致谢。


在Tomalak的回应后添加

有关XML的一些信息:

  • 每个<Column>节点中的任意数量的<Result>个节点
  • 每个<Date>节点中只有一个<Result>节点
  • 每个<Name>节点中只有一个<Column>节点
  • <Name>节点的值可以是任何值,而不仅仅是“小”或“正常”

1 个答案:

答案 0 :(得分:4)

你的事情过于复杂。怎么样:

<xsl:stylesheet 
  version="1.0" 
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
  <xsl:output encoding="utf-8" indent="yes" />

  <xsl:template match="Response">
    <table>
      <tr>
        <th>Time</th>
        <th>Small</th>
        <th>Normal</th>
      </tr>
      <xsl:apply-templates select="Result" />
    </table>
  </xsl:template>

  <xsl:template match="Result">
    <tr>
      <td><xsl:value-of select="Date" /></td>
      <td><xsl:value-of select="Column[Name = 'Small']/Value" /></td>
      <td><xsl:value-of select="Column[Name = 'Normal']/Value" /></td>
    </tr>
  </xsl:template>

</xsl:stylesheet>

输出完全符合您的要求:

<table>
  <tr>
    <th>Time</th>
    <th>Small</th>
    <th>Normal</th>
  </tr>
  <tr>
    <td>13 Jan 2010 00:00:00</td>
    <td>100</td>
    <td></td>
  </tr>
  <tr>
    <td>14 Jan 2010 00:00:00</td>
    <td>100</td>
    <td></td>
  </tr>
  <tr>
    <td>14 Jan 2010 16:07:12</td>
    <td></td>
    <td>36.170537</td>
  </tr>
  <tr>
    <td>15 Jan 2010 00:00:00</td>
    <td>100</td>
    <td></td>
  </tr>
</table>

你的XSLT非常复杂,主要是因为:

<xsl:for-each select="//Column"> 

这方面的两个提示:

  • 只要你能避免它,就不要为每个人做。 98%的时间你需要应用模板(即使最初有点难以绕过它)。抵制使用for-each的冲动,它会使你的代码膨胀。
  • 只要您可以避免使用//运算符,请不要使用它。您的输入XML使用//完美结构化,就像您通过完全展平它而使此结构无法使用一样。相反,尝试使用现有的XML结构,就像我已经完成的那样。

修改

跟进OP的评论。下面的解决方案可以处理任何数量和种类的<Name>节点。它使用for-each,但作为抽象迭代的一种手段,而不是作为处理子节点的手段。

<xsl:stylesheet 
  version="1.0" 
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
  <xsl:output encoding="utf-8" indent="yes" />

  <xsl:key name="kName" match="Name" use="." />

  <xsl:variable name="vName" select="
    //Name[generate-id() = generate-id(key('kName', .)[1])]
  " />

  <xsl:template match="Response">
    <table>
      <tr>
        <th>Time</th>
        <xsl:for-each select="$vName">
          <th><xsl:value-of select="." /></th>
        </xsl:for-each>
      </tr>
      <xsl:apply-templates select="Result" />
    </table>
  </xsl:template>

  <xsl:template match="Result">
    <xsl:variable name="self" select="." />
    <tr>
      <td><xsl:value-of select="Date" /></td>
      <xsl:for-each select="$vName">
        <td><xsl:value-of select="$self/Column[Name = current()]/Value" /></td>
      </xsl:for-each>
    </tr>
  </xsl:template>

</xsl:stylesheet>

您可以使用<xsl:sort>修改生成列的顺序。

为了获得大输入文档的最佳性能,另一个调整可以加速这个表达式:

<xsl:value-of select="$self/Column[Name = current()]/Value" />

使用这个附加键:

<xsl:key name="kColumn" match="Column" use="concat(Name,'|',generate-id(..))" />

你可以把它重写为:

<xsl:value-of select="key('kColumn', concat(.,'|',generate-id($self)))/Value" />