9

In my CMS it is possible to create a new article, and choose an image to be shown on that article. When an image is chosen, a thumbnail of the image will automatically be created as well.

If the uploaded image is called image.jpg, then the corresponding thumbnail will automatically be named image_thumbnail.jpg.

I would now like to use the thumbnail image, everywhere on the website where the article is mentioned, except in the article itself (where the original big image should be shown).

But how can I do that?

I imagine if I could get the original name of the image, and then split it up before the suffix (.jpg, .png, .jpeg etc.) and hardcode _thumbnail after the name, then that would be sufficient.

In other words, I want to take the complete filename, and cut it into two parts, so that I can insert the string _thumbnail between the two parts.

Maybe that would work, but what if an image called image.2horses.jpg (a file with more than one dot in the filename) is uploaded? A naive cut before the '.' wouldn't work here.

Is there a way to get around this? Perhaps by cutting the filename up before the last 4 (.jpg, .png) or 5 (.jpeg) characters?

ire_and_curses
  • 68,372
  • 23
  • 116
  • 141
Kim Andersen
  • 1,913
  • 4
  • 23
  • 36

6 Answers6

21

Off the top of my head:

<xsl:template name="substring-before-last">
  <xsl:param name="string1" select="''" />
  <xsl:param name="string2" select="''" />

  <xsl:if test="$string1 != '' and $string2 != ''">
    <xsl:variable name="head" select="substring-before($string1, $string2)" />
    <xsl:variable name="tail" select="substring-after($string1, $string2)" />
    <xsl:value-of select="$head" />
    <xsl:if test="contains($tail, $string2)">
      <xsl:value-of select="$string2" />
      <xsl:call-template name="substring-before-last">
        <xsl:with-param name="string1" select="$tail" />
        <xsl:with-param name="string2" select="$string2" />
      </xsl:call-template>
    </xsl:if>
  </xsl:if>
</xsl:template>

Called as:

<xsl:template match="/">

  <xsl:variable name="filename" select="'image.2horses.jpg'" />

  <xsl:variable name="basename">
    <xsl:call-template name="substring-before-last">
      <xsl:with-param name="string1" select="$filename" />
      <xsl:with-param name="string2" select="'.'" />
    </xsl:call-template>
  </xsl:variable>

  <xsl:value-of select="$basename" />

</xsl:template>

Yields:

image.2horses
Tomalak
  • 332,285
  • 67
  • 532
  • 628
  • Your code worked as well Tomalak, but I had to mark Chris Nielsen's answer as the correct one, 'cause it solved my problem to the fullest. If I could, I would mark yours as correct too :) – Kim Andersen Jul 14 '09 at 08:24
  • +1; I like your generalized solution better than mine anyhow. – Chris Nielsen Jul 14 '09 at 11:52
9

Given the image's filename in $filename,

If you can assume that all images will end in ".jpg" and won't have ".jpg" elsewhere in the filename, then this should work:

<img src="{substring-before($filename, '.jpg')}_thumbnail.jpg" ... />

If you don't know the image type (like, you want to handle gif and png as well), or if you think the extension may occur multiple times in the filename ("image.jpg.jpg"), then you will want a template to help you:

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

    <xsl:template match="/">
        <p>
            <xsl:call-template name="image_thumbnail">
            <xsl:with-param name="filename" select="'image.jpg'"/>
            </xsl:call-template>
        </p>

        <p>
            <xsl:call-template name="image_thumbnail">
            <xsl:with-param name="filename" select="'image.09.07.11.jpg'"/>
            </xsl:call-template>
        </p>

        <p>
            <xsl:call-template name="image_thumbnail">
            <xsl:with-param name="filename" select="'image.gif'"/>
            </xsl:call-template>
        </p>

        <p>
            <xsl:call-template name="image_thumbnail">
            <xsl:with-param name="filename" select="'image with spaces.jpg'"/>
            </xsl:call-template>
        </p>

        <p>
            <xsl:call-template name="image_thumbnail">
            <xsl:with-param name="filename" select="'image  with  irregular    spaces.jpg'"/>
            </xsl:call-template>
        </p>

        <p>
            <xsl:call-template name="image_thumbnail">
            <xsl:with-param name="filename" select="'image.jpg.again.jpg'"/>
            </xsl:call-template>
        </p>

    </xsl:template>

    <xsl:template name="image_thumbnail">
    <xsl:param name="filename"/>
        <xsl:choose>
        <xsl:when test="contains($filename, '.')">
            <xsl:variable name="before" select="substring-before($filename, '.')"/>
            <xsl:variable name="after" select="substring-after($filename, '.')"/>
            <xsl:choose>
            <xsl:when test="contains($after, '.')">
                <xsl:variable name="recursive">
                    <xsl:call-template name="image_thumbnail">
                    <xsl:with-param name="filename" select="$after"/>
                    </xsl:call-template>
                </xsl:variable>
                <xsl:value-of select="concat($before, '.', $recursive)"/>
            </xsl:when>
            <xsl:otherwise>
                <xsl:value-of select="concat($before, '_thumbnail.', $after)"/>
            </xsl:otherwise>
            </xsl:choose>
        </xsl:when>
        <xsl:otherwise>
            <xsl:value-of select="$filename"/>
        </xsl:otherwise>
        </xsl:choose>
    </xsl:template>

</xsl:stylesheet>
Chris Nielsen
  • 14,731
  • 7
  • 48
  • 54
  • Your solution is roughly equal to mine, I can return the +1 easily. ;-) – Tomalak Jul 14 '09 at 12:19
  • Sorry, -1 from me, assumptions like this one lead to buggy code. I work with tools that do a lot of conversions and they just add the new extension to the end, so I could easily experience something called image.jpg.tif.jpg – David L. May 29 '17 at 10:57
2

A general solution involving only standard XSLT is somewhat hard since you have to search the string from the end. You can split your filename usings two functions, substring-before-last and substring-after-last. Unfortunately, these functions are not part of XSLT. You can Google and try to find implementations. Assuming you have these two functions implemented as XSLT templates you can then use the following template to generate thumbnail names:

<xsl:template name="thumbnail-name">
  <xsl:param name="file-name"/>
  <xsl:call-template name="substring-before-last">
    <xsl:with-param name="text" select="$file-name"/>
    <xsl:with-param name="chars" select="'.'"/>
  </xsl:call-template>
  <xsl:text>_thumbnail.</xsl:text>
  <xsl:call-template name="substring-after-last">
    <xsl:with-param name="text" select="$file-name"/>
    <xsl:with-param name="chars" select="'.'"/>
  </xsl:call-template>
</xsl:template>

You can use the template like this (assuming the variable $file-name contains the name of the image):

<img>
  <xsl:attribute name="src">
    <xsl:call-template name="thumbnail-name">
      <xsl:with-param name="file-name" select="$file-name"/>
    </xsl:call-template>
  </xsl:attribute>
</img>
Martin Liversage
  • 104,481
  • 22
  • 209
  • 256
1

Have a look at the XPath functions overview at W3Schools, specifically the substring-before method.

Rahul
  • 12,181
  • 5
  • 43
  • 64
  • What if there is a filename with more that one dot. E.g. Image.09.07.11.jpg, and i made a substring-before('Image.09.07.11.jpg','.')? Wouldn't I get "Image" and nothing more? – Kim Andersen Jul 13 '09 at 13:36
  • You may get the last dot in the string by recursion or a loop. See http://www.biglist.com/lists/xsl-list/archives/200102/msg00838.html – Scoregraphic Jul 13 '09 at 13:53
0

I believe XPath functions operating on string might help you. I would try with some simple replace or translate.

Grzegorz Oledzki
  • 23,614
  • 16
  • 68
  • 106
0

XSLT 2 solution using regexp:

replace($filename, '(\.[^\.]*)$', concat('_thumbnail', '$1'))

Original answer (also XSLT 2): This removes all after the last separator (including the separator). So below the $separatorRegexp could be '\.jpg' or just '\.' and the $separator '.jpg' or '.' in the other case.

string-join(reverse(remove(reverse(tokenize($filename, $separatorRegexp)),1)),$separator)

Eventually the '_thumbnail.jpg' can be appended with concat.

David L.
  • 3,149
  • 2
  • 26
  • 28
  • When I tried this with `image.2horses.jpg` (from the question) I get `image.2horses`. (Probably because there is only one item in the sequence so there's nothing to string-join.) A different XPath 2.0 approach would be `replace($filename,$separatorRegexp,concat($separator,'$1'))` where $separatorRegexp = `(\.[^\.]*)$` and $separator = `_thumbnail`. This would give you `image.2horses_thumbnail.jpg` as the output. – Daniel Haley Jun 01 '17 at 22:43
  • Here's the replace without the variables: `replace($filename, '(\.[^\.]*)$', concat('_thumbnail', '$1'))` – Daniel Haley Jun 01 '17 at 22:51
  • Daniel Haley: That's what I intended. Appending the _thumbnail.jpg is already trivial. And as my solution involves a regexp anyway, so your is apparently better. – David L. Jun 02 '17 at 08:11