josch
josch

Reputation: 7174

How to find element that matches xslt attribute value in for-each loop

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?

Upvotes: 3

Views: 5856

Answers (3)

Tony Graham
Tony Graham

Reputation: 8068

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.

Upvotes: 0

Martin Honnen
Martin Honnen

Reputation: 167781

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>

Upvotes: 1

Ian Roberts
Ian Roberts

Reputation: 122424

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.

Upvotes: 6

Related Questions