Вложенные предикаты XPath / XSLT: как получить контекст внешнего предиката? - PullRequest
12 голосов
/ 06 июля 2011

Похоже, что этот вопрос ранее не обсуждался для стекового потока, за исключением Работа с вложенными предикатами XPath ... Уточнено , где было предложено решение, не включающее вложенные предикаты.

Итак, я попытался написать упрощенный образец того, что я хотел бы получить:

Введите:

<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>

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

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

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

Ответы [ 4 ]

10 голосов
/ 06 июля 2011

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().

4 голосов
/ 06 июля 2011

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

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

0 голосов
/ 06 июля 2011

Уведомление Оператор точки в 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>
0 голосов
/ 06 июля 2011

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

...