循环xsl的更好方法:对于每个字母?

时间:2019-07-25 00:30:11

标签: xslt xslt-1.0

我有一个长长的XML文件,我从该文件中提取了书名和其他信息,然后按字母顺序对其进行排序,并用每个字母的分隔符。对于不以字母开头(例如数字或符号)的项目,我还需要一个小节。像这样:

  

     

1494-精装书,9.99美元

     

A

     

在金沙之后-平装本,24.95美元

     

北极精神-精装书,65.00美元

     

B

     

Back to the Front-平装本,18.95美元

     

...

我还需要创建一个单独的作者列表,该列表由相同的数据创建,但显示的信息种类不同。

我目前的情况

这是简化的,但是我基本上有两次相同的代码,一次是标题,一次是作者。模板的作者版本使用不同的元素,并且对数据执行不同的操作,因此我不能使用同一模板。

<xsl:call-template name="BIP-letter">
    <xsl:with-param name="letter" select="'#'" />
</xsl:call-template>
<xsl:call-template name="BIP-letter">
    <xsl:with-param name="letter" select="'A'" />
</xsl:call-template>
…
<xsl:call-template name="BIP-letter">
    <xsl:with-param name="letter" select="'Z'" />
</xsl:call-template>

<xsl:template name="BIP-letter">
    <xsl:param name="letter" />
    <xsl:choose>
        <xsl:when test="$letter = '#'">
            <xsl:text>#</xsl:text>
            <xsl:for-each select="//Book[
                                  not(substring(Title,1,1) = 'A') and
                                  not(substring(Title,1,1) = 'B') and
                                  …
                                  not(substring(Title/,1,1) = 'Z')
                                  ]">
                <xsl:sort select="Title" />
                <xsl:appy-templates select="Title" />
                <!-- Add other relevant data here -->
            </xsl:for-each>
        </xsl:when>
        <xsl:otherwise>
            <xsl:value-of select="$letter" />
            <xsl:for-each select="//Book[substring(Title,1,1) = $letter]">
                <xsl:sort select="Title" />
                <xsl:appy-templates select="Title" />
                <!-- Add other relevant data here -->
            </xsl:for-each>
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>

我的问题

上面的代码可以正常工作,但是:

  1. 手动循环浏览每个字母会很长,尤其是必须执行两次。有没有一种方法可以简化?像<xsl:for-each select="[A-Z]">这样的东西,可以在调用模板时用来设置参数吗?

  2. 有没有更简单的方法来选择所有不以字母开头的标题?像//Book[not(substring(Title,1,1) = [A-Z])一样?

  3. 在某些情况下,标题或作者名称以小写字母开头。在上面的代码中,它们将与#标题下的分组,而不是实际的字母。我认为可以解决这一问题的唯一方法-手动执行-将会使代码大量膨胀。

2 个答案:

答案 0 :(得分:0)

此解决方案回答了所有提出的问题:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output method="text"/>
 <xsl:strip-space elements="*"/>

 <xsl:variable name="vLowercase" select="'abcdefghijklmnopqrstuvuxyz'"/>
 <xsl:variable name="vUppercase" select="'ABCDEFGHIJKLMNOPQRSTUVWXYZ'"/>
 <xsl:variable name="vDigits" select="'0123456789'"/>

 <xsl:key name="kBookBy1stChar" match="Book"
          use="translate(substring(Title, 1, 1),
                         'abcdefghijklmnopqrstuvuxyz0123456789',
                         'ABCDEFGHIJKLMNOPQRSTUVWXYZ##########'
                         )"/>

  <xsl:template match="/*">
    <xsl:apply-templates mode="firstInGroup" select=
     "Book[generate-id()
          = generate-id(key('kBookBy1stChar',
                            translate(substring(Title, 1, 1),
                                      concat($vLowercase, $vDigits),
                                      concat($vUppercase, '##########')
                                      )
                            )[1]
                        )
          ]">
      <xsl:sort select="translate(substring(Title, 1, 1),
                                  concat($vLowercase, $vDigits),
                                  concat($vUppercase, '##########')
                                  )"/>
   </xsl:apply-templates>
  </xsl:template>

  <xsl:template match="Book" mode="firstInGroup">
    <xsl:value-of select="'&#xA;'"/>
   <xsl:value-of select="translate(substring(Title, 1, 1),
                                  concat($vLowercase, $vDigits),
                                  concat($vUppercase, '##########')
                                  )"/>
    <xsl:apply-templates select=
    "key('kBookBy1stChar',
         translate(substring(Title, 1, 1),
                   concat($vLowercase, $vDigits),
                   concat($vUppercase, '##########')
                   )
         )">
       <xsl:sort select="Title"/>
    </xsl:apply-templates>
  </xsl:template>

  <xsl:template match="Book">
    <xsl:value-of select="'&#xA;'"/>
    <xsl:value-of select="concat(Title, ' - ', Binding, ', $', price)"/>
  </xsl:template>
</xsl:stylesheet>

当此转换应用于以下xml文档(问题中未提供!):

<Books>
  <Book>
    <Title>After the Sands</Title>
    <Binding>paperback</Binding>
    <price>24.95</price>
  </Book>
  <Book>
    <Title>Cats Galore: A Compendium of Cultured Cats</Title>
    <Binding>hardcover</Binding>
    <price>5.00</price>
  </Book>
  <Book>
    <Title>Arctic Spirit</Title>
    <Binding>hardcover</Binding>
    <price>65.00</price>
  </Book>
  <Book>
    <Title>1494</Title>
    <Binding>hardcover</Binding>
    <price>9.99</price>
  </Book>
  <Book>
    <Title>Back to the Front</Title>
    <Binding>paperback</Binding>
    <price>18.95</price>
  </Book>
</Books>

产生了所需的正确结果

#
1494 - hardcover, $9.99
A
After the Sands - paperback, $24.95
Arctic Spirit - hardcover, $65.00
B
Back to the Front - paperback, $18.95
C
Cats Galore: A Compendium of Cultured Cats - hardcover, $5.00

说明

  1. 使用 Muenchian method 进行分组
  2. 使用标准XPath translate() 功能
  3. 使用 mode 处理以相同(不区分大小写)字符开头的一组图书中的第一本书
  4. 使用 <xsl:sort> 以字母顺序对书籍进行排序

答案 1 :(得分:0)

最有问题的部分是这样:

  

对于不以字母开头(例如数字或符号)的项目,我还需要一个小节。

如果您有一个项目可以开头的所有可能符号的列表,则可以简单地使用translate()将它们全部转换为#字符。否则,它将变得更加复杂。我会尝试类似的东西:

XSLT 1.0(+ EXSLT节点集())

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
extension-element-prefixes="exsl">
<xsl:output method="text" encoding="UTF-8"/>

<xsl:key name="book" match="Book" use="index" />

<xsl:template match="/Books">
    <!-- first-pass: add index char -->
    <xsl:variable name="books-rtf">
        <xsl:for-each select="Book">
            <xsl:copy>
                <xsl:copy-of select="*"/>
                <index>
                    <xsl:variable name="index" select="translate(substring(Title, 1, 1), 'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ')" />
                    <xsl:choose>
                        <xsl:when test="contains('ABCDEFGHIJKLMNOPQRSTUVWXYZ', $index)">
                            <xsl:value-of select="$index"/>
                        </xsl:when>
                        <xsl:otherwise>#</xsl:otherwise>
                    </xsl:choose>
                </index>
            </xsl:copy>
        </xsl:for-each>
    </xsl:variable>
    <xsl:variable name="books" select="exsl:node-set($books-rtf)/Book" />
    <!-- group by index char -->
    <xsl:for-each select="$books[count(. | key('book', index)[1]) = 1]">
        <xsl:sort select="index"/>
        <xsl:value-of select="index"/>
        <xsl:text>&#10;</xsl:text>
        <!-- list books -->
        <xsl:for-each select="key('book', index)">
            <xsl:sort select="Title"/>
            <xsl:value-of select="Title"/>
            <xsl:text> - </xsl:text>
            <xsl:value-of select="Binding"/>
            <xsl:text>, </xsl:text>
            <xsl:value-of select="Price"/>
            <xsl:text>&#10;</xsl:text>
        </xsl:for-each>
        <xsl:text>&#10;</xsl:text>
    </xsl:for-each>
</xsl:template>

</xsl:stylesheet>

但是,这仍然留下了以变音符号开头的项目的问题,例如“Österreich”或说希腊字母。在这种方法下,它们也将聚集在#下。

不幸的是,唯一好的解决方案是迁移到XSLT 2.0。


演示: https://xsltfiddle.liberty-development.net/jyRYYjj/2