Это сложно, но выполнимо (долго читайте вперед, извините за это).
Ключом к «последовательности» с точки зрения осей 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 ')]
Обратите внимание, что я объединяю пробелы, чтобы избежать ложных срабатываний.
<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>
person
Tomalak
schedule
22.01.2012
<xsl:call-template name="text">
. - person Tomalak   schedule 22.01.2012text [bracketed text] text
, вtext <bracket>bracketed text</bracket> text
. - person ptomato   schedule 22.01.2012<span>1</span><span class="x y">2</span><span class="y">3</span>
? И1 <x><y>2</y></x><y>2</y>
, и1 <y><x>2</x>3</y>
хороши? - person Markus Jarderot   schedule 22.01.2012<xsl:template match="*" mode="text">
. Таким образом, вы можете выбросить<xsl:for-each>
и<xsl:call-template>
и напрямую использовать<xsl:apply-templates>
. Это должно сэкономить вам немного кода. - person Tomalak   schedule 22.01.2012y
не будет происходить безx
, поэтому я бы сказал, что первого варианта достаточно. - person ptomato   schedule 22.01.2012