XSL 1.0节点集的复杂选择



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

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

有两个问题。

  1. 变量"nonFixedParameters"的创建不使用select。我不知道如何在如此复杂的情况下使用select(XSL1.0),但也许有办法。

  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没有列出任何名称,因为min和max具有相同的值。

p1[secondValue thirdValue forthValue ] p2[]

我认为,如果使用exsl:node-set之类的扩展函数将结果树片段转换为节点集,并且将主输入树的根节点存储为全局变量或参数,则可以将主输入文档中的节点与新建的临时树的节点进行比较,则样式表可以与XSLT1.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>

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

因此,通过将值标准化为一个公共基数来预处理参数,然后将结果(转换为节点集)用于其余的处理,可能更有价值。我不会花那么多精力在选择那些符合标准的上,因为一旦值一致,选择就变得微不足道了。如果你喜欢的话,我会发布一个演示。

相关内容

  • 没有找到相关文章

最新更新