Как объяснил ОП, он хочет:
все следующие родственные элементы до, но не включая
первый элемент, который не является элементом p, ul, ol или blockquote
I. Решение XPath 1.0:
Требуемые узлы являются пересечением двух наборов узлов :
Все элементы, которые следуют за братьями и сестрами p
с id
со значением 'first'
.
Все элементы, которые предшествуют братьям и сестрам hr
.
Чтобы найти это в XPath 1.0, мы используем формулу Кейсиана для пересечения узлов :
$ns1[count(.|$ns2) = count($ns2)]
Вышеупомянутое выражение XPath выбирает все узлы, которые принадлежат как к набору узлов $ns1
, так и к набору узлов $ns2
.
Пусть $vP1
определяется как /*/p[@id='first']
.
Пусть $vFirstNotInRange
это:
$vP1/following-sibling::*
[not(self::p or self::ul
or self::ol or self::blockquote)
] [1]
Это выбирает первый нежелательный узел (в данном случае hr
) или, точнее: первый элемент, который является следующим родным братом $vP1
, и это не p
, ul
, a ol
или blockquote
.
Тогда два набора узлов, которые мы хотим пересечь, это все следующие братья и сестры $vP1
и все предшествующие братья и сестры $vFirstNotInRange
:
Обозначим $vFollowingP1
первый набор узлов - это:
$vP1/following-sibling::*
И давайте обозначим $vPreceedingNotInRange
второй набор узлов - это:
$vFirstNotInRange/preceding-sibling::*
Наконец, мы подставляем в формулу Кейсины $ns1
с $vPreceedingNotInRange
и $ns2
с $vFollowingP1
. В результате этих подстановок выбираются именно нужные узлы:
$vPreceedingNotInRange
[count(.|$vFollowingP1)
=
count($vFollowingP1)
]
Если мы подставим все переменные, пока не получим выражение, которое не содержит никаких переменных, мы получим:
/*/p[@id='first']/following-sibling::*
[not(self::p or self::ul
or self::ol or self::blockquote
)
] [1]
/preceding-sibling::*
[count(.| /*/p[@id='first']/following-sibling::*)
=
count(/*/p[@id='first']/following-sibling::*)
]
Это выражение выбирает именно нужные узлы.
Вот проверка на основе XSLT :
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:variable name="vP1" select="/*/p[@id='first']"/>
<xsl:variable name="vFirstNotInRange" select=
"$vP1/following-sibling::*
[not(self::p or self::ul
or self::ol or self::blockquote)
] [1]"/>
<xsl:variable name="vFollowingP1"
select="$vP1/following-sibling::*"/>
<xsl:variable name="vPreceedingNotInRange"
select="$vFirstNotInRange/preceding-sibling::*"/>
<xsl:template match="/">
<xsl:copy-of select=
"$vPreceedingNotInRange
[count(.|$vFollowingP1)
=
count($vFollowingP1)
]"/>
================
<xsl:copy-of select=
"/*/p[@id='first']/following-sibling::*
[not(self::p or self::ul
or self::ol or self::blockquote
)
] [1]
/preceding-sibling::*
[count(.| /*/p[@id='first']/following-sibling::*)
=
count(/*/p[@id='first']/following-sibling::*)
]
"/>
</xsl:template>
</xsl:stylesheet>
Когда это преобразование применяется к следующему XML-документу (предоставленный не правильно сформированный фрагмент XML - исправлен и обернут, чтобы быть правильно сформированным):
<html>
<h1>Header</h1>
<p>Paragraph 1</p>
<p id="first">Paragraph 2</p>
<p>Paragraph 3</p>
<ul>
<li>Item 1</li>
<li>Item 2</li>
</ul>
<p>Paragraph 4</p>
<hr/>
<p>Paragraph 5</p>
<blockquote>Blockquote 1</blockquote>
</html>
два выражения XPath (одно с переменными и одно со всеми замещенными переменными) вычисляются и выводятся нужные, правильные выбранные узлы :
<p>Paragraph 3</p>
<ul>
<li>Item 1</li>
<li>Item 2</li>
</ul>
<p>Paragraph 4</p>
================
<p>Paragraph 3</p>
<ul>
<li>Item 1</li>
<li>Item 2</li>
</ul>
<p>Paragraph 4</p>
II. Решение XPath 2.0 :
$vFirstNotInRange/preceding-sibling::*
[. >> $vP1]
Это выбирает любой предшествующий брат $vFirstNotInRange
, который также следует за $vP1
, и выбирает те же самые требуемые узлы:
<p>Paragraph 3</p>
<ul>
<li>Item 1</li>
<li>Item 2</li>
</ul>
<p>Paragraph 4</p>
Объяснение : здесь мы используем XPath 2.0, оператор «следует» >>
.