XPath/XSLT嵌套谓词:如何获取外部谓词的上下文



似乎这个问题之前没有在stackoverflow上讨论过,除了使用嵌套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竟然没有这个明显的特性。

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()函数的使用

XPath 1.0不是"相对完整的"——它不能进行任意连接。如果使用XSLT,总是可以通过将变量绑定到中间节点集,或者(有时)使用current()函数来绕过这些限制。

XPath 2.0引入了范围变量,这使得它相对完整,所以这个限制已经消除了。

<xsl:when test="cage[@animal = /root/shortOfSupply/food/@animal]">还不够表达你们的测试情况吗?

注意 XPath中的点运算符与当前上下文相关。在XSLT中,当前模板上下文_由函数current()给出,大多数时候(并不总是)与.一致。


您可以使用父轴缩写(../)执行测试(以及应用模板):

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

而且第一个模板中的匹配模式是错误的,它应该相对于根:

 /root/animalsDictionary

@Martin的建议显然也是正确的。

修改后的最终模板:

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

最新更新