【问题标题】:How would I do Cartesian product over nested elements?我将如何对嵌套元素进行笛卡尔积?
【发布时间】:2016-06-02 04:32:39
【问题描述】:

鉴于此数据:

<?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. 带上第一个&lt;optionGroup&gt;
  2. 对于那里的每个&lt;option&gt;,遍历所有其他&lt;optionGroup&gt;s 并将他们的&lt;option&gt;s 与“当前”一个“加入”——在结果文档中生成一个包含所有这些&lt;option&gt;s 的元素。

任何&lt;pricePoint&gt; 元素中&lt;optionGroup&gt; 元素的数量未知(一个或多个),任何&lt;optionGroup&gt; 元素中&lt;option&gt; 元素的数量也是未知的(一个或多个)。

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

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

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

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

【问题讨论】:

    标签: xslt permutation self-join cartesian-product


    【解决方案1】:

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

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

    【讨论】:

    • 我认为您不需要副本,您可以将元素传递给:` `
    • 你是对的!我有点过于复杂了。根据您的评论,我在答案中添加了一个新的改进版本。谢谢!
    • 谢谢@MartinHonnen 和蒂姆!我开始认为仅使用 XSLT 并不能真正解决这个问题,因为它需要从“最后一个”集合中的每个元素进行某种回溯,而我不知道如何实现它。很高兴我错了。再次感谢!
    • 我还会将&lt;xsl:copy-of select="$previous" /&gt; &lt;xsl:copy-of select="." /&gt; 缩短为&lt;xsl:copy-of select="$previous | ."/&gt;
    猜你喜欢
    • 1970-01-01
    • 2011-02-06
    • 1970-01-01
    • 2018-11-09
    • 2017-08-24
    • 2015-05-14
    • 2021-11-06
    • 2021-05-06
    • 1970-01-01
    相关资源
    最近更新 更多