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 не хватает этой очевидной функции.
Ответы
Ответ 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>