XSLT: сопоставить первый элемент-потомок

#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:: , не разрешены, но // есть.