Это сложно, но выполнимо (долго читать вперед, извините за это).
Ключом к "последовательности" в терминах осей XPath (которые по определению не являются последовательными) является проверка того, является ли ближайший узел в противоположном направлении , который "первым выполняет условие", также что «запустило» серию под рукой:
a
b <- first node to fulfill the condition, starts series 1
b <- series 1
b <- series 1
a
b <- first node to fulfill the condition, starts series 2
b <- series 2
b <- series 2
a
В вашем случае серия состоит из <span>
узлов, которые имеют строку x
в своих @class
:
span[contains(concat(' ', @class, ' '),' x ')]
Обратите внимание, что я объединяю пробелы, чтобы избежать ложных срабатываний.
A <span>
, который начинает серию (т. Е. Тот, который «сначала выполняет условие»), может быть определен как тот, который имеет x
в своем классе, и ему не предшествует непосредственно <span>
, который также имеет x
not(preceding-sibling::span[1][contains(concat(' ', @class, ' '),' x ')])
Мы должны проверить это условие в <xsl:if>
, чтобы шаблон не генерировал выходные данные для узлов в серии (т. Е. Шаблон будет выполнять фактическую работу только для «начальных узлов»).
Теперь к хитрой части.
Из каждого из этих «начальных узлов» мы должны выбрать все following-sibling::span
узлы, которые имеют x
в своем классе. Также включите текущий span
для учета серий, которые имеют только один элемент. Ладно, достаточно просто:
. | following-sibling::span[contains(concat(' ', @class, ' '),' x ')]
Для каждого из них мы теперь выясняем, совпадает ли их ближайший «начальный узел» с тем, над которым работает шаблон (то есть, который начал их серию). Это значит:
они должны быть частью серии (то есть они должны следовать span
с x
)
preceding-sibling::span[1][contains(concat(' ', @class, ' '),' x ')]
теперь удалите все span
, чей узел стартера не идентичен стартеру серии . Это означает, что мы проверяем любого предшествующего брата span
(который имеет x
), которому непосредственно не предшествует span
с x
:
preceding-sibling::span[contains(concat(' ', @class, ' '),' x ')][
not(preceding-sibling::span[1][contains(concat(' ', @class, ' '),' x ')])
][1]
Затем мы используем generate-id()
для проверки идентичности узла. Если найденный узел идентичен $starter
, то текущим диапазоном является тот, который принадлежит последовательному ряду.
Собираем все вместе:
<xsl:template match="span[contains(concat(' ', @class, ' '),' x ')]">
<xsl:if test="not(preceding-sibling::span[1][contains(concat(' ', @class, ' '),' x ')])">
<xsl:variable name="starter" select="." />
<x>
<xsl:for-each select="
. | following-sibling::span[contains(concat(' ', @class, ' '),' x ')][
preceding-sibling::span[1][contains(concat(' ', @class, ' '),' x ')]
and
generate-id($starter)
=
generate-id(
preceding-sibling::span[contains(concat(' ', @class, ' '),' x ')][
not(preceding-sibling::span[1][contains(concat(' ', @class, ' '),' x ')])
][1]
)
]
">
<xsl:value-of select="text()" />
</xsl:for-each>
</x>
</xsl:if>
</xsl:template>
И да, я знаю, что это не красиво. В ответе Димитра есть решение на основе <xsl:key>
, которое более эффективно.
С вашим вводом сэмпла генерируется этот вывод:
1
<x>234</x>
5
<x>6</x>
7
<x>8</x>