3

Suppose I have the following XML:

<data>
  <foos>
    <foo id="1" checked="yes" />
    <foo id="2" />
    <foo id="3" />
    <foo id="4" checked="yes" />
  </foos>
  <bars>
    <bar for="1" name="blub" />
    <bar for="2" name="bla" />
    <bar for="3" name="baz" />
    <bar for="4" name="plim" />
  </bars>
</data>

Now I want to print all the name attributes of those element bar which point to an element foo that has the attribute checked. So for the example above, my xslt would output blub and plim.

Here is what I have tried so far is to just check whether I can print the id attribute of the foo element that each bar belongs to:

<xsl:template match="/">
  <xsl:for-each select="//bars/bar">
    <xsl:value-of select="../../foos/foo[@id=./@for]/@id" />
  </xsl:for-each>
</xsl:template>

but to no avail. I think the problem is, that the check foo[@id=./@for] will select both @id and @for from the foo element. So how can I say that I want the @for attribute from my current element in the for loop but the @id from the other current element?

josch
  • 6,716
  • 3
  • 41
  • 49
  • If you're working with HTML then say so - this example isn't XML because XML doesn't allow an attribute name like `checked` without a value. I assume that in the real data `foo` is an `input type="checkbox"` and `bar` is `label`? – Ian Roberts Oct 19 '14 at 12:14
  • @IanRoberts thanks, I didn't know and added the attribute value – josch Oct 19 '14 at 12:20
  • possible duplicate of [Current node vs. Context node in XSLT/XPath?](http://stackoverflow.com/questions/1022345/current-node-vs-context-node-in-xslt-xpath) –  Oct 19 '14 at 12:45

3 Answers3

6

how can I say that I want the @for attribute from my current element in the for loop but the @id from the other current element?

Use the current() function:

<xsl:value-of select="../../foos/foo[@id=current()/@for]/@id" />

Inside the square brackets, . is the node that the predicate is testing, whereas current() is the node that is the current target of the for-each.

Ian Roberts
  • 120,891
  • 16
  • 170
  • 183
1

As an alternative that should also improve performance you can define a key

<xsl:key name="foo-by-id" match="foos/foo" use="@id"/>

and then replace

  <xsl:for-each select="//bars/bar">
    <xsl:value-of select="../../foos/foo[@id=./@for]/@id" />
  </xsl:for-each>

with

  <xsl:for-each select="//bars/bar">
    <xsl:value-of select="key('foo-by-id', @for)/@id" />
  </xsl:for-each>
Martin Honnen
  • 160,499
  • 6
  • 90
  • 110
0

To build on the xsl:key idea from @martin-honnen, make a key for the checked items:

<xsl:key name="checked" match="foos/foo[@checked = 'yes']" use="@id" />

Then your XSLT can become:

<xsl:template match="/">
  <xsl:apply-templates select="/data/bars/bar[key('checked', @for)]" />
</xsl:template>

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

Only the foo with checked="yes" are included in the checked key (though if you are working from HTML, it's more likely that it's checked="checked"). key('checked', @for) for an element without a corresponding checked="yes" will return an empty node-set/empty sequence (depending on your XSLT version), so for those elements, the predicate will evaluate to false, so the xsl:apply-templates selects only the elements that you are interested in, so the template for bar becomes very simple.

Tony Graham
  • 7,306
  • 13
  • 20