使用兄弟节点以递归方式替换XSLT中的文本

时间:2013-04-13 17:34:17

标签: xslt recursion

所以我的XML看起来像:

<?xml version="1.0" encoding="UTF-8" ?>
<?xml-stylesheet type="text/xsl" href="reports.xsl"?>
<analysis>
    <layers>
        <layer name="Initial Bool" id="l0">
            <code><![CDATA[some
multiline text
goes here]]></code>
        </layer>
    </layers>
    <report name="Some Node">
        <issues>
            <issue>
                <layer id="l0" />
                <name>Replace expression</name>
                <description>
                    Test description.
                </description>
                <locations>
                    <replace start="20" end="30">hello</replace>
                </locations>
            </issue>
        </issues>
    </report>
</analysis>

我当前的XSLT是:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output indent="yes" method="html" />

    <xsl:template match="/analysis">
        <html>
            <head>
                <title>Tychaia Analysis Reports</title>
                <script src="https://google-code-prettify.googlecode.com/svn/loader/run_prettify.js">
                </script>
            </head>
            <body>
                <xsl:apply-templates />
            </body>
        </html>
    </xsl:template>

    <xsl:template match="/analysis/layers">
        <!-- Do nothing with layers as we reference them from the reports -->
    </xsl:template>

    <xsl:template match="/analysis/report">
        <h2>
            <xsl:value-of select="@name" />
        </h2>
        <xsl:apply-templates />
    </xsl:template>

    <xsl:template match="/analysis/report/issues">
        <xsl:apply-templates />
    </xsl:template>

    <xsl:template match="/analysis/report/issues/issue">
        <h3>
            <xsl:value-of select="name" />
        </h3>
        <p>
            <xsl:value-of select="description" />
        </p>
        <code style="border: none;" class="prettyprint">
            <!-- This is the bit that I can't work out how to do (ideally the
            replaced content would also then get passed into
            remove-leading-whitespace as the text data):
            <xsl:call-template name="format-locations">
                <xsl:with-param name="text" select="/analysis/layers/layer[@id=current()/layer/@id]" />
                <xsl:with-param name="locations" select="locations" />
            </xsl:call-template>
            -->
            <xsl:call-template name="remove-leading-whitespace">
                <xsl:with-param name="text" select="/analysis/layers/layer[@id=current()/layer/@id]" />
            </xsl:call-template>
        </code>
    </xsl:template>

    <!-- THIS DOESN'T WORK -->
    <xsl:template name="format-locations">
        <xsl:param name="text" />
        <xsl:param name="locations" />
        <xsl:variable name="result" select="$text" />
        <xsl:for-each select="$locations/replace">
            <xsl:variable name="result">
                <xsl:value-of select="substring($result, 1, current()/@start)" />
                <xsl:text>yo replaced</xsl:text>
                <xsl:value-of select="substring($result, current()/@end)" />
            </xsl:variable>
        </xsl:for-each>
        <xsl:copy-of select="$result" />
    </xsl:template>

    <!-- Removes leading whitespaces for code rendering -->
    <xsl:template name="remove-leading-whitespace">
        <xsl:param name="text" />
        <xsl:choose>
            <xsl:when test="substring($text, 1, 1) = ' ' or substring($text, 1, 1) = '&#xA;' or substring($text, 1, 1) = '&#xA0;'">
                <xsl:call-template name="remove-leading-whitespace">
                    <xsl:with-param name="text" select="substring($text, 2)" />
                </xsl:call-template>
            </xsl:when>
            <xsl:otherwise>
                <xsl:call-template name="replace-spaces">
                    <xsl:with-param name="text" select="$text" />
                </xsl:call-template>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>

    <!-- Replaces spaces and newlines in code with nbsp and <br/> -->
    <xsl:template name="replace-spaces">
        <xsl:param name="text" />
        <xsl:choose>
            <xsl:when test="contains($text, ' ')">
                <xsl:call-template name="replace-spaces">
                    <xsl:with-param name="text" select="substring-before($text, ' ')" />
                </xsl:call-template>
                <xsl:text>&#xA0;</xsl:text>
                <xsl:call-template name="replace-spaces">
                    <xsl:with-param name="text" select="substring-after($text, ' ')" />
                </xsl:call-template>
            </xsl:when>
            <xsl:when test="contains($text, '&#xA;')">
                <xsl:call-template name="replace-spaces">
                    <xsl:with-param name="text" select="substring-before($text, '&#xA;')" />
                </xsl:call-template>
                <br />
                <xsl:call-template name="replace-spaces">
                    <xsl:with-param name="text" select="substring-after($text, '&#xA;')" />
                </xsl:call-template>
            </xsl:when>
            <xsl:otherwise>
                <xsl:value-of select="$text" />
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>

</xsl:stylesheet>

我希望能够根据<code>中定义的替换内容替换<locations>代码中的内容。这样报告可以突出显示部分代码和每条代码的消息。

但是,我对format-locations的当前策略不起作用,因为您无法在XSLT中第二次设置变量,但我需要非递归地遍历<location>内的子项,即将替换标记嵌套在彼此内部(例如<replace ...><replace ...>...</replace></replace>)是没有意义的,因为XSLT不会递归迭代兄弟姐妹。

无论如何我可以让这个XSLT迭代兄弟姐妹并根据需要执行替换吗?

1 个答案:

答案 0 :(得分:0)

试试这个模板。

<xsl:template name="format-locations">
  <xsl:param name="text"/>
  <xsl:param name="locations"/>

  <xsl:variable name="result">
     <xsl:value-of select="substring($text, 1, $locations/replace[1]/@start)"/>
     <xsl:text>yo replaced</xsl:text>
     <xsl:for-each select="$locations/replace[position() &gt; 1]">
        <xsl:value-of select="substring($text, preceding-sibling::replace[1]/@end + 1, @start - preceding-sibling::replace[1]/@end)"/>
        <xsl:text>yo replaced</xsl:text>
     </xsl:for-each>
     <xsl:value-of select="substring($text, $locations/replace[last()]/@end + 1)"/>
  </xsl:variable>
  <xsl:copy-of select="$result"/>
</xsl:template>

它对第一个替换进行单独检查,然后对后续的(如果有的话)检查它是否根据当前 @start 属性之间的差异来执行子字符串和之前的 @end 属性。最后它将最后一个替换元素分开。