0

I'm trying to display a series of images each with its own caption using XSLT. I've coded the images and the captions by nesting <img> and then <figcaption> within but the resultant html does not display as intended (the captions are not lining up with corresponding images). Is there a way to nest <xsl: for-each> for the captions within the images? Here's the XSLT:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet 
    xmlns:xlink="http://www.w3.org/1999/xlink"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"    
    exclude-result-prefixes="xs"
    version="2.0">    
    <xsl:output method="html"/>      
    <xsl:template match="letter">       
        <html>
            <head>
                <style type="text/css">
                    #wrapper {min-height: 100%;}                    
                    #figcaption {
                    text-align: left;
                    }
                    #main {
                    padding-top: 15px;;
                    width: 1200px;
                    }                    
                </style>
            </head>
            <body>
                <div id="wrapper">                    
                    <div id="images">
                        <figure>                                
                            <xsl:if test="image">                                    
                                <xsl:for-each select="image/@xlink:href">                                        
                                    <img>                                            
                                        <xsl:attribute name="src">                                                
                                            <xsl:value-of select="."/>                                                
                                        </xsl:attribute>                                            
                                    </img>                                        
                                </xsl:for-each>                                    
                            </xsl:if>                                
                            <xsl:if test="image/@label">                                    
                                <xsl:for-each select="image/@label">                                        
                                    <figcaption><xsl:value-of select="."/></figcaption>                                        
                                </xsl:for-each>                                    
                            </xsl:if>                                
                        </figure>
                    </div> 
                </div>
            </body>
        </html>
    </xsl:template>   
</xsl:stylesheet>

Here's the corresponding XML:

<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="XSLT.xsl"?>
<letter xmlns:xlink="http://www.w3.org/1999/xlink">    
    <image label="page 1" xlink:href="http://tinyurl.com/nu7zmhc"/> 
    <image label="page 2" xlink:href="http://tinyurl.com/pysyztr"/> 
    <title>Letter from Shawn Schuyler</title>   
    <date>1963-06-30</date>     
    <language>English</language>    
    <creator>       
        <firstName>William</firstName>      
        <lastName>Schultz</lastName>
        <street>Unites States Disciplinary Barracks</street>            
        <city>Fort Leavenworth</city>           
        <state abbr="KS">Kansas</state>     
    </creator>      
</letter>

My desired output in html is basically this for each image:

<figure>
    <img src='image.jpg'/>
    <figcaption>Caption</figcaption>
</figure>    
Stype
  • 3
  • 3
  • Can you post your source XML and desired output? Also, as a general rule avoid `foreach` in XSL; use template matching; it'll be faster and cleaner. – JohnLBevan Jul 29 '15 at 00:36
  • Added XML and desired output. How would you make this work with template matching? – Stype Jul 29 '15 at 00:53

2 Answers2

1

Or simply:

<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xlink="http://www.w3.org/1999/xlink"
exclude-result-prefixes="xlink">

<xsl:template match="/letter">
    <html>
        <head>
            <style type="text/css">
            #wrapper {min-height: 100%;}
            #figcaption {
            text-align: left;
            }
            #main {
            padding-top: 15px;;
            width: 1200px;
            }
        </style>
        </head>
        <body>
            <div id="wrapper">
                <div id="images">
                    <xsl:for-each select="image">
                        <figure>
                            <img src='{@xlink:href}'/>
                            <figcaption>
                                <xsl:value-of select="@label"/>
                            </figcaption>
                        </figure>  
                    </xsl:for-each>
                </div>
            </div>
        </body>
    </html>
</xsl:template>

</xsl:stylesheet>

Note:

  1. There's nothing wrong with using xsl:for-each, especially in a simple case like this;

  2. There is something wrong with using xsl:element when you can use a literal result element. And while XSLT is naturally verbose, using the attribute value template can reduce the code (quite significanltly, as you can see in this case).

michael.hor257k
  • 113,275
  • 6
  • 33
  • 51
0

Try this:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
    xmlns:xlink="http://www.w3.org/1999/xlink"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    exclude-result-prefixes="xs"
    version="2.0">
  <xsl:output method="html" indent="yes" />
  <xsl:template match="letter">
    <html>
      <head>
        <style type="text/css">
          #wrapper {min-height: 100%;}
          #figcaption {
          text-align: left;
          }
          #main {
          padding-top: 15px;;
          width: 1200px;
          }
        </style>
      </head>
      <body>
        <div id="wrapper">
          <div id="images">
            <xsl:apply-templates select="./image"></xsl:apply-templates>
          </div>
        </div>
      </body>
    </html>
  </xsl:template>
  <xsl:template match="letter/image">
    <xsl:element name="figure">
      <xsl:element name="img">
        <xsl:attribute name="src">
          <xsl:value-of select="./@xlink:href"/>
        </xsl:attribute>
      </xsl:element>
      <xsl:apply-templates select="./@label"></xsl:apply-templates>
    </xsl:element>
  </xsl:template>
  <xsl:template match="letter/image/@label">
    <xsl:element name="figcaption">
      <xsl:value-of select="."/>
      </xsl:element>
    </xsl:template>
</xsl:stylesheet>

xsl:apply-templates says where anything matching the pattern specified in select should be put (with the dot showing the current element's context).

xsl:template is matched against the source document based on the path given in match. Any hits are processed in parallel, then later stitched together based on where the apply-templates elements indicate.

NB: depending on your XSLT engine having output="html" may have different effects on your img element. In HTML5 the img element is defined as not requiring a close tag (or being self-closing), so the engine won't close that tag. Arguments about whether that inconsistency is a good choice or not can be found throughout the net.

Ref: Are (non-void) self-closing tags valid in HTML5?

A good article on this alternate approach to for-each can be found here: http://gregbee.ch/blog/using-xsl-for-each-is-almost-always-wrong You'll find with XLST that once the concept of a template clicks your code will become way shorter are simpler to maintain.

Community
  • 1
  • 1
JohnLBevan
  • 22,735
  • 13
  • 96
  • 178
  • 1
    Wow! Worked perfectly. Thank you! – Stype Jul 29 '15 at 01:15
  • 1
    I'm just getting started with XSLT but this is already making a lot more sense than `for-each`. Articles are now bookmarked! Thanks again for your help and the extra resources. – Stype Jul 29 '15 at 01:31