Search code examples
xmlxsltaltova

How to loop through elements in an xml document using XSLT


I am using XSLT to loop through an XML document.
The root node of the document is called movies and it can contain one or more movie elements. However these movie elements contain single elements except for the actor element. How can you loop through to display more than one actor? I am already using <xsl:for-each> to loop throught each movie.
Here is the xslt:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >

<xsl:template match="/movies">

<html>
    <head>
    <link rel="stylesheet" href="stylecss.css" />
        <title>My Movies Collection</title>
    </head>
    <body>
        <!-- Use for each loop to extract all child nodes and their information-->

    <xsl:for-each select="/movies/movie">
    <!--Add title as well as year-->
        <h1><xsl:value-of select="title" />
        <xsl:text> ( </xsl:text>
        <xsl:value-of select="@year" />
        <xsl:text> ) </xsl:text>

        </h1>
        <p>
        <img>
            <xsl:attribute name="src">
            <xsl:value-of select="poster" />
            </xsl:attribute>
        </img>
        <span id="details">

        <xsl:text>Review: </xsl:text>
        <xsl:value-of select="@review" />
        <xsl:text>, Type: </xsl:text>
        <xsl:value-of select="@type" />
        <br></br>

        <xsl:text>Producer: </xsl:text>
        <xsl:value-of select="producer" /> 
        <br/>

        <xsl:text>Actor: </xsl:text>
        <xsl:value-of select="actor"/> <br/>

        <xsl:text>Director: </xsl:text>
        <xsl:value-of select="director" /><br/>
        <br/>


<i>         <xsl:text>Comments: </xsl:text>
        <xsl:value-of select="comments" /> </i>



        </span>
        </p>

    </xsl:for-each>

    </body>

</html>



</xsl:template>
</xsl:stylesheet>



The xml document itself:

<?xml version="1.0" encoding="UTF-8"?>

<?xml-stylesheet type="text/xsl" href="stylesheet.xslt" ?>
<movies>
  <movie type="comedy" rating="PG" review="4" year="1997">
  <title>How To Be A Player</title>
  <writer>Mark Brown</writer>
  <producer>Todd R. Baker</producer>
  <director>Russell Simmons</director>
  <actor>Bill Bellamy</actor>
  <actor>Mari Morrow</actor>
  <poster>img/htbap.jpeg</poster>
  <comments>Promotes polygamy</comments>  
  </movie>


  <movie type="comedy" rating="PG" review="5" year="2010">
  <title>Grown Ups</title>
  <writer>Adam Sandler</writer>
  <producer>Jack Giarraputo</producer>
  <director>Fred Wolf</director>
  <actor>Chris Rock</actor>
  <actor>Rob Schneider</actor>
  <poster>img/gu.jpeg</poster>
  <comments>Funny stuff</comments>  
  </movie>

 <movie type="drama" rating="PG" review="5" year="2010">
  <title>The Social Network</title>
  <writer>Dana Brunetti</writer>
  <producer>Scott Rudin</producer>
  <director>David Fincher</director>
  <actor>Jesse Eisenberg</actor>
  <actor>Justin Timberlake</actor>
  <poster>img/sn.jpeg</poster>
  <comments>So innovative</comments>  
  </movie>

  <movie type="comedy" rating="PG" review="5" year="2012">
        <title>Project X</title>
        <writer>Nima Nourizadeh</writer>
        <producer>Todd Phillips</producer>
        <director>Nima Nourizadeh</director>
        <actor>Thomas Mann</actor>
        <actor>Oliver Cooper</actor>
        <poster>img/px.jpeg</poster>
        <comments>Best party to ever</comments>
    </movie> 

</movies>

Solution

  • There is no reason why you can nest an xsl:for-each within another one. So you could replace <xsl:value-of select="actor"/>, which just outputs the text of the first actor, with this

     <xsl:for-each select="actor">
         <xsl:value-of select="." /><br />
     </xsl:for-each>
    

    Note that the xpath expression in the select is a relative expression; it is relative to the movie node you are currently positioned on, and so will select all actor elements that are child elements of it. (This also means your current <xsl:for-each select="/movies/movie"> can actually be just <xsl:for-each select="movie">)

    Having said that, in XSLT it is often better to make use of a template based solution, rather than using xsl:for-each as then you could potentially re-use templates, for example if you had multiple writers, or directors too.

    Try this XSLT

    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >
    <xsl:template match="/movies">
    <html>
        <head>
        <link rel="stylesheet" href="stylecss.css" />
            <title>My Movies Collection</title>
        </head>
        <body>
            <xsl:apply-templates select="movie" />
        </body>
    </html>
    </xsl:template>
    
    <xsl:template match="movie">
        <h1>
            <xsl:value-of select="title" />
            <xsl:text> ( </xsl:text><xsl:value-of select="@year" /><xsl:text> ) </xsl:text>
        </h1>
        <p>
            <img src="{poster}"/>
            <span id="details">
                <xsl:text>Review: </xsl:text>
                <xsl:value-of select="@review" />
                <xsl:text>, Type: </xsl:text>
                <xsl:value-of select="@type" />
                <br></br>
    
                <xsl:text>Producer: </xsl:text>
                <xsl:apply-templates select="producer" />
                <br/>
    
                <xsl:text>Actor: </xsl:text>
                <xsl:apply-templates select="actor" />
                <br/>
    
                <xsl:text>Director: </xsl:text>
                <xsl:apply-templates select="director" />
                <br/>
    
                <i>         
                    <xsl:text>Comments: </xsl:text>
                    <xsl:value-of select="comments" />
                </i>
            </span>
        </p>
    </xsl:template>
    
    <xsl:template match="movie/*">
        <xsl:value-of select="."/> <br/>
    </xsl:template>
    </xsl:stylesheet>
    

    Also note the use of Attribute Value Templates in creating the src of the img element

    <img src="{poster}"/>
    

    The curly braces indicate an expression to be evaluated, not output literally.