Q: XSL - Using For-Each, Choose functions

Jammerules
Jammerules used Ask the Experts™
on
Experts,

Noob question - I am trying to do a simple exercise with For-Each and Choose functions of XSL. I want to select a particular value based on a condition. Here are my XSL and XML codes:
XSL:
<xsl:stylesheet
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    version="1.0"> 
  <xsl:output method="html"/>
    
  <xsl:template match="/">
      <html>
          <table>              
              <tr>
                  <td bgcolor="lightgray"> Republican President </td>
                  <td>
                      <xsl:for-each select="Profiles/Profile"> 
                          <xsl:choose>
                              <xsl:when test="./Party='GOP'">
                                 <xsl:value-of select="./FirstName"/>
                                 <xsl:text> - (2001-2009) </xsl:text>
                              </xsl:when> 
                              <xsl:otherwise>
                                  <xsl:value-of select="./FirstName"/>
                                  <xsl:text> DEM </xsl:text>
                              </xsl:otherwise>
                          </xsl:choose>   
                      </xsl:for-each>
                  </td>
              </tr>
              <!--
              <tr>
                  <td> Democratic President </td>
                  <td bgcolor="gray">
                      <xsl:for-each select="Profiles/Profile">
                          <xsl:if test="./Party='DEM'">
                              <xsl:value-of select="./FirstName"/>
                          </xsl:if>
                      </xsl:for-each>
                  </td>
              </tr>  
              -->                     
          </table>
      </html>
  </xsl:template> 

</xsl:stylesheet>
 

Open in new window

XML:
<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="XSLTFile_ex1.xslt"?>
<Profiles>
      <Profile>
          <Salutation>Mr. </Salutation>  
          <FirstName>Barack</FirstName>
          <LastName>Obama
              <Suffixes>
                  <Suffix></Suffix> 
              </Suffixes>
          </LastName>
          <Party>DEM</Party>

          <Salutation>Mr. </Salutation>
          <FirstName>George</FirstName>
          <LastName>
              Bush
              <Suffixes>
                  <Suffix>Sr</Suffix>
              </Suffixes>
          </LastName>
          <Party>GOP</Party>

      </Profile>
</Profiles>

Open in new window


I am unable to get the required result. Where am I going wrong?
Comment
Watch Question

Do more with

Expert Office
EXPERT OFFICE® is a registered trademark of EXPERTS EXCHANGE®
I think there are two things.

Based on the XML you have provided... it looks to me that you should have a "<Profile>" tag surrounding each president Profile.

It doesn't really make sense to loop through the xml the way you have specified.  When the XSL is executed, and you have selected a node.... you cannot go back and reprocess them.

Here is updated XSL and XML that I think will give the expected result.

XML File:

<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="XSLTFile_ex1.xslt"?>
<Profiles>
      <Profile>
          <Salutation>Mr. </Salutation>  
          <FirstName>Barack</FirstName>
          <LastName>Obama
              <Suffixes>
                  <Suffix></Suffix> 
              </Suffixes>
          </LastName>
          <Party>DEM</Party>
      </Profile>
      <Profile>
          <Salutation>Mr. </Salutation>
          <FirstName>George</FirstName>
          <LastName>
              Bush
              <Suffixes>
                  <Suffix>Sr</Suffix>
              </Suffixes>
          </LastName>
          <Party>GOP</Party>

      </Profile>
</Profiles>

Open in new window


XSL File:

<xsl:stylesheet
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    version="1.0"> 
  <xsl:output method="html"/>
 
  <xsl:template match = "/">
    <html>
      <table>
        <tr>
          <td bgcolor="lightgray"> Republican President </td>
          <xsl:apply-templates select="//Profile[.//Party='GOP']"/> 
        </tr>
        <tr>
          <td bgcolor="gray"> Democratic President </td>
          <xsl:apply-templates select="//Profile[.//Party='DEM']"/> 
        </tr>
      </table>
    </html>
  </xsl:template>

  <xsl:template match="//Profile[.//Party='GOP']">
    <td>
      <xsl:value-of select="./FirstName"/>
      <xsl:text> - (2001-2009) </xsl:text>
    </td>
  </xsl:template> 
  
  
  <xsl:template match="//Profile[.//Party='DEM']">
    <td>
      <xsl:value-of select="./FirstName"/>
    </td>
  </xsl:template> 

</xsl:stylesheet>

Open in new window



Resulting HTML File:
<html>
   <table>
      <tr>
         <td bgcolor="lightgray"> Republican President </td>
         <td>George - (2001-2009) </td>
      </tr>
      <tr>
         <td bgcolor="gray"> Democratic President </td>
         <td>Barack</td>
      </tr>
   </table>
</html>

Open in new window

Note:

<xsl:apply-templates select="//Profile[.//Party='GOP']"/>

The apply templates path above is saying.... go find the routine that matches ="//Profile[.//Party='GOP']"/>

Further that XPath means... for each Profile node in the entire document, that contains a child node of Party, and the value of the Party is "GOP".

Hope that helps.
Gertone (Geert Bormans)Information Architect
Top Expert 2006

Commented:
My two cts.

The XML you have as the source could well be processed using a much more complex XSLT, but I think you should follow buttersk advice to wrap each president in its own profile

buttersk however gives the wrong input on what this XPath: Profile[.//Party='DEM']" means
the .//Party in the predicate is referring to all descendants from the current context,
this includes the children, but also deeper descendants. On some non optimised processords this will break performance. If you want children you need a single "/" instead of a double "//"
Profile[./Party='GOP']"
or the shorter form
Profile[Party='GOP']"

since Profiles is you root, put it in the starting template
<xsl:template match = "Profiles">
instead of
<xsl:template match = "/">
This has the advantage that the processor does not need to dig down the entire tree for a Profile element
<xsl:apply-templates select="Profile[Party='GOP']"/>
instead of
<xsl:apply-templates select="//Profile[.//Party='GOP']"/>

You should NEVER have a "//" starting the match statement of a template
<xsl:template match="//Profile[.//Party='DEM']">
should be
<xsl:template match="Profile[Party='DEM']">

All these changes will serve your performance well
Success in ‘20 With a Profitable Pricing Strategy

Do you wonder if your IT business is truly profitable or if you should raise your prices? Learn how to calculate your overhead burden using our free interactive tool and use it to determine the right price for your IT services. Start calculating Now!

Information Architect
Top Expert 2006
Commented:
Your table would get awkward if you would have a different number of Democratic presidents versus the number of rep presidents

Hence this change

<xsl:stylesheet
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    version="1.0"> 
    <xsl:output method="html"/>
    
    <xsl:template match = "Profiles">
        <html>
            <table>
                <tr>
                    <td bgcolor="lightgray"> Republican President </td>
                </tr>
                <xsl:apply-templates select="Profile[Party='GOP']"/> 
                <tr>
                    <td bgcolor="gray"> Democratic President </td>
                </tr>
                <xsl:apply-templates select="Profile[Party='DEM']"/> 
            </table>
        </html>
    </xsl:template>
    
    <xsl:template match="Profile[Party='GOP']">
        <tr>
            <td>
                <xsl:value-of select="FirstName"/>
                <xsl:text> - (2001-2009) </xsl:text>
            </td>
        </tr>
    </xsl:template> 
    
    
    <xsl:template match="Profile[Party='DEM']">
        <tr>
            <td>
                <xsl:value-of select="FirstName"/>
            </td>
        </tr>
    </xsl:template> 
    
</xsl:stylesheet>

Open in new window


You can play with this of course

Note that I changed "./FirstName" in to "FirstName"
which is exactly the same but more readable
(common practice among XSLT developers)

Maybe a bit of explanation on the apply-templates stuff versus the for-each
buttersk switched from for-each to apply-templates
basically they do teh same thing: process a series of selected nodes
the apply templates approach does it in an indirect fashion.
Each node selected in an apply-templates will be picked up by the best matching template
It is worthwhile to learn that technique. Once you grasp it, it will give you better code

Author

Commented:
Buttersk and Gertone,

Thanks so much for your answers and explanations. So far, in my learning process, I only used <xsl :template> for applying styles for a specific element. I did not know that you could also use a condition with it - e.g., "Profile[Party='DEM']". Now, I know :).

At the same time, since I started the exercise with <xsl:choose> and <xsl:when>, can one of you show me the same exercise with them, please? Thanks!

Author

Commented:
Belay that! :)

I have got it working. Output kinda looks silly, but I'm learning. Meh!

For those interested:

XSL:
<xsl:stylesheet
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    version="1.0"> 
  <xsl:output method="html"/>
    
  <xsl:template match="Profiles/Profile">
      <html>
          <table>              
              <tr>
                  <td bgcolor="lightred" > Republican President </td>
                  <td>
                      <xsl:choose>
                          <xsl:when test="./Party='GOP'">
                              <xsl:value-of select="./FirstName"/>
                              <xsl:text> - GOP </xsl:text>
                          </xsl:when> 
                      </xsl:choose>
                  </td>
              </tr> 
              <tr>
                  <td  bgcolor="lightgreen"> Democratic President </td>
                  <td>
                      <xsl:choose>
                          <xsl:when test="./Party='DEM'">
                              <xsl:value-of select="./FirstName"/>
                              <xsl:text> - DEMS </xsl:text>
                          </xsl:when> 
                      </xsl:choose>
                  </td>
              </tr>                    
          </table>
      </html>
  </xsl:template> 
</xsl:stylesheet>

Open in new window

XML:
<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="XSLTFile_ex1.xslt"?>
<Profiles>
      <Profile>
          <Salutation>Mr. </Salutation>  
          <FirstName>Barack</FirstName>
          <LastName>Obama
              <Suffixes>
                  <Suffix></Suffix> 
              </Suffixes>
          </LastName>
          <Party>DEM</Party>
      </Profile>
      <Profile>
          <Salutation>Mr. </Salutation>
          <FirstName>Richard</FirstName>
          <LastName>
              Nixon
              <Suffixes>
                  <Suffix></Suffix>
              </Suffixes>
          </LastName>
          <Party>GOP</Party>
      </Profile>
      <Profile>
          <Salutation>Mr. </Salutation>
          <FirstName>Bill</FirstName>
          <LastName>
              Clinton
              <Suffixes>
                 <Suffix></Suffix>
              </Suffixes>
          </LastName>
          <Party>GOP</Party>
      </Profile>
     <Profile>
         <Salutation>Mr. </Salutation>
         <FirstName>George</FirstName>
         <LastName>
             Bush
             <Suffixes>
                 <Suffix>Jr</Suffix>
             </Suffixes>
         </LastName>
         <Party>GOP</Party>
     </Profile>
</Profiles>

Open in new window

Gertone (Geert Bormans)Information Architect
Top Expert 2006

Commented:
Have a look at this one
(I see that you introduced the xsl:text correctly :-)

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    version="1.0">
    <xsl:stylesheet
        xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        version="1.0"> 
        <xsl:output method="html"/>
        
        <xsl:template match="Profiles">
            <html>
                <table>              
                    <tr>
                        <td bgcolor="lightred" > Republican President </td>
                    </tr>
                    <xsl:apply-templates select="Profile[Party='GOP']"/>
                    <tr>
                        <td  bgcolor="lightgreen"> Democratic President </td>
                    </tr>                    
                    <xsl:apply-templates select="Profile[not(Party='GOP')]"/>
                </table>
            </html>  
        </xsl:template>
        
        <xsl:template match="Profile">
            <tr>
                <td>
                    <xsl:value-of select="FirstName"/>
                    <xsl:choose>
                        <xsl:when test="Party='GOP'">
                            <xsl:text> - GOP </xsl:text>
                        </xsl:when> 
                        <xsl:otherwise>
                            <xsl:text> - DEMS </xsl:text>
                        </xsl:otherwise>
                    </xsl:choose>
                </td>
            </tr>
        </xsl:template> 
        
    </xsl:stylesheet>
</xsl:stylesheet>

Open in new window


Note that I removed some paths to shorter form, please learn to do so
And no need to have
"Profiles/Profile" in a match attribute of a template if the only context for Profile is Profiles,
no extra value, just slow down

Author

Commented:
...if the only context for Profile is Profiles...

Understood. Thanks again for all that you guys do on here. I can't imagine the difficulty for starters like me without forums like this!

Do more with

Expert Office
Submit tech questions to Ask the Experts™ at any time to receive solutions, advice, and new ideas from leading industry professionals.

Start 7-Day Free Trial