使用 xsl:accumulator 跟踪两个 PI 之间的文本节点

Using xsl:accumulator to keep track of text nodes between two PIs

我正在学习 XSLT 3.0 中的累加器,但我没有找到任何可以帮助我解决当前问题的示例。我有大文件,其中处理指令用于标记修改。我需要将这些处理成可见的标记以供审查过程使用。使用累加器,我成功地跟踪了要显示的最新修改代码。到目前为止,还不错。

由于原始文件很大,我创建了一个简单的示例输入 XML 来显示我的任务的本质,并且我调整了 XSL 以显示我正在尝试使用累加器。

简单输入文件:

<?xml version="1.0" encoding="UTF-8"?>
<root>
    <div>
        <p>Paragraph 1</p>
        <?MyPI Start Modification 1?>
        <p>Paragraph 2</p>
        <p>Paragraph 3</p>
        <?MyPI End Modification 1?>
    </div>
    <div>
        <list>
            <item>
                <p>Paragraph 4</p>
                <?MyPI Start Modification 1?>
                <p>Paragraph 5</p>
                <?MyPI End Modification 1?>
            </item>
            <item>
                <?MyPI Start Modification 1?>
                <p>Paragraph 6</p>
                <p>Paragraph 7</p>
                <?MyPI End Modification 1?>
                <?MyPI Start Modification 2?>
                <p>Paragraph 8</p>
                <?MyPI End Modification 2?>
            </item>
        </list>
        <p>Paragraph 9</p>
    </div>
</root>

我的 XSL 使用累加器进行当前修改:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    exclude-result-prefixes="xs"
    version="3.0">
    
    <xsl:mode use-accumulators="#all"/>
    
    <xsl:accumulator name="modifier" initial-value="'Base text'">
        <xsl:accumulator-rule match="processing-instruction('MyPI')[contains(.,'Modification')]">           
            <xsl:choose>
                <xsl:when test="contains(.,'Start')">
                    <xsl:value-of select="substring-after(.,'Start ')"/>
                </xsl:when>
                <xsl:otherwise>Base text</xsl:otherwise>
            </xsl:choose>
        </xsl:accumulator-rule>
    </xsl:accumulator>

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

    <xsl:template match="node()">
        <xsl:copy>
            <xsl:apply-templates select="node()"/>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="processing-instruction('MyPI')">
        <marker>
            <xsl:value-of select="accumulator-after('modifier')"/>
        </marker>
    </xsl:template>

</xsl:stylesheet>

使用此 XSL 的输出:

<?xml version="1.0" encoding="UTF-8"?><root>
    <div>
        <p>Paragraph 1</p>
        <marker>Modification 1</marker>
        <p>Paragraph 2</p>
        <p>Paragraph 3</p>
        <marker>Base text</marker>
    </div>
    <div>
        <list>
            <item>
                <p>Paragraph 4</p>
                <marker>Modification 1</marker>
                <p>Paragraph 5</p>
                <marker>Base text</marker>
            </item>
            <item>
                <marker>Modification 1</marker>
                <p>Paragraph 6</p>
                <p>Paragraph 7</p>
                <marker>Base text</marker>
                <marker>Modification 2</marker>
                <p>Paragraph 8</p>
                <marker>Base text</marker>
            </item>
        </list>
        <p>Paragraph 9</p>
    </div>
</root>

我遇到的问题是,当它们之间没有文本时,相同修改代码的关闭和打开标记应该被隐藏。它们可能紧接着彼此(这很简单),但它们之间也有一些非文本元素边界。我试图创建一个累加器来跟踪自上次修改标记以来的所有文本,但这会导致对同一累加器的嵌套调用,从而产生运行时错误。我正在寻找的是一种不断向累加器添加文本并在找到修改 PI 时将其重置为空字符串的方法。这是我的试用累加器,导致嵌套调用过多:

<xsl:accumulator name="text" initial-value="''">
    <xsl:accumulator-rule match="node()">
        <xsl:choose>
            <xsl:when test="self::processing-instruction('MyPI')"/>
            <xsl:when test="self::text()">
                <xsl:value-of select="concat(accumulator-after('text'),.)"/>
            </xsl:when>
        </xsl:choose>
    </xsl:accumulator-rule>
</xsl:accumulator>

我想我还不明白累加器是如何工作的,所以很难得到我想要的结果。

上述简单 XML 所需的输出:

<?xml version="1.0" encoding="UTF-8"?>
<root>
    <div>
        <marker>Base text</marker>
        <p>Paragraph 1</p>
        <marker>Modification 1</marker>
        <p>Paragraph 2</p>
        <p>Paragraph 3</p>
        <marker>Base text</marker>
    </div>
    <div>
        <list>
            <item>
                <p>Paragraph 4</p>
                <marker>Modification 1</marker>
                <p>Paragraph 5</p>
            </item>
            <item>
                <p>Paragraph 6</p>
                <p>Paragraph 7</p>
                <marker>Mpdification 2</marker>
                <p>Paragraph 8</p>
            </item>
        </list>
        <marker>Base text</marker>
        <p>Paragraph 9</p>
    </div>
</root>

希望有人能指出我正确的方向。我猜从 XML 处理中的特定节点开始累积文本节点将是更多人需要解决的问题。在我目前的情况下,我不需要实际的文本内容,我只需要知道自上次 PI 以来是否有任何可见文本(即我需要删除或忽略此检查中的任何空格)。

如果有其他不涉及累加器的方法,那也可以。

在此先感谢您的帮助

也许

<xsl:accumulator name="text" initial-value="()" as="xs:string?">
    <xsl:accumulator-rule match="processing-instruction('MyPI')" select="''"/>
    <xsl:accumulator-rule match="text()[normalize-space()]" select="$value || ."/>
</xsl:accumulator>

给你一个关于如何设置一个累加器来收集文本节点值的例子,我不确定我是否理解将累加器重置为空字符串的条件,所以这基本上是你的样本中的匹配项,只是转录在(希望)可编译的 XSLT 3 中,如果有更多与开始或结束处理指令对或名称相关的条件,您可以适应。

关于解释 $value 变量的规范,请参阅 https://www.w3.org/TR/xslt-30/#accumulator-declaration:

The select attribute and the contained sequence constructor of the xsl:accumulator-rule element are mutually exclusive: if the select attribute is present then the sequence constructor must be empty. The expression in the select attribute of xsl:accumulator-rule or the contained sequence constructor is evaluated with a static context that follows the normal rules for expressions in stylesheets, except that:

An additional variable is present in the context. The name of this variable is value (in no namespace), and its type is the type that appears in the as attribute of the xsl:accumulator declaration.

The context item for evaluation of the expression or sequence constructor will always be a node that matches the pattern in the match attribute.

https://www.w3.org/TR/xslt-30/#accumulator-examples 中的两个示例也使用了 $value