是否可以将 XSLT 标记化处理从 for-each 重构到模板中?

Is it possible to refactor XSLT tokenization handling out of a for-each and into a template?

我有以下 XML 源文件,其中包含以不同货币显示的字符串列表价格:

<?xml version="1.0" encoding="UTF-8"?>
<menu>
    <item>
        <name id="A">Onion Rings</name>
        <prices>.00,R45.00</prices>
        <kcal>270</kcal>
    </item>
    <!-- Some more items -->
</menu>

以及以下对价格字符串进行标记的 XSL 文件,以便输出文件对每个价格都有一个单独的价格元素:

<xsl:output method="xml" omit-xml-declaration="no" indent="yes" media-type="text/xml" />

<xsl:template match="/">
    <foods>
        <xsl:apply-templates select="menu/item">
            <xsl:sort select="name/@id" order="descending" />
        </xsl:apply-templates>
    </foods>
</xsl:template>

<xsl:template match="menu/item">
    <food>
        <xsl:attribute name="id">
            <xsl:value-of select="name/@id" />
        </xsl:attribute>
        <name><xsl:value-of select="name" /></name>
        <prices>
            <xsl:for-each select="tokenize(prices, ',')">
                <price><xsl:value-of select="." /></price>
            </xsl:for-each>
        </prices>
        <calories><xsl:value-of select="kcal" /></calories>
    </food>
</xsl:template>

这会产生我想要的输出,但有没有办法分解出 for-each 并让每个项目由模板处理?类似于:

<food>
    ...
    <prices>
       <xsl:apply-templates select="tokenize(prices, ',')" />
    </prices>
    ...
</food>

<template match="tokenize(prices, ',')">
    <price><xsl:value-of select="." /></price>
</template>

在 Exselt 或 Saxon 9.6/9.7 PE 和 EE 支持的 XSLT 3.0 中,您可以按如下方式进行操作:

<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="3.0"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    exclude-result-prefixes="xs">

    <xsl:output method="xml" omit-xml-declaration="no" indent="yes" media-type="text/xml" />

    <xsl:template match="@* | node()">
        <xsl:copy>
            <xsl:apply-templates select="@* | node()"/>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="/">
        <foods>
            <xsl:apply-templates select="menu/item">
                <xsl:sort select="name/@id" order="descending" />
            </xsl:apply-templates>
        </foods>
    </xsl:template>

    <xsl:template match="menu/item">
        <food id="{name/@id}">
            <xsl:apply-templates select="name"/>
            <prices>
                <xsl:apply-templates select="tokenize(prices, ',')"/>
            </prices>
            <xsl:apply-templates select="kcal"/>
        </food>
    </xsl:template>

    <xsl:template match="kcal">
        <calories>
            <xsl:value-of select="."/>
        </calories>
    </xsl:template>

    <xsl:template match=".[. instance of xs:string]">
        <price><xsl:value-of select="." /></price>
    </xsl:template>

</xsl:transform>

使用 XSLT 2.0,您只能编写匹配节点的模板,而不能编写字符串(或日期或数字)等原始值,这是从 XSLT 3.0 开始的新功能。