获取xslt 1.0排序顺序中的上一个元素,而不是dom顺序

时间:2015-05-29 16:38:04

标签: xml xslt xslt-1.0

我有一个CD列表,其中的条目没有任何特定的排序顺序:

<?xml version="1.0" encoding="UTF-8"?>
<catalog>
    <cd>
        <title>Empire Burlesque</title>
        <artist>Bob Dylan</artist>
        <country>USA</country>
        <company>Columbia</company>
        <price>10.90</price>
        <year>1985</year>
    </cd>
    <cd>
        <title>Hide your heart</title>
        <artist>Bonnie Tyler</artist>
        <country>UK</country>
        <company>CBS Records</company>
        <price>9.90</price>
        <year>1988</year>
    </cd>
    <cd>
        <title>Greatest Hits</title>
        <artist>Dolly Parton</artist>
        <country>USA</country>
        <company>RCA</company>
        <price>9.90</price>
        <year>1982</year>
    </cd>
    <cd>
        <title>Still got the blues</title>
        <artist>Gary Moore</artist>
        <country>UK</country>
        <company>Virgin records</company>
        <price>10.20</price>
        <year>1990</year>
    </cd>
</catalog>

我想以html格式输出列表,按标题的第一个字母分组。既然我怎么说,我首先通过简单的方式运行XML:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    exclude-result-prefixes="xs" version="1.0">
    <xsl:output method="xml" indent="yes" />
    <xsl:template match="catalog">
        <catalog>
            <xsl:apply-templates select="cd"><xsl:sort select="title"/></xsl:apply-templates>
        </catalog>
    </xsl:template>
    <xsl:template match="cd"><xsl:copy-of select="."/></xsl:template>
</xsl:stylesheet>

然后我可以使用preceding-sibling来检查标题字母的更改。生成的工作表如下所示:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:template match="/">
    <html>
      <head>
        <title> Booklist with books <xsl:value-of select="count(/catalog/cd)"/>
        </title>
        <style type="text/css">
          table.main {width : 100%}
          table.main td {padding : 2px; border-bottom : 1px solid gray}
          th {text-align : left}
          tr.header {background-color : #9acd32}
          table.bar {border: 1px solid gray; background-color #CACACA}
          table.bar td {border-left : 1px solid gray; padding : 4px; margin : 2px; font-size : x-large}
          tr.firstbook {background-color : #CACACA}
          td.firstbook {font-size : xx-large}
          td.firstbook a.up {text-decoration: none; font-size : normal}
        </style>
      </head>
      <body>
        <xsl:apply-templates mode="header"/>
        <xsl:apply-templates/>
      </body>
    </html>
  </xsl:template>

  <xsl:template match="catalog">
    <table class="main">
      <tr class="header">
        <th> Title </th>
        <th> Artist </th>
        <th> Country </th>
        <th> Company </th>
        <th> Price </th>
        <th> Year </th>
      </tr>
      <xsl:apply-templates select="cd">
        <xsl:sort select="title"/>
      </xsl:apply-templates>
    </table>
  </xsl:template>

  <xsl:template match="cd">
    <xsl:variable name="firstLetter" select="substring(title,1,1)"/>
    <xsl:variable name="oldLetter" select="substring(preceding-sibling::*[1]/title,1,1)"/>

    <xsl:if test="not($firstLetter=$oldLetter)">
      <tr class="firstbook">
        <td class="firstbook" colspan="5">
          <a name="{$firstLetter}">
            <xsl:value-of select="$firstLetter"/>
          </a>
        </td>
        <td class="firstbook">
          <a class="up" href="#">&#11014;</a>
        </td>
      </tr>
    </xsl:if>

    <tr>
      <td>
        <xsl:value-of select="title"/>
      </td>
      <td>
        <xsl:value-of select="artist"/>
      </td>
      <td>
        <xsl:value-of select="country"/>
      </td>
      <td>
        <xsl:value-of select="company"/>
      </td>
      <td>
        <xsl:value-of select="price"/>
      </td>
      <td>
        <xsl:value-of select="year"/>
      </td>
    </tr>
  </xsl:template>

  <!-- Header link handling -->
  <xsl:template match="catalog" mode="header">
    <table class="bar">
      <tr>
        <xsl:apply-templates mode="header"
          select="cd[not(substring(title,1,1)=substring(preceding-sibling::*[1]/title,1,1))]">
          <xsl:sort select="title"/>
        </xsl:apply-templates>
      </tr>
    </table>
  </xsl:template>

  <xsl:template mode="header" match="cd">
    <xsl:variable name="firstLetter" select="substring(title,1,1)"/>
    <td>
      <a href="#{$firstLetter}">
        <xsl:value-of select="$firstLetter"/>
      </a>
    </td>
  </xsl:template>

</xsl:stylesheet>

关键部分是这种比较: not(substring(title,1,1)=substring(preceding-sibling::*[1]/title,1,1)) ,它查看DOM而不是排序操作的结果。

我正在寻找的是xslt-1.0中的一种方式来结合这两种转换的效果,所以我有一个样式表,一个未排序的输入列表和一个看起来像的结果目前生成的两个样式表:

Screenshot from the XSLT result

我该怎么做?

2 个答案:

答案 0 :(得分:1)

您可以先对变量进行排序(然后在XSLT中为结果树片段),然后您可以使用类似exsl:node-set的扩展函数将结果树片段转换为要进一步处理的节点集使用现有代码。

因此,您需要进行两项更改,catalog的模板必须为

  <xsl:template match="catalog">
    <table class="main">
      <tr class="header">
        <th> Title </th>
        <th> Artist </th>
        <th> Country </th>
        <th> Company </th>
        <th> Price </th>
        <th> Year </th>
      </tr>
      <xsl:variable name="sorted-cds">
        <xsl:for-each select="cd">
          <xsl:sort select="title"/>
          <xsl:copy-of select="."/>
        </xsl:for-each>
      </xsl:variable>
      <xsl:apply-templates select="exsl:node-set($sorted-cds)/cd"/>
    </table>
  </xsl:template>

并且样式表根必须声明exsl命名空间:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:exsl="http://exslt.org/common"
  exclude-result-prefixes="exsl">

请注意,并非所有XSLT处理器都支持exsl:node-set,而它们通常在专有命名空间中至少支持类似的扩展功能。因此,假设您要使用Microsoft的MSXML(例如在Internet Explorer中),那么您需要使用<xsl:apply-templates select="ms:node-set($sorted-cds)/cd"/>

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:ms="urn:schemas-microsoft-com:xslt"
  exclude-result-prefixes="ms">

答案 1 :(得分:1)

我提供的链接@ michael.hor我再次看了Muenchian。我不知道你可以用substring()实际使用这种技术。我也不是for-each的粉丝。

事实证明我可以使用模板匹配和键功能。因此,独立于扩展功能运行的解决方案,使用当前的XSLT引擎(从未在IE上测试过,在我的Linux或Mac上没有这样做)看起来像这样:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

  <xsl:output method="html"/>
  <xsl:key name="cd-by-letter" match="cd" use="substring(title,1,1)"/>

  <xsl:template match="/">
    <html>
      <head>
        <title> Booklist with books <xsl:value-of select="count(/catalog/cd)"/>
        </title>
        <style type="text/css">
          table.main {width : 100%}
          table.main td {padding : 2px; border-bottom : 1px solid gray}
          th {text-align : left}
          tr.header {background-color : #9acd32}
          table.bar {border: 1px solid gray; background-color #CACACA}
          table.bar td {border-left : 1px solid gray; padding : 4px; margin : 2px; font-size : x-large}
          tr.firstbook {background-color : #CACACA}
          td.firstbook {font-size : xx-large}
          td.firstbook a.up {text-decoration: none; font-size : normal}
        </style>
      </head>
      <body>
        <xsl:apply-templates mode="header"/>
        <xsl:apply-templates/>
      </body>
    </html>
  </xsl:template>

  <xsl:template match="catalog">
    <table class="main">
      <tr class="header">
        <th> Title </th>
        <th> Artist </th>
        <th> Country </th>
        <th> Company </th>
        <th> Price </th>
        <th> Year </th>
      </tr>

      <xsl:apply-templates select="cd[count(. | key('cd-by-letter', substring(title,1,1))[1]) = 1]">
        <xsl:sort select="title"/>
      </xsl:apply-templates>
    </table>
  </xsl:template>

  <xsl:template match="cd">
    <xsl:variable name="firstLetter" select="substring(title,1,1)"/>

    <tr class="firstbook">
      <td class="firstbook" colspan="5">
        <a name="{$firstLetter}">
          <xsl:value-of select="$firstLetter"/>
        </a>
      </td>
      <td class="firstbook">
        <a class="up" href="#">&#11014;</a>
      </td>
    </tr>

    <xsl:apply-templates select="key('cd-by-letter',$firstLetter)" mode="group">
      <xsl:sort select="title"/>
    </xsl:apply-templates>
  </xsl:template>

  <xsl:template match="cd" mode="group">
    <tr>
      <td>
        <xsl:value-of select="title"/>
      </td>
      <td>
        <xsl:value-of select="artist"/>
      </td>
      <td>
        <xsl:value-of select="country"/>
      </td>
      <td>
        <xsl:value-of select="company"/>
      </td>
      <td>
        <xsl:value-of select="price"/>
      </td>
      <td>
        <xsl:value-of select="year"/>
      </td>
    </tr>
  </xsl:template>

  <!-- Header link handling -->
  <xsl:template match="catalog" mode="header">
    <table class="bar">
      <tr>
        <xsl:apply-templates mode="header"
          select="cd[count(. | key('cd-by-letter', substring(title,1,1))[1]) = 1]">
          <xsl:sort select="title"/>
        </xsl:apply-templates>
      </tr>
    </table>
  </xsl:template>

  <xsl:template mode="header" match="cd">
    <xsl:variable name="firstLetter" select="substring(title,1,1)"/>
    <td>
      <a href="#{$firstLetter}">
        <xsl:value-of select="$firstLetter"/>
      </a>
    </td>
  </xsl:template>

</xsl:stylesheet>

感谢所有人的帮助!