【问题标题】:Complex selection of XSL 1.0 node setXSL 1.0 节点集的复杂选择
【发布时间】:2015-02-23 11:57:55
【问题描述】:

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

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

本示例使用“GenerateNonFixedParameters”模板创建了一个复杂的 C++ 函数参数选择,这些参数没有常量值(即相同的最小值和最大值),其中最小值和最大值可以是十进制或十六进制。参数引用位于文档中其他位置的枚举,这些定义由命名模板调用“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 没有列出任何名称,因为 min 和 max 具有相同的值。

p1[secondValue thirdValue forthValue ] p2[]

【问题讨论】:

  • 如果您需要构造一个结果树片段,然后将其作为一个节点集进行处理,那么 XSLT 1.0 处理器确实支持扩展功能。当然,如果您需要比较来自不同文档的节点(例如中间文档和主输入文档),您可以将主文档存储在变量或参数中。所以xsltransform.net/94hvTzi/1 应该让您了解哪些 XSLT 技术有助于处理结果树片段和不同的文档。我将此作为评论发布,因为它是仅代码/链接的建议。
  • 如果您使用 MSXML 则不支持 exsl:node-set,您可以改用 &lt;xsl:for-each xmlns:ms="urn:schemas-microsoft-com:xslt" select="ms:node-set($parameters)/parameter"&gt;
  • 查看当前的 XSLT,调用模板 GenerateNonFixedParamaters,在其中迭代 parameter 元素。然后,您获取此结果并将选定的参数传递给ListParameterValues,在其中迭代选定的参数并重新计算在GenerateNonFixedParamaters 中计算的相同“最小值”和“最大值”值。或许你可以将两个模板合二为一,这样一计算最小值和最大值就立即输出参数;例如xsltransform.net/eiZQaFn
  • 使用 Martin Honnen 所建议的“节点集”几乎可以肯定是最好的方法。一个不太优雅的替代方法是返回参数的“@name”,并使用字符串函数contains 选择具有匹配名称的参数。看到这个:xsltransform.net/gWmuiJp/1

标签: xslt xpath xslt-1.0


【解决方案1】:

如果您使用像exsl:node-set 这样的扩展函数将结果树片段转换为节点集并且将主输入树的根节点存储到全局变量或参数,然后您将能够将主输入文档中的节点与新构建的临时树的节点进行比较。

根据这些建议,代码看起来像

<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

【讨论】:

    【解决方案2】:

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

    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>
    

    这种方法的问题在于它的工作原理与宣传的完全一样;在选择过程结束时选择的节点是原始的、未修改的参数。结果,它们仍然带有十进制和十六进制值的混合,在处理选定的集合时必须再次转换它们。

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

    【讨论】:

    • 你知道,我对你和其他几个人的回答质量印象深刻,这里和其他问题!这是使用 XSL 1.0 创建节点集的绝佳技术,递归使用 select。对于我的情况,我不能轻易使用它,因为结果变量在循环的深处,而不是在顶部——尽管我承认我没有在我的问题中说明这是一个要求。我需要将节点集传递给的模板是从几个不同的地方调用的。但我从这个答案中学到了一些有用的东西,希望其他人也能!
    猜你喜欢
    • 2013-06-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多