16

It seems that this question was not discussed on stackoverflow before, save for Working With Nested XPath Predicates ... Refined where the solution not involving nested predicates was offered.

So I tried to write the oversimplified sample of what I'd like to get:

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>

Output:

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

or, alternatively, if there is no intersections,

<root>
    <everythingIsFine/>
</root>

And i want to get it using a nested predicates:

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

So what should i write in place of that ??????

I know i could rewrite the entire stylesheet using one more template and extensive usage of variables/params, but it makes even this stylesheet significantly more complex, let alone the real stylesheet i have for real problem.

It is written in XPath reference that the dot . sign means the current context node, but it doesn't tell whether there is any possibility to get the node of context before that; and i just can't believe XPath is missing this obvious feature.

Community
  • 1
  • 1
penartur
  • 9,792
  • 5
  • 39
  • 50
  • I think the "missing feature" you are speaking about is the XSLT function `current()`. However, in this situation you don't need that. See my answer. – Emiliano Poggi Jul 06 '11 at 11:38
  • "I know that i could replace (...) but it won't help in my actual problem, and i really need to use nested predicates", if this is true, change your example, because does not seem so. – Emiliano Poggi Jul 06 '11 at 11:47
  • AFAIK `current()` references to the scope of the entire template, while i need the scope of the outer predicate. That is, in the inner predicate `current()` will mean ``, `.` will mean ``, and i need to get `` in some way. – penartur Jul 06 '11 at 11:47
  • 1
    As I said, you don't need that (as demonstrated in the answers below) unless you have provided a misleading input sample. – Emiliano Poggi Jul 06 '11 at 11:49
  • OK, it seems that my over-simplified example is too over-simplified and, as such, pushes the minds in the wrong direction. Let's replace that simple and plain `./@animal=?????/@animal` with `externalFunctions:someReallyWeirdStringComparisonFunction(./@animal, ?????/animal)`. Voila, `cage[externalFunctions:someReallyWeirdStringComparisonFunction(/shortOfSupply/food/@animal, @animal)]` does not work as `cage[/shortOfSupply/food/@animal = @animal]` used to. – penartur Jul 06 '11 at 11:51
  • 1
    I'm sorry but I can't keep guessing. The quality and correcteness of the answers here are directly proportional to the quality of the quesion. – Emiliano Poggi Jul 06 '11 at 12:06
  • @empo, I'm trying to keep the examples relatively simple instead of dumping the real input data there. Again, imagine that instead of plain `=` operator we are using some string comparison function; then the method of using `=` on nodesets becomes inapplicable. The original question is simple: _how do i get the context of outer predicate from the inner predicate?_, not _how do i get rid of nested predicates?_ I'll try to work out the better example, but the question won't change. – penartur Jul 06 '11 at 12:26
  • @empo, i revised the original question, making the example more relevant and more difficult to solve without using a feature i'm looking for; check it out. – penartur Jul 06 '11 at 12:34
  • Good question, +1. See my answer for explanation (this cannot be achieved with a single XPath 1.0 expression) and two solutions -- an XSLT 1.0 solution and an XPath 2.0 one-liner solution. – Dimitre Novatchev Jul 06 '11 at 16:05

4 Answers4

11

XPath 2.0 one-liner:

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

When applied on the provided XML document selects:

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

One cannot use a single XPath 1.0 expression to find that a given cage contains a hungry animal.

Here is an XSLT solution (XSLT 2.0 is used only to avoid using an extension function for the comparison -- in an XSLT 1.0 solution one will use an extension function for the comparison and the xxx:node-set() extension to test if the RTF produced by applying templates in the body of the variable contains any child element):

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

When this transformation is applied on the provided XML document (corrected to be well-formed):

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

the wanted, correct result is produced:

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

Explanation: Do note the use of the XSLT current() function.

Dimitre Novatchev
  • 240,661
  • 26
  • 293
  • 431
  • That's a good answer, but a bit late one :) However, your code mainly consists of unnecessary `isA` implementation which is originally external in my example (and without that implementation there would not be requirement for XSLT 2.0); also, the very same idea of avoiding nested predicates was already mentioned in my last comment on **empo** answer :) – penartur Jul 06 '11 at 16:05
  • @penatur: I haven't read any other answer or your comments on them. And I explicitly point out in my answer that XSLT 2.0 isn't necessary -- the same solution can be used almost exactly in XSLT 1.0. The implementation of `my:isA()` function is necessary if one wants to show actually running code without writing any extensions in a non-xslt language -- this is the main reason I had to use XSLT 2.0. In the rest of the code I have been careful not to use any other XSLT 2.0 feature, so that the solution can be re-written in XSLT 1.0. Why do you think my answer is "a bit late"? – Dimitre Novatchev Jul 06 '11 at 16:08
  • When i wrote my comment there wasn't that one-liner in your answer :) i guess it works in 2.0, but i have to stick with 1.0 (.NET). BTW, don't you think that such 2.0 syntax is a bit ugly compared to clear 1.0? Does 2.0 still not support referencing an arbitrary-level predicate? EDIT: Also there wasn't your comment; you're a bit late because Michael Kay already gave a correct answer a couple of minutes earlier. – penartur Jul 06 '11 at 16:09
  • @penatur: Can you suggest implementing range variables in a way that you consider "not ugly"? – Dimitre Novatchev Jul 06 '11 at 16:12
  • Well, how about e.g. ``? Not that i carefully thought of whether it would be easy to add to language. – penartur Jul 06 '11 at 16:30
  • @penatur: Yes, it is possible to use e.g. special functions like `outer1()`, `outer2()`, ..., etc. however they require counting of scopes and can very easily result in un-understandable, messy expressions. The ability to define inline variables makes XPath 2.0 as powerful as a programming language. This trend is emphasized even more in XPath 3.0 where one can define inline (anonymous) "function-items" and pass them around as parameters and results. For examples see my blog: http://dnovatchev.wordpress.com/ – Dimitre Novatchev Jul 06 '11 at 16:46
  • My example does not require special functions with that name and counting of scopes; it uses named scopes, so i can't see how it is un-understandable. It just adds a bit more completeness to a system. And i'm trying to avoid variables (even immutable ones) in XSLT as long as possible; maybe i didn't grok XSLT, but i just feel that variables and foreaches do not belong there (especially considering that XSLT transforms are called from an imperative C#). Thank you for the link, i'll check it :) – penartur Jul 06 '11 at 17:06
  • @penatur: Your proposed syntax cannot be directly implemented in XPath, because the `[]` operator is already reserved for predicates and overloading it to express scope results in ambiguity. As for avoiding variables and `xsl:for-each`, there is nothing imperative in these constructs and they are quite valuable. Without variables XSLT would be crippled. – Dimitre Novatchev Jul 06 '11 at 17:15
  • This is ambiguity only when parser works without knowing a context (but how then it is able to process in-line strings?). Otherwise: `[]` means the predicate when used after node-set description; and the scope name definition when used after `:` in the beginning of predicate content. The context could be determined just by looking at the last significant token before the `[`: if it is `:`, then it is certainly not a node-set description, so `[` has a second meaning; otherwise, `[` has a first meaning. But then again, i didn't carefully thought of it. – penartur Jul 06 '11 at 17:31
  • @penatur: The best person to tell us why the W3C chose this road is @Michael Kay himself. For me, the proposed scope notation is quite limited and doesn't aid readability. – Dimitre Novatchev Jul 06 '11 at 17:39
  • I don't pretend that this is a best notation possible; it is just what crossed my mind in a minute of thinking on possible notations :) – penartur Jul 06 '11 at 17:47
  • By the way, it would also be useful in [this question](http://stackoverflow.com/questions/6595034/xpath-xslt-nested-predicates-how-to-get-the-context-of-outer-predicate): `/parentNode/*[:[outer] generate-id(.)!=generate-id(/parentNode/*[name(.)=name(~outer/.)][1])]/name()` returns all duplicate tag names – penartur Jul 07 '11 at 07:17
4

XPath 1.0 is not "relationally complete" - it can't do arbitrary joins. If you're in XSLT, you can always get round the limitations by binding variables to intermediate nodesets, or (sometimes) by using the current() function.

XPath 2.0 introduces range variables, which makes it relationally complete, so this limitation has gone.

Michael Kay
  • 156,231
  • 11
  • 92
  • 164
  • 1
    That's a shame. I wonder whether there was some technological limitation beyond it or they just forgot about nested joins when developing that `.` vs. `current()` feature... – penartur Jul 06 '11 at 15:54
  • 2
    XSLT and XPath were developed by people whose background was in document processing, not databases. Things like joins weren't high on the agenda. This all happened before XML started to be widely used for data as well as documents. – Michael Kay Jul 07 '11 at 12:06
  • I didn't realise it is a join until Dimitre pointed me at this. Still it seems quite logically to be able to reference arbitrary-level context when there is nested contexts feature... – penartur Jul 07 '11 at 13:12
0

Doesn't <xsl:when test="cage[@animal = /root/shortOfSupply/food/@animal]"> suffice to express your test condition?

Martin Honnen
  • 160,499
  • 6
  • 90
  • 110
0

Notice The dot operator in XPath is related to the current context. In XSLT the current template context_ is given by the function current(), which most of the time (not always) coincides with the ..


You can perform the test (and the apply templates as well), using the parent axis abbreviation (../):

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

Moreover the match pattern in the the first template is wrong, it should be relative to the root:

 /root/animalsDictionary

@Martin suggestion is also obviously correct.

Your final template slightly modified:

<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>
Emiliano Poggi
  • 24,390
  • 8
  • 55
  • 67
  • Your test suggestion was originally described in the penultimate paragraph of the question. As for your point about the match pattern, that was just a mistype :) – penartur Jul 06 '11 at 11:37
  • No, it is still there: I know that i could replace the test with cage[/shortOfSupply/food/@animal = @animal] and rewrite output of "hungry" cages list using ; but it won't help in my actual problem, and i really need to use nested predicates there (or to write really much of really unneeded and obscure code). – penartur Jul 06 '11 at 11:45
  • Why not `cage[ext:isEqualAnimals(/root/shortOfSupply/food/@animal, @animal)]`? – Emiliano Poggi Jul 06 '11 at 12:50
  • Because `/root/shortOfSupply/food/@animal` returns nodeset, and `isEqualAnimals` compares strings. I now see the drawback of my example; it seems that it is unclear that `shortOfSupply` may contain several child nodes. I'll fix it now (EDIT: fixed an example in question) – penartur Jul 06 '11 at 12:58
  • Why not using a template matching a cage, then? – Emiliano Poggi Jul 06 '11 at 13:03
  • I'm afraid you need to _redesign_ your stylesheet or your function. – Emiliano Poggi Jul 06 '11 at 13:12
  • I invented that `hungryAnimals`/`everythingIsFine` thing to make such solution more complicated. So, IIUC, you're suggesting me to implement ``, that will do ``, and then applying that template to all ``s, storing the output in some `$hungryOutput` variable, and then checking whether `$hungryOutput` is empty to choose between `` and ``? That's the solution i'm using now, but it is too long and complicated. – penartur Jul 06 '11 at 13:13