我如何在嵌套元素上做笛卡尔积?

时间:2016-02-20 15:07:38

标签: xslt permutation self-join cartesian-product

鉴于此数据:

<?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> 并将“{1}}与”当前“一起”加入“ - 在结果文档中生成包含所有这些<option>的单个元素。
  3. 任何<option>元素中<optionGroup>元素的数量未知(一个或多个),任何<pricePoint>元素中的<option>元素数量也是未知的(一个或多个)。

    据我所知,这是制作笛卡尔产品的经典案例,但我对如何使用XSLT 1.0正确实现这一点缺乏想法。

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

    <optionGroup>

    当然,在应用模板时,我迄今为止的所有尝试都失败了,无法递归。

1 个答案:

答案 0 :(得分:3)

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

<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>

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

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

编辑:回应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>