#xslt #xpath
#xslt #xpath
Вопрос:
Я пытаюсь сопоставить первый bar
элемент, который встречается как потомок foo
элемента в шаблоне сопоставления xsl, и изо всех сил. Начальная попытка:
<xsl:template match="//foo//bar[1]">
...
</xsl:template>
сбой, потому что есть несколько bar
совпадающих элементов. Итак:
<xsl:template match="(//foo//bar)[1]">
...
</xsl:template>
но это не удается скомпилировать.
Ответ №1:
Сложно. Я не знаю, насколько это было бы эффективно или иным образом, но вы могли бы перевернуть шаблон с ног на голову и переместить логику в предикат (которому разрешено использовать оси, отличные от дочерних, атрибутов и //
):
<xsl:template match="foo//bar[not(preceding::bar/ancestor::foo)]">
(любой bar
внутри a foo
при условии, что перед ним нет другого bar-inside-a-foo). В качестве альтернативы вы можете попробовать ключевой трюк, аналогичный тому, как работает группировка Muenchian, что может быть более эффективным
<!-- trick key - all matching nodes will end up with the same key value - all
we care about is finding whether a particular node is the first such node
in the document or not. -->
<xsl:key name="fooBar" match="foo//bar" use="1" />
<xsl:template match="foo//bar[generate-id() = generate-id(key('fooBar', 1)[1])]">
Комментарии:
1. Я думаю, что это будет соответствовать только первому
<bar>
в файле, а не первому<bar>
за<foo>
.2. @Tomalak это то, что я принял за «первый элемент bar, который встречается как потомок foo», означает — единственный узел, который будет выбран XPath
(//foo//bar)[1]
.3. … в отличие от «первого элемента bar, который является потомком каждого foo». Нам придется подождать, пока OP подтвердит правильность интерпретации.
4. Я экстраполировал тот факт, что OP пытается написать шаблон, что не было бы строго необходимым, если ожидать однократного выполнения. Но вы правы, это неоднозначно.
5. Извините, в документе есть один foo — это проясняет?
Ответ №2:
Вы не можете сделать это с помощью выражений соответствия. На самом деле, вы можете сделать это с помощью выражений соответствия, просто не в каждом процессоре XSLT, как кажется. Смотрите Комментарии.
Я бы использовал <xsl:if>
.
<xsl:template match="foo//bar">
<xsl:if test="generate-id() = generate-id(ancestor::foo[1]//bar)">
<!-- ... -->
</xsl:if>
</xsl:template>
Это гарантирует, что в дальнейшем обрабатывается только первый потомок <bar>
на <foo>
(!).
ПРИМЕЧАНИЕ: при задании набора узлов generate-id()
возвращает идентификатор первого узла в наборе.
Комментарии:
1. На самом деле это можно было бы перенести в выражение сопоставления, поскольку
match="foo//bar[generate-id() = generate-id(ancestor::foo[1]//bar)]"
, поскольку у вас нет ограничений на то, какие оси вы можете использовать, находясь внутри предиката.2. @IanRoberts Я тоже так думал, но в моем тесте это не сработало. Не могли бы вы попробовать и подтвердить?
3. Работает для меня с использованием xsltproc (XSLT 1.0), сбой с ошибкой типа на Saxon 9 HE (XSLT 2.0), поскольку
generate-id
ожидается один узел, а не последовательность.4. Саксон будет рад, если я изменю выражение, чтобы обеспечить единый узел (
generate-id(ancestor::foo[1]/descendant::bar[1])
)5. @Ian Вы снова правы (я даже полагаю, что мы это уже обсуждали). Кстати, оба
msxsl.exe
(т.Е. MSXML2) и xmlplayground.com (PHP / libxslt) сопоставьте все узлы, когда предикат перемещается из<xsl:if>
выражения соответствия в выражение соответствия. Любопытно, мне было бы очень интересно узнать, правильно ли это поведение или нет в XSLT 1.0.
Ответ №3:
Альтернативное решение основано на «эмпирическом правиле»: используйте расширенный XPATH с «select», а не при «совпадении». Сопоставьте XML с шаблонами просто по имени, например foo , даже не //foo .
<?xml version="1.0" encoding="UTF-8"?>
<foo>
<bar>bar1<bar>bar2</bar></bar>
<bar>bar3</bar>
</foo>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="foo">
<xsl:apply-templates select="descendant::bar[1]"/>
</xsl:template>
<xsl:template match="bar">
<!--only the first bar was selected --><xsl:value-of select="text()"/>
</xsl:template>
</xsl:stylesheet>
Комментарии:
1. Это нормально, если вы хотите обработать только первый, но обычная точка сопоставления шаблонов заключается в том, что вы хотите обрабатывать все узлы в порядке документов, но обрабатывать первую строку в foo иначе, чем последующие.
2.И
//foo//bar[1]
допускается в выражении соответствия, суть вопроса в том, что оно соответствует слишком многим узлам (любой бар внутри foo, где этот бар является первым баром внутри его прямого родительского элемента, а не только первым потомком бара foo).3. Я полностью согласен с вами, но мое решение часто является всем, что нам нужно.
4. Не все выражения XPATH разрешены с XPATH, descendant::bar[1] нельзя использовать с match .
5.Точно. Такие оси, как
descendant::
, не разрешены, но//
есть.