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

Obtaining Previous element in xslt 1.0 sort order, not dom order

我有一个 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 检查标题字母的变化。结果 sheet 如下所示:

<?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中的一种方法结合两个t运行sformations的效果,所以我有一个风格sheet,一个未排序的输入列表和一个看起来像当前两种样式sheet的结果:

我该怎么做?

你可以先排序到一个变量中(然后在 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,而它们通常在专有名称空间中至少支持类似的扩展功能。所以假设你想使用微软的 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">

感谢 link 提供了我的@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>

谢谢大家的帮助!