3

I have a list containing urls for images that I would like to parse and display using XSL but I only want the first 3 images.

e.g.

<xsl:value-of select="@Icons" />

Returns:

['http://www.test.com/image1.jpg',
 'http://www.test.com/image2.jpg',
 'http://www.test.com/image3.jpg',
 'http://www.test.com/image4.jpg',
 'http://www.test.com/image5.jpg']

Final output should be:

<img src="http://www.test.com/image1.jpg" alt=""/>
<img src="http://www.test.com/image2.jpg" alt=""/>
<img src="http://www.test.com/image3.jpg" alt=""/>

I have part of the code to pick the first 3 items but I'm unsure how to parse the list and display the images in html.

<xsl:template name="recurse_till_ten">
    <xsl:param name="num">1</xsl:param>
    <xsl:if test="not($num = 3)">
        //Parasing list here
        <xsl:call-template name="recurse_till_ten">
            <xsl:with-param name="num">
                <xsl:value-of select="$num + 1">
            </xsl:with-param>
        </xsl:call-template>
    </xsl:if>
</xsl:template>
marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Rsquare
  • 81
  • 5

3 Answers3

1

XSLT 1.0 Solution

This XSLT 1.0 style-sheet...

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes" />
<xsl:strip-space elements="*" />

<xsl:template match="/">
 <xsl:variable name="strip-chars" select="&quot;[]''&quot;" /> 
 <xsl:for-each select="*">
   <xsl:copy>
     <xsl:call-template name="tokenize">
       <xsl:with-param name="content" select="concat(translate(@Icons,$strip-chars,''),',')" />
       <xsl:with-param name="count" select="3" />
     </xsl:call-template>
   </xsl:copy>
  </xsl:for-each>
</xsl:template>

<xsl:template name="tokenize">
  <xsl:param name="content" />
  <xsl:param name="count" />
  <xsl:if test="contains($content,',') and $count > 0">
    <img src="{substring-before($content,',')}" alt="" />
    <xsl:call-template name="tokenize">
      <xsl:with-param name="content" select="normalize-space(substring-after($content,','))" />
      <xsl:with-param name="count" select="$count - 1" />
    </xsl:call-template>
  </xsl:if>  
</xsl:template>

</xsl:stylesheet>

...when applied to this input document...

<t Icons="['http://www.test.com/image1.jpg',
 'http://www.test.com/image2.jpg',
 'http://www.test.com/image3.jpg',
 'http://www.test.com/image4.jpg',
 'http://www.test.com/image5.jpg']"/>

...will yield...

<t>
  <img src="http://www.test.com/image1.jpg" alt="" />
  <img src="http://www.test.com/image2.jpg" alt="" />
  <img src="http://www.test.com/image3.jpg" alt="" />
</t>

And when applied to this short input document...

<t Icons="['http://www.test.com/image1.jpg',
 'http://www.test.com/image2.jpg']"/>

...yields...

<t>
  <img src="http://www.test.com/image1.jpg" alt="" />
  <img src="http://www.test.com/image2.jpg" alt="" />
</t>

Note

Suresh's solution, as at time of edit, will not work for this second use case in which the count of images is 3 or less.


XSLT 2.0 Solution

The same outcome can be reached simpler, more efficiently and more extensibly with XSLT 2.0 with a style-sheet like this...

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes" />
<xsl:strip-space elements="*" />

<xsl:template match="/">
 <xsl:for-each select="*">
   <xsl:copy>
    <xsl:variable name="image-elements" as="element()*">
      <xsl:analyze-string select="@Icons" regex="'([^']*)'(,|\])" >
       <xsl:matching-substring>
         <img src="{regex-group(1)}" alt="" />
       </xsl:matching-substring>
      </xsl:analyze-string>
    </xsl:variable>
    <xsl:copy-of select="$image-elements[not(position() > 3)]" />
   </xsl:copy>
  </xsl:for-each>
</xsl:template>

</xsl:stylesheet>

XSLT 2.0 XPATH Solution

For interest, here is a variation for XSLT 2.0 using purely XPATH.

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes" />
<xsl:strip-space elements="*" />

<xsl:template match="/">
 <xsl:for-each select="*">
   <xsl:copy>
    <xsl:for-each select="(for $i in tokenize(@Icons,',') return
                           replace($i,'.*''([^'']*)''.*','$1'))
                            [not(position() > 3)]">
      <img src="{.}" alt="" />
    </xsl:for-each>
   </xsl:copy>
  </xsl:for-each>
</xsl:template>

</xsl:stylesheet>

Update and Comment on Dimitre's Solution

Congradulations to Dimitre for his more concise XSLT 2.0 Solution. He uses a slightly different input from mine that does not have the square brackets in it. Looking at Dimitre's solution, I can see a tweak to make it signficantly smaller still.

This XSLT 2.0 style-sheet, a tweak on Dimitre's solution, is the tightest of all solutions, and as a bonus works on both my form of input document, and his, to produce the same and correct result document.

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>

<xsl:template match="/">
  <xsl:for-each select="tokenize(/*/@Icons, '\[?\s*''\s*,?\]?')[.][position() le 3]">
   <img src="{.}" alt=""/>
  </xsl:for-each>
 </xsl:template>
</xsl:stylesheet>
Sean B. Durkin
  • 12,659
  • 1
  • 36
  • 65
1

A much shorter XSLT 2.0 solution:

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>

 <xsl:variable name="vDelims">\s*'\s*,?</xsl:variable>
 <xsl:variable name="vTokens" select="tokenize(/*/@Icons, $vDelims)[.]"/>

 <xsl:template match="/*">
  <xsl:for-each select="$vTokens[position() le 3]">
   <img src="{.}" alt=""/>
  </xsl:for-each>
 </xsl:template>
</xsl:stylesheet>

When this transformation is applied on the following XML document:

<t Icons=
 "'http://www.test.com/image1.jpg',
  'http://www.test.com/image2.jpg',
  'http://www.test.com/image3.jpg',
  'http://www.test.com/image4.jpg',
  'http://www.test.com/image5.jpg'"/>

the wanted, correct result is produced:

<img src="http://www.test.com/image1.jpg" alt=""/>
<img src="http://www.test.com/image2.jpg" alt=""/>
<img src="http://www.test.com/image3.jpg" alt=""/>
Dimitre Novatchev
  • 240,661
  • 26
  • 293
  • 431
  • I suspect that the OP's square brackets [] were meant to be taken literally, as if the Icon attribute was a JSON or Javascript style array. Very concise solution though! – Sean B. Durkin Sep 06 '12 at 06:37
  • @SeanB.Durkin, We need the OP to tell us -- this solution can be easily adjusted to accomodate for the sqare brackets. As for making this solution "significantly smaller", I used intentionally variables to make the solution more readable and understandable. I would even intersperse the RegEx with comments that make it more understandable. When working with RegEx terseness is not a virtue anymore :) – Dimitre Novatchev Sep 06 '12 at 11:47
0

Use the tokenize function. There is a nice article on tokenize at http://www.xml.com/pub/a/2003/05/07/tr.html. (Or)

use this

<?xml version="1.0" standalone="yes"?> 
<xsl:stylesheet
 version="1.0"
   xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
   <xsl:variable name="v" select="data/text()"/>
      <list>

      <xsl:call-template name="parse">
        <xsl:with-param name="in" select="normalize-space(translate($v,'[]',''))"/>
        <xsl:with-param name="num">0</xsl:with-param>
      </xsl:call-template>  
  </list> 
</xsl:template>

<xsl:template name="parse">
    <xsl:param name="in"/>
    <xsl:param name="num"/>
    <xsl:variable name="char">'</xsl:variable>
    <xsl:if test="$num &lt; 3">
        <xsl:element name="image">
           <xsl:attribute name="href"><xsl:value-of select="translate(substring-before($in,','),$char,'')"/></xsl:attribute>
        </xsl:element>
      <xsl:call-template name="parse">
        <xsl:with-param name="in" select="substring-after($in,',')"/>
        <xsl:with-param name="num"><xsl:value-of select="$num + 1"/></xsl:with-param>
      </xsl:call-template>  
    </xsl:if>
</xsl:template>

</xsl:stylesheet>
BenMorel
  • 34,448
  • 50
  • 182
  • 322
randominstanceOfLivingThing
  • 16,873
  • 13
  • 49
  • 72