我将如何对嵌套元素进行笛卡尔积?

How would I do Cartesian product over nested elements?

鉴于此数据:

<?xml version='1.0' encoding='utf-8'?>
<data>
    <pricePoint>
        <optionGroup id='g1'>
            <option id='a1'>a1</option>
            <option id='a2'>a2</option>
        </optionGroup>
        <optionGroup id='g2'>
            <option id='b1'>b1</option>
            <option id='b2'>b2</option>
        </optionGroup>
        <optionGroup id='g3'>
            <option id='c1'>c1</option>
            <option id='c2'>c2</option>
        </optionGroup>
    </pricePoint>
</data>

我要的结果是:

<result>
    <variant>
        <option id='a1'>a1</option>
        <option id='b1'>b1</option>
        <option id='c1'>c1</option>
    </variant>
    <variant>
        <option id='a1'>a1</option>
        <option id='b1'>b1</option>
        <option id='c2'>c2</option>
    </variant>
    <variant>
        <option id='a1'>a1</option>
        <option id='b2'>b2</option>
        <option id='c1'>c1</option>
    </variant>
    <variant>
        <option id='a1'>a1</option>
        <option id='b2'>b2</option>
        <option id='c2'>c2</option>
    </variant>
    <variant>
        <option id='a2'>a2</option>
        <option id='b1'>b1</option>
        <option id='c1'>c1</option>
    </variant>
    <variant>
        <option id='a2'>a2</option>
        <option id='b1'>b1</option>
        <option id='c2'>c2</option>
    </variant>
    <variant>
        <option id='a2'>a2</option>
        <option id='b2'>b2</option>
        <option id='c1'>c1</option>
    </variant>
    <variant>
        <option id='a2'>a2</option>
        <option id='b2'>b2</option>
        <option id='c2'>c2</option>
    </variant>
</result>

  1. 带上第一个<optionGroup>
  2. 对于那里的每个 <option>,遍历所有其他 <optionGroup> "join" 他们的 <option> 和 "current" 一个——在结果文档中生成一个包含所有这些 <option> 的元素。

任何 <pricePoint> 元素中的 <optionGroup> 元素数量未知(一个或多个),并且任何 <optionGroup> 元素中的 <option> 元素数量未知,也是(一个或多个)。

据我所知,这是制作笛卡尔积的经典案例,但我不知道如何使用 XSLT 1.0 正确实现它。

到目前为止,我的(相当无助的)尝试围绕着应用模板匹配 <optionGroup> 元素的想法,同时处理其中一个元素并试图将其排除在进一步处理之外,如下所示:

<xsl:stylesheet
    version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

    <xsl:output method="xml" indent="yes" />

    <xsl:template match="/">
        <xsl:apply-templates />
    </xsl:template>

    <xsl:template match="optionGroup">
        <xsl:message>Saw optionGroup <xsl:value-of select="@id" /></xsl:message>
        <xsl:variable name="id" select="@id" />
        <xsl:apply-templates select="../optionGroup[@id != $id]" />
    </xsl:template>

    <xsl:template match="option">
        <xsl:message>Saw option</xsl:message>
    </xsl:template>

</xsl:stylesheet>

当然,到目前为止我所有的尝试都在应用模板时因无限递归而失败。

这是一种方法,虽然我不确定它的效率,因为它必须重复创建以前节点的副本。

<xsl:stylesheet
    version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

    <xsl:output method="xml" indent="yes" />

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

    <xsl:template match="pricePoint">
        <xsl:apply-templates select="optionGroup[1]/option" />
    </xsl:template>

    <xsl:template match="optionGroup[following-sibling::*]/option">
        <xsl:param name="previous" />
        <xsl:apply-templates select="../following-sibling::optionGroup[1]/option">
            <xsl:with-param name="previous">
                <xsl:if test="$previous">
                    <xsl:copy-of select="$previous" />
                </xsl:if>
                <xsl:copy-of select="." />
            </xsl:with-param>
        </xsl:apply-templates>
    </xsl:template>

    <xsl:template match="option">
        <xsl:param name="previous" />
        <variant>
            <xsl:copy-of select="$previous" />
            <xsl:copy-of select="." />
        </variant>
    </xsl:template>
</xsl:stylesheet>

它首先选择第一个 optionGroup 下的 option 元素,然后递归调用下一组 option 元素的模板,传递 option 作为参数,以允许它建立一个包含先前 option 个元素的列表。

当涉及到最后一个optionGroup下的option个元素时,它可以输出所有以前的元素,以及当前的元素。

编辑:为了回应 Martin Honnen 的评论,这里有一种稍微改进的方法,它不依赖于传递的副本

<xsl:stylesheet
    version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

    <xsl:output method="xml" indent="yes" />

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

    <xsl:template match="pricePoint">
        <xsl:apply-templates select="optionGroup[1]/option" />
    </xsl:template>

    <xsl:template match="optionGroup[following-sibling::*]/option">
        <xsl:param name="previous" select="/.." />
        <xsl:apply-templates select="../following-sibling::optionGroup[1]/option">
            <xsl:with-param name="previous" select="$previous | ." />
        </xsl:apply-templates>
    </xsl:template>

    <xsl:template match="option">
        <xsl:param name="previous" />
        <variant>
            <xsl:copy-of select="$previous" />
            <xsl:copy-of select="." />
        </variant>
    </xsl:template>
</xsl:stylesheet>