Подтвердить что ты не робот

XPath/XSLT вложенные предикаты: как получить контекст внешнего предиката?

Кажется, что этот вопрос ранее не обсуждался в stackoverflow, сохранялся Работа с вложенными XPath Predicates... Refined, где предлагалось решение, не связанное с вложенными предикатами.

Итак, я попытался написать упрощенную модель того, что я хотел бы получить:

Input:

<root>
    <shortOfSupply>
        <food animal="doggie"/>
        <food animal="horse"/>
    </shortOfSupply>
    <animalsDictionary>
        <cage name="A" animal="kittie"/>
        <cage name="B" animal="dog"/>
        <cage name="C" animal="cow"/>
        <cage name="D" animal="zebra"/>
    </animals>
</root>

Вывод:

<root>
    <hungryAnimals>
        <cage name="B"/>
        <cage name="D"/>
    </hungryAnimals>
</root>

или, альтернативно, если нет пересечений,

<root>
    <everythingIsFine/>
</root>

И я хочу получить его с помощью вложенных предикатов:

<xsl:template match="cage">
    <cage>
        <xsl:attribute name="name">
            <xsl:value-of select="@name"/>
        </xsl:attribute>
    </cage>
</xsl:template>

<xsl:template match="/root/animalsDictionary">
    <xsl:choose>
        <!--                                                             in <food>     in <cage>       -->
        <xsl:when test="cage[/root/shortOfSupply/food[ext:isEqualAnimals(./@animal, ?????/@animal)]]">
            <hungryAnimals>
                <xsl:apply-templates select="cage[/root/shortOfSupply/food[ext:isEqualAnimals(@animal, ?????/@animal)]]"/>
            </hungryAnimals>
        </xsl:when>
        <xsl:otherwise>
            <everythingIsFine/>
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>

Итак, что я должен писать вместо этого ??????

Я знаю, что могу переписать всю таблицу стилей, используя еще один шаблон и широкое использование переменных /params, но делает эту таблицу стилей значительно более сложной, не говоря уже о реальной таблице стилей, которая у меня есть для реальной проблемы.

В документе XPath указано, что знак точки . означает текущий контекст node, но он не указывает, есть ли какая-либо возможность получить node контекста до этого; и я просто не могу поверить, что XPath не хватает этой очевидной функции.

4b9b3361

Ответ 1

One-Liner XPath 2.0:

for $a in /*/animalsDictionary/cage
      return
        if(/*/shortOfSupply/*[my:isA($a/@animal, @animal)])
          then $a
          else ()

При применении к предоставленному XML-документу выбирается:

   <cage name="B"/>
   <cage name="D"/>

Нельзя использовать одно выражение XPath 1.0, чтобы найти, что данная клетка содержит голодного животного.

Вот решение XSLT. XSLT 2.0 используется только для того, чтобы избежать использования функции расширения для сравнения. В решении XSLT 1.0 для сравнения используется функция расширения, а xxx:node-set() для проверки, если RTF, созданный приложением шаблонов в теле переменной, содержит любой дочерний элемент):

<xsl:stylesheet version="2.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:xs="http://www.w3.org/2001/XMLSchema"
 xmlns:my="my:my" exclude-result-prefixes="xs my">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>

 <my:Dict>
  <a genName="doggie">
    <name>dog</name>
    <name>bulldog</name>
    <name>puppy</name>
  </a>
  <a genName="horse">
    <name>horse</name>
    <name>zebra</name>
    <name>pony</name>
  </a>
  <a genName="cat">
    <name>kittie</name>
    <name>kitten</name>
  </a>
 </my:Dict>

 <xsl:variable name="vDict" select=
  "document('')/*/my:Dict/a"/>

 <xsl:template match="/">
  <root>
   <xsl:variable name="vhungryCages">
    <xsl:apply-templates select=
    "/*/animalsDictionary/cage"/>
   </xsl:variable>

   <xsl:choose>
    <xsl:when test="$vhungryCages/*">
     <hungryAnimals>
       <xsl:copy-of select="$vhungryCages"/>
     </hungryAnimals>
    </xsl:when>
    <xsl:otherwise>
     <everythingIsFine/>
    </xsl:otherwise>
   </xsl:choose>
  </root>
 </xsl:template>

 <xsl:template match="cage">
  <xsl:if test="
  /*/shortOfSupply/*[my:isA(current()/@animal,@animal)]">

  <cage name="{@name}"/>
  </xsl:if>
 </xsl:template>

 <xsl:function name="my:isA" as="xs:boolean">
  <xsl:param name="pSpecName" as="xs:string"/>
  <xsl:param name="pGenName" as="xs:string"/>

  <xsl:sequence select=
   "$pSpecName = $vDict[@genName = $pGenName]/name"/>
 </xsl:function>
</xsl:stylesheet>

Когда это преобразование применяется к предоставленному XML-документу (исправлено, чтобы быть хорошо сформированным):

<root>
    <shortOfSupply>
        <food animal="doggie"/>
        <food animal="horse"/>
    </shortOfSupply>
    <animalsDictionary>
        <cage name="A" animal="kittie"/>
        <cage name="B" animal="dogs"/>
        <cage name="C" animal="cow"/>
        <cage name="D" animal="zebras"/>
    </animalsDictionary>
</root>

требуется, правильный результат получается:

<root>
   <hungryAnimals>
      <cage name="B"/>
      <cage name="D"/>
   </hungryAnimals>
</root>

Объяснение. Обратите внимание на использование функции XSLT current().

Ответ 2

XPath 1.0 не является "реляционно полным" - он не может выполнять произвольные объединения. Если вы находитесь в XSLT, вы всегда можете обойти ограничения, связывая переменные с промежуточными узлами узлов или (иногда) с помощью функции current().

XPath 2.0 вводит переменные диапазона, что делает его реляционно полным, поэтому это ограничение ушло.

Ответ 3

Вам не хватает <xsl:when test="cage[@animal = /root/shortOfSupply/food/@animal]">, чтобы выразить свое тестовое условие?

Ответ 4

Примечание Оператор точки в XPath связан с текущим контекстом. В XSLT текущий контекст шаблона_ задается функцией current(), которая большую часть времени (не всегда) совпадает с ..


Вы можете выполнить тест (и применять шаблоны также), используя аббревиатуру родительской оси (../):

 cage[@animal=../../shortOfSupply/food/@animal]

Кроме того, шаблон соответствия в первом шаблоне неверен, он должен относиться к корню:

 /root/animalsDictionary

@Предложение Мартина также очевидно правильно.

Ваш последний шаблон слегка изменен:

<xsl:stylesheet 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    version="1.0">

    <xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
    <xsl:strip-space elements="*"/>

    <xsl:template match="root/animalsDictionary">
        <xsl:choose>
            <xsl:when test="cage[@animal=../../shortOfSupply/food/@animal]">
                <hungryAnimals>
                    <xsl:apply-templates select="cage[@animal
                            =../../shortOfSupply/food/@animal]"/>
                </hungryAnimals>
            </xsl:when>
            <xsl:otherwise>
                <everythingIsFine/>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>

    <xsl:template match="cage">
        <cage name="{@name}"/>
    </xsl:template> 

</xsl:stylesheet>