XSLT:将多个序列合并为一个序列

时间:2018-01-26 15:29:13

标签: xml variables xpath foreach xslt-1.0

假设我有一个这样的XML文档:

<?xml version="1.0" encoding="UTF-8"?>
<everything>
    <something>
        <part>
            <prop type="a">1</prop>
            <prop type="b">2</prop>
            <prop type="c">3</prop>
        </part>
        <part>
            <prop type="a">4</prop>
            <!--No b!-->
            <prop type="c">5</prop>
        </part>
    </something>
    <something>
        <part>
            <prop type="a">6</prop>
            <prop type="b">7</prop>
            <!--No c!-->
        </part>
    </something>
</everything>

我想要这样的输出:

<something>
   <props type="a">14</props>
   <props type="b">2?</props>
   <props type="c">35</props>
</something>

<something>
   <props type="a">6</props>
   <props type="b">7</props>
   <props type="c">?</props>
</something>

换句话说,首先我想知道文档中的所有prop/@type是什么。在这种情况下,'a','b'和'c'。我已经找到了该部分的解决方案,我将为这个问题掩盖。接下来,对于每个“东西”,我想迭代那些预期的prop元素,并将当前“something”元素中该类型的所有道具合并在一个名为props的新元素下。如果在零件元素中未找到其中一个预期的prop元素,则插入问号。

以下是我最好的尝试。我认为至少有两个问题。首先,在<xsl:for-each select="$allTheProps/prop">̀循环的范围内,我不能像我通常可以在$ allTheProps循环之外那样select="part"。另一个问题是我认为你不能在路径模式中有变量,所以我无法测试@type=$nodeType。最终结果是我的props元素只是空的。我怎样才能解决这些问题?

<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
    <xsl:output method="html" doctype-public="XSLT-compat" omit-xml-declaration="yes" encoding="UTF-8" indent="yes" />

    <xsl:variable name="allTheProps">
        <!--These elements are actually dynamically generated
            based on all prop types found in the document-->
        <prop type="a"></prop>
        <prop type="b"></prop>
        <prop type="c"></prop>
    </xsl:variable>

    <xsl:template match="something">
        <xsl:copy>
            <!--for each found type-->
            <xsl:for-each select="$allTheProps/prop">
                <xsl:variable name="nodeType" select="@type"/>

                <props>
                    <xsl:attribute name="type"><xsl:value-of select="$nodeType" />
                    </xsl:attribute>

                    <!--merge the props from each part of a something node-->
                    <xsl:for-each select="part">
                       <xsl:value-of select="prop[@type=$nodeType]"/>
                       <xsl:if test="not(prop[@type=$nodeType])">?</xsl:if>
                    </xsl:for-each> 
                </props>
            </xsl:for-each>
        </xsl:copy>
    </xsl:template>
</xsl:transform>

2 个答案:

答案 0 :(得分:1)

在XSLT 3.0中,你可以做到

<xsl:template match="something">
  <something>
    <xsl:merge>
      <xsl:merge-source for-each="part" select="prop">
        <xsl:merge-key select="@type">
      </xsl:merge-source>
      <xsl:merge-action>
        <props type="{current-merge-key()}">
          <xsl:value-of select="string-join(current-merge-group(), '')"/>
        </props>
      </xsl:merge-action>
    </xsl:merge>
  </something>
</xsl:template>

答案 1 :(得分:0)

您忘记了第一个<xsl:for-each select="$allTheProps/prop">将上下文更改为变量allTheProps,因此您的下一个<xsl:for-each select="part">将在此变量的上下文中执行,在您期望的模板中(part)。因此,您最终会在for-each

中找到一个空的节点集

解决方案是在变量中保留上下文 - 这里我将其命名为subTree - 并访问此变量。因此,只需进行两项小修改即可使代码按预期工作。

<xsl:template match="something">
    <xsl:variable name="subTree" select="*" />        <!-- save context -->

    <xsl:copy>
        <!--for each found type-->
        <xsl:for-each select="$allTheProps/prop">
            <xsl:variable name="nodeType" select="@type"/>

            <props>
                <xsl:attribute name="type"><xsl:value-of select="$nodeType" />
                </xsl:attribute>

                <!--merge the props from each part of a something node-->
                <xsl:for-each select="$subTree">     <!-- access variable -->
                   <xsl:value-of select="prop[@type=$nodeType]"/>
                   <xsl:if test="not(prop[@type=$nodeType])">?</xsl:if>
                </xsl:for-each> 
            </props>
        </xsl:for-each>
    </xsl:copy>
</xsl:template>