使用XSLT中ForEach下的“自定义函数”选择不同的值

问题描述

在使用自定义函数在ForEach循环中进行迭代时,我需要忽略重复的值。我有以下示例,并分享了正在尝试做的xslt。我不能在xslt 2.0中申请foreach组,因为它将破坏现有的代码功能。希望通过自定义功能本身解决此问题。

自定义功能

<xsl:function name="OriginalBook.Genre">
    <xsl:param name="book"/>
    <xsl:for-each select="$book[price &lt 10]">
       <xsl:element name="OriginalGenre">
          <xsl:if test="book[not(preceding::genre)]">
            <xsl:value-of select="current()/genre"/>
          </xsl:if>
      </xsl:element>
    </xsl:for-each>
</xsl:function>

<xsl:template match="/">
    <OriginalBook>  
        <xsl:copy-of select="OriginalBook.Genre(/catalog/book)"/>
    </OriginalBook>
</xsl:template>

输入:

<?xml version="1.0"?>
<catalog>
   <book>
      <author>Gambardella,Matthew</author>
      <title>XML Developer's Guide</title>
      <genre>Computer</genre>
      <price>9.95</price>
      <publish_date>2000-10-01</publish_date>
   </book>
   <book>
      <author>Ralls,Kim</author>
      <title>XML Developer's Guide</title>
      <genre>Computer</genre>
      <price>5.95</price>
      <publish_date>2000-12-16</publish_date>
   </book>
   <book>
      <author>Corets,Eva</author>
      <title>Maeve Ascendant</title>
      <genre>Fantasy</genre>
      <price>5.95</price>
      <publish_date>2000-11-17</publish_date>
   </book>
   <book>
      <author>galos,Mike</author>
      <title>Visual Studio 7: A Comprehensive Guide</title>
      <genre>Computer</genre>
      <price>49.95</price>
      <publish_date>2001-04-16</publish_date>
   </book>
</catalog>

所需的输出

<OriginalBook>
    <OriginalGenre>Computer</OriginalGenre>
    <OriginalGenre>Fantasy</OriginalGenre>
</OriginalBook>

解决方法

就其价值而言,假设XSLT 3消除重复项的经典方法是for-each-group group-bydistinct-valuesmap:merge

<?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"
    xmlns:map="http://www.w3.org/2005/xpath-functions/map"
    exclude-result-prefixes="#all"
    xmlns:mf="http://example.com/mf"
    expand-text="yes"
    version="3.0">
    
  <xsl:function name="mf:grouping-example" as="element(OriginalGenre)*">
      <xsl:param name="books" as="element(book)*"/>
      <xsl:for-each-group select="$books[price &lt; 10]" group-by="genre">
          <OriginalGenre>{current-grouping-key()}</OriginalGenre>
      </xsl:for-each-group>
  </xsl:function>
  
  <xsl:function name="mf:distinct-values-example" as="element(OriginalGenre)*">
      <xsl:param name="books" as="element(book)*"/>
      <xsl:for-each select="distinct-values($books[price &lt; 10]/genre)">
          <OriginalGenre>{.}</OriginalGenre>
      </xsl:for-each>
  </xsl:function>
  
  <xsl:function name="mf:map-merge-example" as="element(OriginalGenre)*">
      <xsl:param name="books" as="element(book)*"/>
      <xsl:for-each 
          select="$books[price &lt; 10]/genre!map { data() : . } 
                  => map:merge() => map:keys()">
          <OriginalGenre>{.}</OriginalGenre>
      </xsl:for-each>
  </xsl:function>
  
  <xsl:output indent="yes"/>
  
  <xsl:template match="/">
      <Results>
          <Result-Grouping>
              <xsl:sequence select="mf:grouping-example(catalog/book)"/>
          </Result-Grouping>
          <Result-distinct-values>
              <xsl:sequence select="mf:distinct-values-example(catalog/book)"/>
          </Result-distinct-values>
          <Result-map-merge-example>
              <xsl:sequence select="mf:map-merge-example(catalog/book)"/>
          </Result-map-merge-example>
      </Results>
  </xsl:template>
  
</xsl:stylesheet>

顺序循环的“等效”不是xsl:for-each,而是xsl:iterate

  <xsl:function name="mf:iterate-example" as="element(OriginalGenre)*">
      <xsl:param name="books" as="element(book)*"/>
      <xsl:iterate select="$books[price &lt; 10]/genre/data()">
          <xsl:param name="genres" as="xs:string*" select="()"/>
          <xsl:if test="not(. = $genres)">
            <OriginalGenre>{.}</OriginalGenre>
          </xsl:if>
          <xsl:next-iteration>
            <xsl:with-param name="genres" select="if (. = $genres) then $genres else ($genres,.)"/>
          </xsl:next-iteration>
      </xsl:iterate>
  </xsl:function>

https://xsltfiddle.liberty-development.net/bEzkTcU

,

我看到了几个问题。

  • 如果要创建多个OriginalGenre元素,请为该函数调用产生的每个值创建一个元素,然后将该元素移入函数内部。
  • 在您的for-each语句中,当前上下文正在更改。 xsl:if将以book作为上下文项进行评估,因此它不会有book子级,并且您要确保它的genre不相同与任何preceding::genre一样,例如:<xsl:if test="not(genre = preceding::genre)">
  • 该函数未绑定到名称空间,因此我只分配了local
  • 我会使用xsl:copy-of而不是xsl:sequence

我会做这样的事情:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="3.0" xmlns:local="local">
    <xsl:output indent="yes"/>
    
    <xsl:function name="local:OriginalBook.Genre">
        <xsl:param name="book"/>
        <xsl:for-each select="$book[price &lt; 10]">
            <xsl:if test="not(genre = preceding::genre)">
                <OriginalGenre><xsl:value-of select="current()/genre"/></OriginalGenre>
            </xsl:if>
        </xsl:for-each>
    </xsl:function>
    
    <xsl:template match="/">
        <OriginalBook>  
            <xsl:sequence select="local:OriginalBook.Genre(/catalog/book)"/>
        </OriginalBook>
    </xsl:template>
</xsl:stylesheet>