XSL 1.0 节点集的复杂选择

Complex selection of XSL 1.0 node set

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

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

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

有两个问题。

  1. 变量"nonFixedParameters"的创建没有使用select。对于如此复杂的情况 (XSL 1.0),我不知道如何使用 select,但也许有办法。

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

标有这两个问题位置的 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 列出了 none,因为 min 和 max 具有相同的值。

p1[secondValue thirdValue forthValue ] p2[]

我认为如果您使用 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

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

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>

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

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