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

问题描述

我正在学习 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

select 属性和包含的序列构造函数 xsl:accumulator-rule 元素是互斥的:如果选择 属性存在则序列构造函数必须为空。这 xsl:accumulator-rule 的 select 属性中的表达式或 包含的序列构造函数使用静态上下文进行评估,该上下文 遵循样式表中表达式的正常规则,除了:

上下文中存在一个附加变量。这个名字 变量是 value(在没有命名空间中),它的类型是 出现在 xsl:accumulator 声明的 as 属性中。

用于评估表达式或序列的上下文项 构造函数将始终是与模式匹配的节点 匹配属性。

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