Использование 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 (т.е. мне нужно удалить или игнорировать любые пробелы в этой проверке).

Если есть другой метод, не использующий аккумуляторы, это тоже хорошо.

Заранее благодарю за любую помощь


person 4everJang    schedule 14.04.2021    source источник
comment
Я так и не понял, ни из описания, ни из кода, какие ИП связаны, становятся маркерами, их нужно устранять. Откуда исходный <marker>Base text</marker>, где конечный <marker>Base text</marker> в нужном выводе?   -  person Martin Honnen    schedule 14.04.2021
comment
При вычислении нового аккумулятора используйте $value, чтобы получить текущее значение   -  person Martin Honnen    schedule 14.04.2021
comment
«Базовый текст» — это состояние по умолчанию, т. е. когда никаких изменений не предусмотрено. Модификации имеют стартовый ИП и близкий ИП.   -  person 4everJang    schedule 14.04.2021
comment
Ваш второй комментарий решил мою проблему: я нигде не нашел параметр $value, но это то, что мне нужно, чтобы все это работало. С помощью текстового аккумулятора я узнаю, были ли какие-либо текстовые узлы между последним закрывающим PI и текущим открывающим PI. Если видимого текста не было, а ИП относятся к одной модификации, я могу свернуть два диапазона в один. Спасибо за ваш быстрый и полезный ответ.   -  person 4everJang    schedule 14.04.2021
comment
Но у вас есть несколько <?MyPI Start Modification 1?>, т.е. с одинаковым номером. Это актуально? Или вы просто имеете дело с двумя братьями и сестрами, например. <?MyPI Start Modification n?> и <?MyPI End Modification n?>?   -  person Martin Honnen    schedule 14.04.2021
comment
Идентификаторы для PI не имеют значения, просто разные коды - модификация 1 и 2 выполняются в разные моменты времени. Фактический сложный файл содержит все виды дополнительной информации в PI, но они не имеют отношения к проблеме, которую мне нужно решить, поэтому я их пропустил.   -  person 4everJang    schedule 14.04.2021


Ответы (1)


Возможно

<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 являются взаимоисключающими: если присутствует атрибут select, то конструктор последовательности должен быть пустым. Выражение в атрибуте select правила xsl:accumulator-rule или содержащегося в нем конструктора последовательности оценивается со статическим контекстом, который следует обычным правилам для выражений в таблицах стилей, за исключением того, что:

В контексте присутствует дополнительная переменная. Имя этой переменной — value (без пространства имен), а ее тип — это тип, указанный в атрибуте as объявления xsl:accumulator.

Элемент контекста для оценки выражения или конструктора последовательности всегда будет узлом, который соответствует шаблону в атрибуте соответствия.

и два примера в https://www.w3.org/TR/xslt-30/#accumulator-examples также используют $value.

person Martin Honnen    schedule 14.04.2021