问题描述
在使用自定义函数在ForEach循环中进行迭代时,我需要忽略重复的值。我有以下示例,并分享了正在尝试做的xslt。我不能在xslt 2.0中申请foreach组,因为它将破坏现有的代码功能。希望通过自定义功能本身解决此问题。
<xsl:function name="OriginalBook.Genre">
<xsl:param name="book"/>
<xsl:for-each select="$book[price < 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-by
,distinct-values
或map: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 < 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 < 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 < 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 < 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 < 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>