XML:
<node>
<node date="01-01-2002">Node</node>
<node date="01-01-2005">Node</node>
<node date="01-01-2001">Node</node>
<node date="01-01-2003">Node</node>
<node date="01-01-2006">Node</node>
<node>
<node date="01-01-2000">Node</node>
<node date="01-01-2007">Node</node>
</node>
<node date="01-01-2004">Node</node>
</node>
Problem:
I need to sort by date AND take a limited number of sorted nodes. Need to be able to traverse any number of levels.
Required result:
<p>01-01-2000</p>
<p>01-01-2001</p>
<p>01-01-2002</p>
<p>01-01-2003</p>
<p>01-01-2004</p>
Assumptions:
For sorting by date I use c# extension method that returns time stamp:
<xsl:sort select="cs:formatDate(#date)" order="ascending" data-type="number" />
Limit to 5 oldest nodes.
Order: ascending
XSLT 1.0
EDIT:
As requested this is where i got so far:
I can do sorting and limiting for not nested nodes:
<xsl:template match="node">
<xsl:apply-templates select="node">
<xsl:sort select="cs:formatDate(#date,'dd-MM-yyyy','timestamp')" order="ascending" data-type="number" />
<xsl:with-param name="limit" select="5"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="node[#date]">
<xsl:param name="limit" />
<xsl:if test="position() < $limit+1">
<h5><xsl:value-of select="#date"/></h5>
</xsl:if>
</xsl:template>
Or when I try to apply for nested as below, I get nested nodes sorted in isolation, and I cannot limit them in same way anymore:
<xsl:template match="*">
<xsl:apply-templates select="node[#date]">
<xsl:sort select="cs:formatDate(#date,'dd-MM-yyyy','timestamp')" order="ascending" data-type="number" />
</xsl:apply-templates>
<xsl:apply-templates select="node[not(#date)]">
</xsl:apply-templates>
</xsl:template>
<xsl:template match="node[#date]">
<h5><xsl:value-of select="#date"/></h5>
</xsl:template>
<xsl:template match="node[not(#date)]">
<xsl:apply-templates select="node[#date]">
<xsl:sort select="cs:formatDate(#date,'dd-MM-yyyy','timestamp')" order="ascending" data-type="number" />
</xsl:apply-templates>
<xsl:apply-templates select="node[not(#date)]">
</xsl:apply-templates>
</xsl:template>
EDIT:
I thought it is obvious, but probably not: I need sort to be applied before the limit. E.g: "get oldest five" and NOT:"get first five nodes from xml and then sort them"
<xsl:template match="/">
<xsl:apply-templates select="//node[#date]">
<xsl:sort select="concat(substring-after(substring-after(#date,'-'),'-'),substring-before(substring-after(#date,'-'),'-'),substring-before(#date,'-'))" order="ascending" data-type="number" />
<xsl:with-param name="start" select="1"/>
<xsl:with-param name="end" select="5"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="node">
<xsl:param name="start" />
<xsl:param name="end" />
<xsl:if test="position() >= $start and position() <= $end">
<p>
<xsl:value-of select="#date"/>
</p>
</xsl:if>
</xsl:template>
Related
I have a number of vendor records which contain multiple addresses e.g.
<vendor>
<addresses>
<address primary="yes">
<line1 />
<city />
<state />
....
</address>
<address primary="no">
<line1 />
<city />
<state />
....
</address>
</addresses>
</vendor>
Some required elements are missing -- preventing updating of the records. Can xmlstarlet can be used to add an element with a default value if it is missing?
Here's a simple example. I'll use xmllint --auto for the xml source. Then we'll add an <add-me> element as a child of <info> if it doesn't exist using the identity transform pattern.
Source xml:
xmllint --auto
<?xml version="1.0"?>
<info>abc</info>
Add the missing element:
xmllint --auto | xsltproc add-missing.xsl -
<?xml version="1.0"?>
<info><add-me>some stuff</add-me>abc</info>
add-missing.xsl:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="info">
<xsl:copy>
<xsl:if test="not(add-me)">
<add-me>some stuff</add-me>
</xsl:if>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Another XSLT w/xmlstarlet option is to use a variable that contains the required elements (with or without default values) and treat is as a node set (using the supported exsl:node-set() function).
You can then iterate over the node set to see if an element with the same name already exists. If it does, use it. Otherwise use the default.
Example...
XML Input (input.xml)
<vendor>
<addresses>
<address primary="yes">
<line1>address 1 line1</line1>
<state>address 1 state1</state>
</address>
<address primary="no">
<line1>address 2 line1</line1>
<city>address 2 city</city>
<state>address 2 state</state>
</address>
</addresses>
</vendor>
XSLT 1.0 (so.xsl)
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common" exclude-result-prefixes="exsl">
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:variable name="req_elems">
<req>
<line1/>
<city/>
<state/>
<country/>
</req>
</xsl:variable>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="address">
<xsl:variable name="ctx" select="."/>
<xsl:copy>
<xsl:apply-templates select="#*"/>
<xsl:for-each select="exsl:node-set($req_elems)/req/*">
<xsl:choose>
<xsl:when test="$ctx/*[local-name()=local-name(current())]">
<xsl:apply-templates select="$ctx/*[local-name()=local-name(current())]"/>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="."/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
XML Output
<vendor>
<addresses>
<address primary="yes">
<line1>address 1 line1</line1>
<city/>
<state>address 1 state1</state>
<country/>
</address>
<address primary="no">
<line1>address 2 line1</line1>
<city>address 2 city</city>
<state>address 2 state</state>
<country/>
</address>
</addresses>
</vendor>
Note: This only works if the only allowed elements in address are the same as $req_elements. For example, if you have an element named "foo" in address, it will be dropped from the output.
i need to add an attribute initial-page-number to a tag fo:sequence
tha tag is
<fo:page-sequence master-reference="alternating" initial-page-number="1"><fo:page-sequence>
..
...
</fo:page-sequence>
become
<fo:page-sequence master-reference="alternating" initial-page-number="1">
..
</fo:page-sequence>
but with the xslt i obtain two fo:page:
<fo:page-sequence master-reference="alternating" initial-page-number="1"><fo:page-sequence>
</fo:page-sequence></fo:page-sequence>
How can i replace old fo:page-sequence with new one?
This is my xsl stylesheet:
<xsl:stylesheet>
<xsl:template match="ss:split/fo:page-sequence">
<xsl:choose>
<xsl:when test="#master-reference['alternating']">
<xsl:element name="fo:page-sequence">
<xsl:for-each select="#*">
<xsl:attribute name="{name()}"><xsl:value-of select="."/></xsl:attribute>
</xsl:for-each>
<xsl:attribute name="initial-page-number">
<xsl:value-of select="1"/>
</xsl:attribute>
<xsl:copy>
<xsl:apply-templates select="child::*"/>
</xsl:copy>
</xsl:element>
</xsl:when>
</xsl:choose>
</xsl:template>
<xsl:template match='comment()'>
<xsl:comment><xsl:value-of select="."/></xsl:comment>
</xsl:template>
<xsl:template match="#*|*">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Your stylesheet changes every fo:page-sequence because the predicate ['alternating'] is always true.
You can check for the master-reference value in the match pattern, plus you can just copy the existing attributes, and you can copy the contents of the fo:page-sequence since it won't contain another fo:page-sequence:
<xsl:template
match="ss:split/fo:page-sequence[#master-reference = 'alternating']">
<xsl:copy>
<xsl:copy-of select="#*" />
<xsl:attribute name="initial-page-number">1</xsl:attribute>
<xsl:copy-of select="node()" />
</xsl:copy>
</xsl:template>
Your stylesheet creates an fo:page-sequence using <xsl:element name="fo:page-sequence">, and another one with <xsl:copy> (as the matching element is an fo:page-sequence).
Just remove the xsl:copy (but leave <xsl:apply-templates select="child::*"/>, as you want to process the children of the current node!) and you should get what you need.
I have a XML row which has a lengthy sentence for instance:
<ROW>4.)Whatever universe a professor believes in must at any rate be a universe that lends itself to lengthy discourse. A universe definable in two sentences is something for which the professorial intellect has no use. No faith in anything of that cheap kind!
I need to break the sentence into smaller sentences and display them in multiple lines in Altova Stylevision.
Iam using Autocalc to display the contents in row dynamically. Is there any Xpath expression which I can use to split the row into multiple rows based on the number of words or number of characters in each line.So that I can display the row as:
Whatever universe a professor believes in must at any rate
be a universe that lends itself to lengthy discourse.
and so on.
It seems you are trying to break your string input into multiple pieces (word/String length). I have created a sample for you to make it easy. Please go through and check if it can help you:
input xml
<?xml version="1.0" encoding="UTF-8"?>
<ROW>4.)Whatever universe a professor believes in must at any rate be a universe that lends itself to lengthy discourse. A universe definable in two sentences is something for which the professorial intellect has no use. No faith in anything of that cheap kind!11</ROW>
XSLT
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet 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 indent="yes" method="xml"/>
<xsl:template match="ROW">
<output>
<xsl:variable name="Length" select="string-length(.)"/>
<xsl:variable name="Words" select="count(tokenize(.,'\s'))"/>
<xsl:variable name="ByLength" select="50"/>
<xsl:variable name="ByWord" select="5"/>
<!-- To get ouput by words -->
<xsl:if test="$ByWord">
<xsl:variable name="Result">
<xsl:call-template name="GetBreakingWords">
<xsl:with-param name="Value" select="."/>
<xsl:with-param name="ByWord" select="$ByWord"/>
<xsl:with-param name="Words" select="$Words"/>
</xsl:call-template>
</xsl:variable>
<p>By word example</p>
<xsl:for-each select="$Result/row">
<xsl:sort select="#id" data-type="number"/>
<!--<row><xsl:value-of select="."/></row>-->
<xsl:copy-of select="."/>
</xsl:for-each>
</xsl:if>
<!-- To get ouput by string length -->
<xsl:if test="$ByLength">
<xsl:variable name="Result">
<xsl:call-template name="GetBreakingStrings">
<xsl:with-param name="Value" select="."/>
<xsl:with-param name="ByLength" select="$ByLength"/>
<xsl:with-param name="Length" select="$Length"/>
</xsl:call-template>
</xsl:variable>
<p>By String example</p>
<xsl:for-each select="$Result/row">
<xsl:sort select="#id" data-type="number"/>
<!--<row><xsl:value-of select="."/></row>-->
<xsl:copy-of select="."/>
</xsl:for-each>
</xsl:if>
</output>
</xsl:template>
<xsl:template name="GetBreakingWords">
<xsl:param name="ByWord"/>
<xsl:param name="Value"/>
<xsl:param name="Words"/>
<xsl:param name="counter" select="1"/>
<xsl:choose>
<xsl:when test="($Words gt $ByWord)">
<xsl:variable name="CurrentRow" select="normalize-space(string-join(for $x in tokenize($Value, '\s')[position() ge 1 and position() le $ByWord] return $x, ' '))"/>
<xsl:call-template name="GetBreakingWords">
<xsl:with-param name="ByWord" select="$ByWord"/>
<xsl:with-param name="Words" select="count(tokenize(substring-after($Value, $CurrentRow),'\s'))"/>
<xsl:with-param name="Value" select="substring-after($Value, $CurrentRow)"/>
<xsl:with-param name="counter" select="sum($counter + 1)"></xsl:with-param>
</xsl:call-template>
<row id="{$counter}"><xsl:value-of select="$CurrentRow"/></row>
</xsl:when>
<xsl:when test="not($Words gt $ByWord) and $Value">
<row id="{$counter}"><xsl:value-of select="$Value"/></row>
</xsl:when>
</xsl:choose>
</xsl:template>
<xsl:template name="GetBreakingStrings">
<xsl:param name="ByLength"/>
<xsl:param name="Value"/>
<xsl:param name="Length"/>
<xsl:param name="counter" select="1"/>
<xsl:choose>
<xsl:when test="($Length gt $ByLength)">
<xsl:variable name="CurrentRow" select="normalize-space(string-join(substring($Value,0,$ByLength),' '))"/>
<xsl:call-template name="GetBreakingStrings">
<xsl:with-param name="ByLength" select="$ByLength"/>
<xsl:with-param name="Length" select="string-length(substring-after($Value, $CurrentRow))"/>
<xsl:with-param name="Value" select="substring-after($Value, $CurrentRow)"/>
<xsl:with-param name="counter" select="sum($counter + 1)"></xsl:with-param>
</xsl:call-template>
<row id="{$counter}"><xsl:value-of select="$CurrentRow"/></row>
</xsl:when>
<xsl:when test="not($Length gt $ByLength) and $Value">
<row id="{$counter}"><xsl:value-of select="$Value"/></row>
</xsl:when>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
output:
<?xml version="1.0" encoding="UTF-8"?>
<output>
<p>By word example</p>
<row id="1">4.)Whatever universe a professor believes</row>
<row id="2">in must at any</row>
<row id="3">rate be a universe</row>
<row id="4">that lends itself to</row>
<row id="5">lengthy discourse. A universe</row>
<row id="6">definable in two sentences</row>
<row id="7">is something for which</row>
<row id="8">the professorial intellect has</row>
<row id="9">no use. No faith</row>
<row id="10">in anything of that</row>
<row id="11"> cheap kind!11</row>
<p>By String example</p>
<row id="1">4.)Whatever universe a professor believes in must</row>
<row id="2">at any rate be a universe that lends itself to l</row>
<row id="3">engthy discourse. A universe definable in two sen</row>
<row id="4">tences is something for which the professorial in</row>
<row id="5">tellect has no use. No faith in anything of that</row>
<row id="6"> cheap kind!11</row>
</output>
I am using selenium with perl and have label on page, to access this label i have following xpath: //*[text()='some here'] , the problem that a need to get full xpath of this element, like /html/body/table/tr/..../any other/and other/ , is there is any selenium method or perl function ? looking for perl solution or any other working things.
thanks
looking for perl solution or any other
working things
This XPath 2.0 expression:
string-join(for $node in ancestor-or-self::node()
return concat(('#')[$node/self::attribute()],
$node/name(),
(concat('[',
count($node/preceding-sibling::node()
[name()=$node/name()]) + 1,
']'))[$node/../node()
[name()=$node/name()][2]]),
'/')
Edit: Shorter expression.
This XSLT 1.0 transformation produces an XPath expression for every node contained in the $pNode parameter:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()|#*">
<path>
<xsl:call-template name="buildPath"/>
</path>
<xsl:apply-templates select="node()|#*"/>
</xsl:template>
<xsl:template name="buildPath">
<xsl:variable name="pNode" select="."/>
<xsl:variable name="theResult">
<xsl:for-each select="$pNode">
<xsl:variable name="theNode" select="."/>
<xsl:for-each select=
"$theNode
|
$theNode/ancestor-or-self::node()[..]">
<xsl:element name="slash">/</xsl:element>
<xsl:choose>
<xsl:when test="self::*">
<xsl:element name="nodeName">
<xsl:value-of select="name()"/>
<xsl:variable name="thisPosition" select=
"count(preceding-sibling::*
[name(current())
=
name()])"/>
<xsl:variable name="numFollowing" select=
"count(following-sibling::
*[name(current())
=
name()])"/>
<xsl:if test="$thisPosition + $numFollowing > 0">
<xsl:value-of select=
"concat('[', $thisPosition +1, ']')"/>
</xsl:if>
</xsl:element>
</xsl:when>
<xsl:otherwise> <!-- This node is not an element -->
<xsl:choose>
<xsl:when test="count(. | ../#*) = count(../#*)">
<!-- Attribute -->
<xsl:element name="nodeName">
<xsl:value-of select="concat('#',name())"/>
</xsl:element>
</xsl:when>
<xsl:when test="self::text()"> <!-- Text -->
<xsl:element name="nodeName">
<xsl:value-of select="'text()'"/>
<xsl:variable name="thisPosition"
select="count(preceding-sibling::text())"/>
<xsl:variable name="numFollowing"
select="count(following-sibling::text())"/>
<xsl:if test="$thisPosition + $numFollowing > 0">
<xsl:value-of select=
"concat('[', $thisPosition +1, ']')"/>
</xsl:if>
</xsl:element>
</xsl:when>
<xsl:when test="self::processing-instruction()">
<!-- Processing Instruction -->
<xsl:element name="nodeName">
<xsl:value-of select="'processing-instruction()'"/>
<xsl:variable name="thisPosition"
select="count(preceding-sibling::processing-instruction())"/>
<xsl:variable name="numFollowing"
select="count(following-sibling::processing-instruction())"/>
<xsl:if test="$thisPosition + $numFollowing > 0">
<xsl:value-of select=
"concat('[', $thisPosition +1, ']')"/>
</xsl:if>
</xsl:element>
</xsl:when>
<xsl:when test="self::comment()"> <!-- Comment -->
<xsl:element name="nodeName">
<xsl:value-of select="'comment()'"/>
<xsl:variable name="thisPosition"
select="count(preceding-sibling::comment())"/>
<xsl:variable name="numFollowing"
select="count(following-sibling::comment())"/>
<xsl:if test="$thisPosition + $numFollowing > 0">
<xsl:value-of select=
"concat('[', $thisPosition +1, ']')"/>
</xsl:if>
</xsl:element>
</xsl:when>
<!-- Namespace: -->
<xsl:when test=
"count(. | ../namespace::*)
=
count(../namespace::*)">
<xsl:variable name="apos">'</xsl:variable>
<xsl:element name="nodeName">
<xsl:value-of select="concat('namespace::*',
'[local-name() = ', $apos, local-name(), $apos, ']')"/>
</xsl:element>
</xsl:when>
</xsl:choose>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
<!-- <xsl:text>
</xsl:text> -->
</xsl:for-each>
</xsl:variable>
<xsl:value-of select="$theResult"/>
</xsl:template>
</xsl:stylesheet>
when applied on the following XML document:
<div id="entry-1" class="item-asset asset hentry">
<div class="asset-header">
<h2 class="asset-name entry-title">
<a rel="bookmark" href="http://blahblah.com/paper-scissors">Paper Scissors</a>
</h2>
</div>
<div class="asset-content entry-content">
<div class="asset-body">
<p>Paper and scissors</p>
</div>
</div>
</div>
the result is a list of the XPath expressions for every node in the document:
<path>/div</path>
<path>/div/#id</path>
<path>/div/#class</path>
<path>/div/div[1]</path>
<path>/div/div[1]/#class</path>
<path>/div/div[1]/h2</path>
<path>/div/div[1]/h2/#class</path>
<path>/div/div[1]/h2/a</path>
<path>/div/div[1]/h2/a/#rel</path>
<path>/div/div[1]/h2/a/#href</path>
<path>/div/div[1]/h2/a/text()</path>
<path>/div/div[2]</path>
<path>/div/div[2]/#class</path>
<path>/div/div[2]/div</path>
<path>/div/div[2]/div/#class</path>
<path>/div/div[2]/div/p</path>
<path>/div/div[2]/div/p/text()</path>
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?
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
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>
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>
Have a look at the XPath functions overview at W3Schools, specifically the substring-before method.
I believe XPath functions operating on string might help you. I would try with some simple replace or translate.
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.