复杂选择XSL 1.0节点集

时间:2015-02-23 11:57:56

标签: xslt xpath xslt-1.0

(这个问题是我问题的简化版本。可以找到已经回答的更简化的版本here。由于michael.hor257k的评论,我发布了这个更复杂的问题谁建议可能有一种替代方法可以解决它 - 可能在循环中使用select,或者可能采用完全不同的方法。)

我想处理一个我无法控制的格式的XML文件来生成C ++代码。我需要以几种不同的方式处理XML中定义的函数,以生成代码的不同部分。作为其中的一部分,我需要选择与复杂条件匹配的函数参数子集,并将此选择传递给多个命名模板;命名模板需要能够访问原始文档。

此示例创建一个复杂的C ++函数参数选择,这些参数不具有常量值(即相同的最小值和最大值),其中min和max可以是十进制或十六进制,使用" GenerateNonFixedParameters"模板。参数引用位于文档中其他位置的枚举,这些定义由命名模板调用" ListParameterValues"引用。

有两个问题。

  1. 创建变量" nonFixedParameters"不使用选择。我无法弄清楚如何使用select这样一个复杂的情况(XSL 1.0),但也许有办法。

  2. 节点的副本不够,因为" ListParameterValues"当前的模板需要对文档中的一组原始节点进行操作。

  3. 示例XSL标记了这两个问题的位置:

    <?xml version="1.0" encoding="UTF-8" ?>
    <xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
        <xsl:output method="text" encoding="iso-8859-1" omit-xml-declaration="yes" />
    
        <xsl:template match="/">
            <xsl:for-each select="//function">
                <!-- 1. This does not use 'select' therefore it does not work. This is XSL 1.0 so as="node()*" cannot be used. -->
                <xsl:variable name="nonFixedParameters">
                    <xsl:call-template name="GenerateNonFixedParameters"/>
                </xsl:variable>
                <xsl:call-template name="ListParameterValues">
                    <xsl:with-param name="parameters" select="$nonFixedParameters"/>
                </xsl:call-template>
            </xsl:for-each>
        </xsl:template>
    
        <xsl:template name="ListParameterValues">
            <xsl:param name="parameters"/>
            <xsl:for-each select="$parameters">
                <xsl:value-of select="@name"/>
                <xsl:text>[</xsl:text>
                <xsl:variable name="min">
                    <xsl:call-template name="ToNum">
                        <xsl:with-param name="hexOrNum" select="@min" />
                    </xsl:call-template>
                </xsl:variable>
                <xsl:variable name="max">
                    <xsl:call-template name="ToNum">
                        <xsl:with-param name="hexOrNum" select="@max" />
                    </xsl:call-template>
                </xsl:variable>
                <!-- 2. This must be executed in the context of a document node, therefore this does not work. -->
                <xsl:for-each select="//enum[@name=current()/@enum]/value">
                    <xsl:if test="@val &gt;= $min and @val &lt;= $max">
                        <xsl:value-of select="@name"/>
                        <xsl:text> </xsl:text>
                    </xsl:if>
                </xsl:for-each>
                <xsl:text>] </xsl:text>
            </xsl:for-each>
        </xsl:template>
    
        <xsl:template name="GenerateNonFixedParameters">
            <xsl:for-each select="parameter">
                <xsl:variable name="min">
                    <xsl:call-template name="ToNum">
                        <xsl:with-param name="hexOrNum" select="@min" />
                    </xsl:call-template>
                </xsl:variable>
                <xsl:variable name="max">
                    <xsl:call-template name="ToNum">
                        <xsl:with-param name="hexOrNum" select="@max" />
                    </xsl:call-template>
                </xsl:variable>
                <xsl:if test="$min != $max">
                    <!-- Here a copy is clearly the wrong approach! -->
                    <xsl:copy-of select="."/>
                </xsl:if>
            </xsl:for-each>
        </xsl:template>
    
        <xsl:template name="HexToNum">
            <xsl:param name="hex" />
            <xsl:param name="num" select="0"/>
            <xsl:param name="msb" select="translate(substring($hex, 1, 1), 'abcdef', 'ABCDEF')"/>
            <xsl:param name="value" select="string-length(substring-before('0123456789ABCDEF', $msb))"/>
            <xsl:param name="result" select="16 * $num + $value"/>
            <xsl:if test="string-length($hex) &gt; 1">
                <xsl:call-template name="HexToNum">
                    <xsl:with-param name="hex" select="substring($hex, 2)"/>
                    <xsl:with-param name="num" select="$result"/>
                </xsl:call-template>
            </xsl:if>
            <xsl:if test="string-length($hex) &lt;= 1">
                <xsl:value-of select="$result"/>
            </xsl:if>
        </xsl:template>
    
        <xsl:template name="ToNum">
            <xsl:param name="hexOrNum" />
            <xsl:if test="starts-with($hexOrNum, '0x')">
                <xsl:call-template name="HexToNum">
                    <xsl:with-param name="hex" select="substring-after($hexOrNum, '0x')"/>
                </xsl:call-template>
            </xsl:if>
            <xsl:if test="not(starts-with($hexOrNum, '0x'))">
                <xsl:value-of select="$hexOrNum"/>
            </xsl:if>
        </xsl:template>
    
    </xsl:transform>
    

    简单的XML来提供上述内容:

    <?xml version="1.0" encoding="UTF-8"?>
    <body>
        <dictionary>
            <enum name="EnumName">
                <value name="firstValue" val="1" />
                <value name="secondValue" val="2" />
                <value name="thirdValue" val="3" />
                <value name="forthValue" val="4" />
                <value name="fifthValue" val="5" />
            </enum>
        </dictionary>
        <function name="FunctionOne">
            <parameter name="p1" type="enum" enum="EnumName" min="2" max="0x4"/>
            <parameter name="p2" type="enum" enum="EnumName" min="0x03" max="3"/>
        </function>
    </body>
    

    想要输出。请注意,p1列出了[min..max]中的所有名称,但是p2没有列出,因为min和max具有相同的值。

    p1[secondValue thirdValue forthValue ] p2[]
    

2 个答案:

答案 0 :(得分:2)

我认为如果使用像exsl:node-set这样的扩展函数将结果树片段转换为节点集并且存储主输入树的根节点,则可以使样式表与XSLT 1.0一起使用然后,您可以将主输入文档中的节点与新构建的临时树的节点进行比较,从而将全局变量或参数转换为全局变量或参数。

基于这些建议,代码看起来像

<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0" xmlns:exsl="http://exslt.org/common">
    <xsl:output method="text" encoding="iso-8859-1" omit-xml-declaration="yes" />

    <xsl:variable name="main-root" select="/"/>

    <xsl:template match="/">
        <xsl:for-each select="//function">
            <!-- 1. Using exsl:node-set or similar you can convert that result tree fragment into a node set to process it further -->
            <xsl:variable name="nonFixedParameters">
                <xsl:call-template name="GenerateNonFixedParameters"/>
            </xsl:variable>
            <xsl:call-template name="ListParameterValues">
                <xsl:with-param name="parameters" select="$nonFixedParameters"/>
            </xsl:call-template>
        </xsl:for-each>
    </xsl:template>

    <xsl:template name="ListParameterValues">
        <xsl:param name="parameters"/>
        <!-- <xsl:for-each xmlns:ms="urn:schemas-microsoft-com:xslt" select="ms:node-set($parameters)/parameter"> for MSXML or XslTransform -->
        <xsl:for-each select="exsl:node-set($parameters)/parameter">
            <xsl:value-of select="@name"/>
            <xsl:text>[</xsl:text>
            <xsl:variable name="min">
                <xsl:call-template name="ToNum">
                    <xsl:with-param name="hexOrNum" select="@min" />
                </xsl:call-template>
            </xsl:variable>
            <xsl:variable name="max">
                <xsl:call-template name="ToNum">
                    <xsl:with-param name="hexOrNum" select="@max" />
                </xsl:call-template>
            </xsl:variable>
            <!-- 2. This must be executed in the context of a document node, therefore using the global variable works. -->
            <xsl:for-each select="$main-root//enum[@name=current()/@enum]/value">
                <xsl:if test="@val &gt;= $min and @val &lt;= $max">
                    <xsl:value-of select="@name"/>
                    <xsl:text> </xsl:text>
                </xsl:if>
            </xsl:for-each>
            <xsl:text>] </xsl:text>
        </xsl:for-each>
    </xsl:template>

    <xsl:template name="GenerateNonFixedParameters">
        <xsl:for-each select="parameter">
            <xsl:variable name="min">
                <xsl:call-template name="ToNum">
                    <xsl:with-param name="hexOrNum" select="@min" />
                </xsl:call-template>
            </xsl:variable>
            <xsl:variable name="max">
                <xsl:call-template name="ToNum">
                    <xsl:with-param name="hexOrNum" select="@max" />
                </xsl:call-template>
            </xsl:variable>
            <xsl:if test="$min != $max">
                <xsl:copy-of select="."/>
            </xsl:if>
        </xsl:for-each>
    </xsl:template>

    <xsl:template name="HexToNum">
        <xsl:param name="hex" />
        <xsl:param name="num" select="0"/>
        <xsl:param name="msb" select="translate(substring($hex, 1, 1), 'abcdef', 'ABCDEF')"/>
        <xsl:param name="value" select="string-length(substring-before('0123456789ABCDEF', $msb))"/>
        <xsl:param name="result" select="16 * $num + $value"/>
        <xsl:if test="string-length($hex) &gt; 1">
            <xsl:call-template name="HexToNum">
                <xsl:with-param name="hex" select="substring($hex, 2)"/>
                <xsl:with-param name="num" select="$result"/>
            </xsl:call-template>
        </xsl:if>
        <xsl:if test="string-length($hex) &lt;= 1">
            <xsl:value-of select="$result"/>
        </xsl:if>
    </xsl:template>

    <xsl:template name="ToNum">
        <xsl:param name="hexOrNum" />
        <xsl:if test="starts-with($hexOrNum, '0x')">
            <xsl:call-template name="HexToNum">
                <xsl:with-param name="hex" select="substring-after($hexOrNum, '0x')"/>
            </xsl:call-template>
        </xsl:if>
        <xsl:if test="not(starts-with($hexOrNum, '0x'))">
            <xsl:value-of select="$hexOrNum"/>
        </xsl:if>
    </xsl:template>

</xsl:transform>

该示例在http://xsltransform.net/94hvTzi/1上线。

答案 1 :(得分:1)

让我展示一种在原始上下文中实际选择和处理原始节点的不同方法 - 正如前一个线程中所讨论的那样。考虑:

XSLT 1.0

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >
<xsl:output method="text" encoding="utf-8"/>

<xsl:template match="/">
    <xsl:for-each select="body/function">
        <xsl:call-template name="select-parameters">
            <xsl:with-param name="input-set" select="parameter"/>
        </xsl:call-template>
    </xsl:for-each>
</xsl:template>

<xsl:template name="select-parameters">
    <xsl:param name="input-set"/>
    <xsl:param name="output-set" select="dummy-node"/>
    <xsl:variable name="current-node" select="$input-set[1]" />
    <xsl:choose>
        <xsl:when test="$current-node">
            <xsl:variable name="min">
                <xsl:call-template name="ToNum">
                    <xsl:with-param name="hexOrNum" select="$current-node/@min" />
                </xsl:call-template>
            </xsl:variable>
            <xsl:variable name="max">
                <xsl:call-template name="ToNum">
                    <xsl:with-param name="hexOrNum" select="$current-node/@max" />
                </xsl:call-template>
            </xsl:variable>
            <!-- recursive call -->
            <xsl:call-template name="select-parameters">
                <xsl:with-param name="input-set" select="$input-set[position() > 1]"/>
                <xsl:with-param name="output-set" select="$output-set | $current-node[$min != $max]"/>
            </xsl:call-template>
        </xsl:when>
        <xsl:otherwise>
            <!-- call a template to process the currently selected node-set -->
            <xsl:call-template name="process-parameters">
                <xsl:with-param name="input-set" select="$output-set"/>
            </xsl:call-template>
            <!-- call more templates here, if required -->
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>

<xsl:key name="enum-by-name" match="enum" use="@name" />

<xsl:template name="process-parameters">
    <xsl:param name="input-set"/>
        <xsl:for-each select="$input-set">
            <xsl:variable name="min">
                <xsl:call-template name="ToNum">
                    <xsl:with-param name="hexOrNum" select="@min" />
                </xsl:call-template>
            </xsl:variable>
            <xsl:variable name="max">
                <xsl:call-template name="ToNum">
                    <xsl:with-param name="hexOrNum" select="@max" />
                </xsl:call-template>
            </xsl:variable>
            <xsl:value-of select="concat(@name, '[')"/>
            <xsl:for-each select="key('enum-by-name', @enum)/value[@val &gt;= $min and @val &lt;= $max]">
                <xsl:value-of select="@name"/>
                <xsl:text> </xsl:text>
            </xsl:for-each>
            <xsl:text>] </xsl:text>
        </xsl:for-each>
</xsl:template>



<xsl:template name="HexToNum">
    <xsl:param name="hex" />
    <xsl:param name="num" select="0"/>
    <xsl:param name="msb" select="translate(substring($hex, 1, 1), 'abcdef', 'ABCDEF')"/>
    <xsl:param name="value" select="string-length(substring-before('0123456789ABCDEF', $msb))"/>
    <xsl:param name="result" select="16 * $num + $value"/>
    <xsl:if test="string-length($hex) &gt; 1">
        <xsl:call-template name="HexToNum">
            <xsl:with-param name="hex" select="substring($hex, 2)"/>
            <xsl:with-param name="num" select="$result"/>
        </xsl:call-template>
    </xsl:if>
    <xsl:if test="string-length($hex) &lt;= 1">
        <xsl:value-of select="$result"/>
    </xsl:if>
</xsl:template>

<xsl:template name="ToNum">
    <xsl:param name="hexOrNum" />
    <xsl:if test="starts-with($hexOrNum, '0x')">
        <xsl:call-template name="HexToNum">
            <xsl:with-param name="hex" select="substring-after($hexOrNum, '0x')"/>
        </xsl:call-template>
    </xsl:if>
    <xsl:if test="not(starts-with($hexOrNum, '0x'))">
        <xsl:value-of select="$hexOrNum"/>
    </xsl:if>
</xsl:template>

</xsl:stylesheet>

这种方法的问题在于它的工作方式与宣传的完全相同;在选择过程结束时选择的节点是原始的未修改参数。因此,它们仍然携带十进制和十六进制值的混合,并且在处理所选集时必须再次转换它们。

因此,通过将值标准化为公共基础来预处理参数可能更有价值,然后使用结果(转换为节点集)进行其余处理。我不会花费太多精力来选择那些符合标准的东西 - 因为一旦这些值一致,选择就变得微不足道了。如果您愿意,我会发布一个演示该演示的演示。