Wrox Programmer Forums

Need to download code?

View our list of code downloads.

Go Back   Wrox Programmer Forums > XML > XSLT
Password Reminder
Register
| FAQ | Members List | Calendar | Search | Today's Posts | Mark Forums Read
XSLT General questions and answers about XSLT. For issues strictly specific to the book XSLT 1.1 Programmers Reference, please post to that forum instead.
Welcome to the p2p.wrox.com Forums.

You are currently viewing the XSLT section of the Wrox Programmer to Programmer discussions. This is a community of tens of thousands of software programmers and website developers including Wrox book authors and readers. As a guest, you can read any forum posting. By joining today you can post your own programming questions, respond to other developers’ questions, and eliminate the ads that are displayed to guests. Registration is fast, simple and absolutely free .
DRM-free e-books 300x50
Reply
 
Thread Tools Search this Thread Display Modes
  #1 (permalink)  
Old February 3rd, 2011, 04:46 PM
Friend of Wrox
Points: 2,570, Level: 21
Points: 2,570, Level: 21 Points: 2,570, Level: 21 Points: 2,570, Level: 21
Activity: 0%
Activity: 0% Activity: 0% Activity: 0%
 
Join Date: Jun 2003
Location: San Diego, CA, USA
Posts: 836
Thanks: 0
Thanked 0 Times in 0 Posts
Default Select nodes whose attribute matches another node's attribute?

Hi all,

I'm stumped. I've read through tutorials, examples, forums, and I can't find an answer to my problem. I'm trying to figure out how select a subset of nodes to transform by limiting the selection by attribute value, where the attribute value is not hard-coded -- but is actually determined by another element's attribute value in the same file. (Think foreign keys in relational databases...)

For example, think of a school containing a roster of students, and identifying which students belong in which classrooms. Some students may belong to multiple classrooms.
Code:
<school>
  <classrooms>
    <classroom id="1"><student id="1"><student id="42"> ... </classroom>
    <classroom id="2"><student id="4"><student id="42"> ... </classroom>
  </classrooms>

  <students>
    <student id="1"><name>John Doe</name></student>
    <student id="2"><name>Jane Smith</name></student>
         ...
    <student id="42"><name>Joe Somebody</name></student>
  </students>
</school>
Now, I want to print out all the students, but organized by classroom. I was thinking I'd need to iterate over each classroom, and then print out each student who's ID matched the student listed as being in that room:
Code:
<xsl:template match="/">
   <xsl:for-each select="classrooms/classroom">
     Classroom <xsl:value-of select="@id" />
     <xsl:for-each select="student"><!-- classroom/student, that is -->
           <xsl:apply-templates match="/school/students/student[@id=@id]" /><!-- student info from the roster -->
      </xsl:for-each><!-- student in the classroom -->
   </xsl:for-each><!-- classroom in the school -->
<xsl:template>
In the bold apply-templates statement, I'm trying to say "For each student in the classroom, print the information from the roster for that student." In other words, the first @id is "the ID of the student in the classroom" and the second @id is "the ID of the student in the school roster".

Does this make sense? Is this even possible?

Thanks so much in advance for all your help.
Nik
__________________
Take care,

Nik
Reply With Quote
  #2 (permalink)  
Old February 3rd, 2011, 06:15 PM
samjudson's Avatar
Friend of Wrox
Points: 8,687, Level: 40
Points: 8,687, Level: 40 Points: 8,687, Level: 40 Points: 8,687, Level: 40
Activity: 0%
Activity: 0% Activity: 0% Activity: 0%
 
Join Date: Aug 2007
Location: Newcastle, , United Kingdom.
Posts: 2,128
Thanks: 1
Thanked 189 Times in 188 Posts
Default

The thing you are missing is the current() function. Inside the predicate (the square brackets) the context becomes the element you are in, so [@id=@id] will always equal true, as the id attribute of every students/student element will always match itself.

current() gives you the context node outside of the current XPath, i.e. the student element inside the classroom. Try [@id=current()/@id] instead.
__________________
/- Sam Judson : Wrox Technical Editor -/

Think before you post: What have you tried?
Reply With Quote
  #3 (permalink)  
Old February 4th, 2011, 06:14 AM
mhkay's Avatar
Wrox Author
Points: 18,487, Level: 59
Points: 18,487, Level: 59 Points: 18,487, Level: 59 Points: 18,487, Level: 59
Activity: 0%
Activity: 0% Activity: 0% Activity: 0%
 
Join Date: Apr 2004
Location: Reading, Berks, United Kingdom.
Posts: 4,962
Thanks: 0
Thanked 292 Times in 287 Posts
Default

Also, when you're doing a join, think of xsl:key. Use xsl:key to define a primary key, and use the key() function to locate its corresponding element using the foreign key as input.

Code:
<xsl:key name="student-key" match="student" use="@id"/>
...
<xsl:for-each select="classroom/student">
   <xsl:apply-templates select="key('student-key', @id)"/>
...
__________________
Michael Kay
http://www.saxonica.com/
Author, XSLT 2.0 and XPath 2.0 Programmer\'s Reference
Reply With Quote
  #4 (permalink)  
Old February 4th, 2011, 06:18 AM
samjudson's Avatar
Friend of Wrox
Points: 8,687, Level: 40
Points: 8,687, Level: 40 Points: 8,687, Level: 40 Points: 8,687, Level: 40
Activity: 0%
Activity: 0% Activity: 0% Activity: 0%
 
Join Date: Aug 2007
Location: Newcastle, , United Kingdom.
Posts: 2,128
Thanks: 1
Thanked 189 Times in 188 Posts
Default

Should that not be <xsl:key name="student-key" match="students/student" use="@id"/> - as you don't want to include the classroom/student elements in the key.
__________________
/- Sam Judson : Wrox Technical Editor -/

Think before you post: What have you tried?
Reply With Quote
  #5 (permalink)  
Old February 4th, 2011, 06:21 AM
mhkay's Avatar
Wrox Author
Points: 18,487, Level: 59
Points: 18,487, Level: 59 Points: 18,487, Level: 59 Points: 18,487, Level: 59
Activity: 0%
Activity: 0% Activity: 0% Activity: 0%
 
Join Date: Apr 2004
Location: Reading, Berks, United Kingdom.
Posts: 4,962
Thanks: 0
Thanked 292 Times in 287 Posts
Default

Yes, I missed that one. Mind you, I don't think it's a wonderful piece of XML design to use the same element name for two different purposes depending on context.
__________________
Michael Kay
http://www.saxonica.com/
Author, XSLT 2.0 and XPath 2.0 Programmer\'s Reference
Reply With Quote
  #6 (permalink)  
Old April 18th, 2018, 11:30 AM
Registered User
Points: 3, Level: 1
Points: 3, Level: 1 Points: 3, Level: 1 Points: 3, Level: 1
Activity: 0%
Activity: 0% Activity: 0% Activity: 0%
 
Join Date: Apr 2018
Posts: 1
Thanks: 0
Thanked 0 Times in 0 Posts
Cool Alternative Attribute mismatch template

I wrote this template which return a variable that tests whether any node attributes or attributes values mismatch. It is a sort of alternative approach to the problem.

Template:
Code:
  <xsl:template name="attribute-value-mismatch">
    <xsl:param name="attributes1" />
    <xsl:param name="attributes2" />
    <attribute-match>
      <xsl:if test="(count($attributes1) != count($attributes2))">
        <attribute />
      </xsl:if>
      <xsl:if test="(count($attributes1) = count($attributes2))">
        <xsl:for-each select="$attributes1">
          <xsl:variable name="attribute1" select="."/>
          <xsl:variable name="result">
            <attroot>
              <xsl:for-each select="$attributes2">
                <xsl:if test="name(.) = name($attribute1/.)">
                  <xsl:if test="not(. = $attribute1/.)">
                    <not-matched-name/>
                  </xsl:if>
                </xsl:if>
                <xsl:if test="name(.) != name($attribute1/.)">
                  <not-matched-name/>
                </xsl:if>
              </xsl:for-each>
            </attroot>
          </xsl:variable>
          <xsl:if test="count(msxsl:node-set($result)//not-matched-name) = count(msxsl:node-set($attributes2))">
            <attribute />
          </xsl:if>
        </xsl:for-each>
      </xsl:if>
    </attribute-match>
  </xsl:template>
Usage:
Code:
<xsl:variable name="attribute-mismatch">
          <xsl:call-template name="attribute-value-mismatch">
            <xsl:with-param name="attributes1" select="$node1/@*"></xsl:with-param>
            <xsl:with-param name="attributes2" select="$node2/@*"></xsl:with-param>
          </xsl:call-template>
        </xsl:variable>
        <xsl:if test="count(msxsl:node-set($attribute-mismatch)//attribute) = 0">...
I have tested it thoroughly and it works without any hard coded attribute names.
Reply With Quote
Reply


Thread Tools Search this Thread
Search this Thread:

Advanced Search
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is Off
HTML code is Off
Trackbacks are Off
Pingbacks are On
Refbacks are Off


Similar Threads
Thread Thread Starter Forum Replies Last Post
Sorting some nodes by attribute value acw274 XSLT 8 July 2nd, 2008 01:22 AM
How can I go through all child nodes & attribute.. vishnu108mishra C# 1 November 13th, 2007 05:00 AM
Comparing nodes attribute(Michael Kay help meeee) sumapathy XSLT 4 August 25th, 2007 03:21 AM
XPath - Selecting nodes based on attribute values billy_bob_the_3rd XML 4 December 1st, 2004 06:12 PM
passing attribute info while traversing nodes Ashley XML 1 June 20th, 2003 07:42 AM



All times are GMT -4. The time now is 07:53 AM.


Powered by vBulletin®
Copyright ©2000 - 2020, Jelsoft Enterprises Ltd.
© 2013 John Wiley & Sons, Inc.