#xml #xslt #xpath
#xml #xslt #xpath
Вопрос:
Учитывая XML-документ, подобный этому:
<r>
<a/><b/><c/>
<d>
<d1/>
<d2>
<d2a/>
<d2b/>
<d2c/>
</d2>
</d>
<e/>
</r>
И, учитывая критерии «Начать с b, остановиться на d2b», существует ли выражение XPath, которое может выбрать либо:
В идеале:
<c/><d><d1/><d2><d2a/></d2></d>
Разумно:
<c/>
Я знаю, что с критериями «начинающимися с ‘a’ и заканчивающимися на ‘e'» я могу использовать выражение //*[preceding-sibling::a][following-sibling::e]
; Мне интересно, есть ли способ выполнить какое-то нечетное пересечение осей предков и предыдущих братьев и сестер, чтобы найти общего предка, когда начальный и конечный элементы не гарантированно разделяютсяодин и тот же родительский элемент.
Комментарии:
1. вопрос не требует изменения структуры, не так ли? это просто требует возврата несколько странного подмножества. (Я не знаю, возможно ли это)
2. @SteveBennett: На самом деле, вопрос требует изменения структуры, OP хочет новую структуру, в которой
d2
у dosn’t естьd2b
d2c
дочерние элементы и — поэтому эти элементы каким-то образом должны быть удалены — и XPath не может удалять элементы!
Ответ №1:
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>
Explanation:
-
The identity rule copies every matched node «as-is».
-
There is a single overriding template matching any element.
-
Внутри этого шаблона выполняются два теста: принадлежит ли текущий узел к набору всех элементов, «следующих за началом», и принадлежит ли текущий узел к набору всех элементов, «предшествующих концу». Если это так, текущий узел передается в шаблон идентификации (копируется), в противном случае он игнорируется (удаляется).
Решение 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/>
Объяснение: Здесь я использую формулу Kayessian (by @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/>
UPDATE: Here is a XPath 1.0 solution for the «reasonably» question. Again it is expressed as XSLT stylesheet module, in which, for better readability, subexpressions are defined as separate variables:
<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
.
Комментарии:
1. Отличный ответ для первого «идеального» случая. Я подозревал, что только выбор не может привести к тому, что я описывал. Приятно, что вы включили решение XPath для тех, кому оно может понадобиться. У вас есть ответ на «Разумный» случай? (Найдите узел (ы) между предками каждой конечной точки, которые имеют общий родительский элемент.)
2. @Phrogz: Я думал, что решение XSLT — это то, что вам нужно, поэтому я не рассмотрел менее сложное пожелание. Сделаю это, но после возвращения домой с работы (куда я только что прибыл).
3. @Phrogz: Если я понимаю вашу «разумную» задачу, выбранными узлами должны быть:
c
иd
— не толькоc
. Если мое понимание неверно, пожалуйста, объясните.4. Выбор между
<b>
и<e>
даст<c>
и<d>
; выбор между<b>
и<d>
даст только<c>
. Для обеспечения согласованности я предлагаю, чтобы<d2b>
это рассматривалось как<d>
, и, следовательно<c>
, было выбрано только.5. @Phrogz: В обновлении в конце моего ответа я предоставляю решение (чистый XPath) для вашего «разумного» требования.
Ответ №2:
Для вашей «разумной» цели: учитывая критерии «Начать с b, остановиться на d2b», вы можете использовать следующий XPath:
//b/following-sibling::*[following::d2b]
Поскольку following::
ось исключает потомков, будут выбраны только следующие братья и сестры b
вплоть до того, который является предком (или самим собой) d2b.
(Я предполагаю, что в документе есть только один <b>
элемент, как вы, похоже, предполагали.)
Комментарии:
1. @_LarsH: К сожалению, ваше простое выражение XPath выбирает только один элемент с XML-документом в моем обновлении, но необходимо выбрать три элемента.
2. @Dimitre: Я интерпретировал спецификацию по-разному, особенно. «Выбор между
<b>
и<e>
даст<c>
и<d>
; выбор между<b>
и<d>
даст только<c>
. Для согласованности я предлагаю, чтобы<d2b>
это рассматривалось как<d>
, и, таким образом, будет выбран только <c> . » Я обращусь к @OP, чтобы оценить, правильно ли я понял, что он хотел. Если нет, я буду рад это узнать.3. Да, в этой части вопрос не задан.
4. @LarsH Вы интерпретировали мои комментарии так, как я их предполагал. Я все еще не понимаю, как это может работать, но я вижу, что это так. Мне нужно будет прочитать об
following
оси, потому что, похоже, она ведет себя противоположно тому, как я бы подозревал. Спасибо!5. Хорошо, теперь я понял. Использование
foo[following::bar]
означает «Совпадениеfoo
, если за нимbar
следует» , а не «Совпадениеfoo
, если оно следуетbar
» . Это мощная ось, которую я раньше не видел. Очень приятно, это пригодится. Спасибо!