XPath (как 1.0, так и 2.0) - это язык запросов для документов XML. Как таковой он не может изменять узлы и структуру любого XML-документа .
Требуемый результат можно получить с помощью преобразования XSLT (I. XSLT 1.0, используемый ниже):
<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:param name="pStart" select="/*/b"/>
<xsl:param name="pEnd" select="/*/d/d2/d2b"/>
<xsl:variable name="vFollowingStart" select=
"$pStart/following::* | $pStart/descendant::*"/>
<xsl:variable name="vPrecedingEnd" select=
"$pEnd/preceding::* | $pEnd/ancestor::*"/>
<xsl:template match="node()|@*" name="identity">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*">
<xsl:choose>
<xsl:when test=
"count(.|$vFollowingStart) = count($vFollowingStart)
and
count(.|$vPrecedingEnd) = count($vPrecedingEnd)
">
<xsl:call-template name="identity"/>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
когда это преобразование применяется к предоставленному документу XML :
<r>
<a/><b/><c/>
<d>
<d1/>
<d2>
<d2a/>
<d2b/>
<d2c/>
</d2>
</d>
<e/>
</r>
желаемый, правильный результат, получается :
<c/>
<d>
<d1/>
<d2>
<d2a/>
</d2>
</d>
Объяснение
Правило идентификации копирует каждый соответствующий узел "как есть".
Существует один переопределяющий шаблон, соответствующий любому элементу.
Внутри этого шаблона выполняются два теста: принадлежит ли текущий узел к набору всех элементов «после начала», и , принадлежит ли текущий узел к набору всех элементов, предшествующих конец". Если это так, текущий узел передается в шаблон идентификации (копируется), в противном случае он игнорируется (удаляется).
II. Решение XSLT 2.0
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:param name="pStart" select="/*/b"/>
<xsl:param name="pEnd" select="/*/d/d2/d2b"/>
<xsl:variable name="vFollowingStart" select=
"$pStart/following::* | $pStart/descendant::*"/>
<xsl:variable name="vPrecedingEnd" select=
"$pEnd/preceding::* | $pEnd/ancestor::*"/>
<xsl:variable name="vWanted" select=
"$vFollowingStart intersect $vPrecedingEnd"/>
<xsl:template match="node()|@*" name="identity">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*[not(. intersect $vWanted)]">
<xsl:apply-templates/>
</xsl:template>
</xsl:stylesheet>
Когда это преобразование применяется к указанному выше XML-файлу, снова получается тот же правильный результат.
Объяснение : Использование оператора XPath 2.0 intersect
.
III. Решение XPath 1.0, выбирающее только узлы без изменения документа:
Для удобства чтения я предоставляю XSLT-преобразование, которое выводит результат выбора нужных узлов. С этой же целью подвыражения определяются как переменные:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:param name="pStart" select="/*/b"/>
<xsl:param name="pEnd" select="/*/d/d2/d2b"/>
<xsl:template match="node()|@*">
<xsl:variable name="vFollowingStart" select=
"$pStart/following::* | $pStart/descendant::*"/>
<xsl:variable name="vPrecedingEnd" select=
"$pEnd/preceding::* | $pEnd/ancestor::*"/>
<xsl:copy-of select=
"$vFollowingStart
[count(.|$vPrecedingEnd)
=
count($vPrecedingEnd)
]
"/>
</xsl:template>
</xsl:stylesheet>
Когда это преобразование применяется к предоставленному XML-документу (см. Выше), требуемые выбранные узлы выводятся :
<c/>
<d>
<d1/>
<d2>
<d2a/>
<d2b/>
<d2c/>
</d2>
</d>
<d1/>
<d2>
<d2a/>
<d2b/>
<d2c/>
</d2>
<d2a/>
Объяснение : Здесь я использую формулу Кейса (от @Michael Kay) для пересечения двух наборов узлов $ns1
и $ns2
:
$ns1[count(.|$ns2) = count($ns2)]
IV. Наконец, решение Xpath 2.0 (соответствующее решению XPath 1.0):
Я снова использую преобразование XSLT (2.0) для копирования результатов в вывод:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:param name="pStart" select="/*/b"/>
<xsl:param name="pEnd" select="/*/d/d2/d2b"/>
<xsl:template match="node()|@*">
<xsl:variable name="vFollowingStart" select=
"$pStart/following::* | $pStart/descendant::*"/>
<xsl:variable name="vPrecedingEnd" select=
"$pEnd/preceding::* | $pEnd/ancestor::*"/>
<xsl:sequence select=
"$vFollowingStart intersect $vPrecedingEnd"/>
</xsl:template>
</xsl:stylesheet>
Получаются те же результаты (в точности нужные узлы), что и в решении XPath 1.0 :
<c/>
<d>
<d1/>
<d2>
<d2a/>
<d2b/>
<d2c/>
</d2>
</d>
<d1/>
<d2>
<d2a/>
<d2b/>
<d2c/>
</d2>
<d2a/>
ОБНОВЛЕНИЕ : Вот решение XPath 1.0 для «разумного» вопроса. Опять же, это выражается в виде модуля таблицы стилей 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:param name="pStart" select="/*/*/b"/>
<xsl:param name="pEnd" select="/*/*/d/d2/d2b"/>
<xsl:variable name="vFollowingStart" select=
"$pStart/following::* | $pStart/descendant::*"/>
<xsl:variable name="vcommonAncestor" select=
"$pStart/ancestor::*
[count(.|$pEnd/ancestor::*)
=
count($pEnd/ancestor::*)
][1]
"/>
<xsl:variable name="vEndHighestAncestor" select=
"$vcommonAncestor/*
[count($pEnd | descendant::*)
=
count(descendant::*)
]"/>
<xsl:variable name="vPrecedingEnd" select=
"$vEndHighestAncestor/preceding::*
|
$vEndHighestAncestor/ancestor::*"/>
<xsl:template match="node()|@*" name="identity">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/">
<xsl:copy-of select=
"//*[count(.|$vFollowingStart) = count($vFollowingStart)
and
count(.|$vPrecedingEnd) = count($vPrecedingEnd)
]
"/>
</xsl:template>
</xsl:stylesheet>
Когда это преобразование применяется к следующему документу XML (аналогично предоставленному, но обернуто в еще один верхний элемент, и два дочерних элемента (g
и h
) добавляются к c
- чтобы было интереснее:
<t>
<r>
<a/><b/><c><g/><h/></c>
<d>
<d1/>
<d2>
<d2a/>
<d2b/>
<d2c/>
</d2>
</d>
<e/>
</r>
</t>
желаемый, правильный набор узлов выбран и скопирован на выход :
<c>
<g/>
<h/>
</c>
<g/>
<h/>
Объяснение : Это почти то же самое, что и раньше, но мы принимаем за $pEnd
его самого высокого предка - который является непосредственным потомком общего предка $pStart
и $pEnd
.