我将如何对嵌套元素进行笛卡尔积?
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>
即
- 带上第一个
<optionGroup>
。
- 对于那里的每个
<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>
鉴于此数据:
<?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>
即
- 带上第一个
<optionGroup>
。 - 对于那里的每个
<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>