4
votes

Apologies in advance - total novice here and struggling after reading pretty much every XSLT thread in here. So I desperately need several aspirins and your guidance!!

I have three incoming parameters/variables that need to be processed using xslt version 1.0.

<!-- Variables in the XSL -->
<xsl:variable name="tw">125</xsl:variable>
<xsl:variable name="rows">4</xsl:variable>
<xsl:variable name="cols">6</xsl:variable>

I want to end up with the calculated values (the pixels) in the 'background-positions' of the HTML as seen below:-

<div style="background-position:-0px -0px;"><img src="images/thumbs/1.jpg" alt="one" /></div>
<div style="background-position:-125px -0px;"><img src="images/thumbs/2.jpg" alt="two" /></div>
<div style="background-position:-250px -0px;"><img src="images/thumbs/3.jpg" alt="three" /></div>
<div style="background-position:-375px -0px;"><img src="images/thumbs/4.jpg" alt="four" /></div>
<div style="background-position:-500px -0px;"><img src="images/thumbs/5.jpg" alt="five" /></div>
<div style="background-position:-625px -0px;"><img src="images/thumbs/6.jpg" alt="six" /></div>

<div style="background-position:-0px -125px;"><img src="images/thumbs/7.jpg" alt="seven" /></div>
<div style="background-position:-125px -125px;"><img src="images/thumbs/8.jpg" alt="eight" /></div>
<div style="background-position:-250px -125px;"><img src="images/thumbs/9.jpg" alt="nine" /></div>
<div style="background-position:-375px -125px;"><img src="images/thumbs/10.jpg" alt="ten" /></div>
<div style="background-position:-500px -125px;"><img src="images/thumbs/11.jpg" alt="11" /></div>
<div style="background-position:-625px -125px;"><img src="MIB/images/thumbs/12.jpg" alt="12" /></div>

<div style="background-position:-0px -250px;"><img src="images/thumbs/13.jpg" alt="13" /></div>
<div style="background-position:-125px -250px;"><img src="/mages/thumbs/14.jpg" alt="14" /></div>
<div style="background-position:-250px -250px;"><img src="images/thumbs/15.jpg" alt="15" /></div>
<div style="background-position:-375px -250px;"><img src="images/thumbs/16.jpg" alt="16" /></div>
<div style="background-position:-500px -250px;"><img src="images/thumbs/17.jpg" alt="17" /></div>
<div style="background-position:-625px -250px;"><img src="images/thumbs/18.jpg" alt="18" /></div>

<div style="background-position:-0px -375px;"><img src="images/thumbs/19.jpg" alt="19" /></div>
<div style="background-position:-125px -375px;"><img src="images/thumbs/20.jpg" alt="20" /></div>
<div style="background-position:-250px -375px;"><img src="images/thumbs/21.jpg" alt="21" /></div>
<div style="background-position:-375px -375px;"><img src="images/thumbs/22.jpg" alt="22" /></div>
<div style="background-position:-500px -375px;"><img src="images/thumbs/23.jpg" alt="23" /></div>
<div style="background-position:-625px -375px;"><img src="images/thumbs/24.jpg" alt="24" /></div>

I realise that I need to loop (somehow) and keep a count (somehow) and having seen numerous looping and counting examples I expected the process to be straight forward but then I read about position() and number and so many other things that my head is spinning. I have no clue how or where to position the counting loop within a for-each statement or even if a for-each is the best solution. None of my feeble attempts produced well formed XSL so you can see I am getting no where fast.

So here's hoping one of the guru's here can help me start the process off and assist in my understanding of xslt.

The image data is coming from an XML file and I am able to process the img src and alt without a problem so it's just the pixel calculations, looping, counting etc that is the problem

<xsl:for-each select="DATASET/ITEM">
<div style="background-position:-{rowpos}px -{colpos}px;"><img src="{thumbnailimage}" alt="{imagealttext}" /></div>
</xsl:for-each>
<!-- rowpos and colpos are the calculated values that are generated from whatever loop and count process is used -->

So for clarification:

Calculation. There are 3 variables which would produce 6 calculated values. tw=125. The width is used for the background positions so depending on the row/column the calculated values are as shown in the example - e.g. tw-tw, tw, tw*2, tw*3, tw*4 and tw*5 (a total of 6 calculations). The number of rows and number of columns determines how and where each thumbnail is positioned using the calculated pixel values - which in the case above are 0px, -125px, -250px, -375px, -500px and -675px.

Further expansion of the calculations:- Row and Column positions are determined by the width of a thumbnail image. The position of each thumbnail is determined by the number of rows and number of columns. A 3 row by 3 column grid with 150px wide thumbs will require 9 thumbnails therefore 9 sets of px values but only 2 unique calculations would be required e.g. tw*2 and tw*3 as shown below:-

<div style="background-position: -0px -0px;" /><img src="image 1.jpg" alt="Alt text 1" /></div>
<div style="background-position: -125px -0px;" /><img src="image 2.jpg" alt="Alt text 2" /></div>
<div style="background-position: -250px -0px;" /><img src="image 3.jpg" alt="Alt text 3" /></div>
<div style="background-position: -375px -0px;" /><img src="image 4.jpg" alt="Alt text 4" /></div>
<div style="background-position: -500px -0px;" /><img src="image 5.jpg" alt="Alt text 5" /></div>
<div style="background-position: -0px -125px;" /><img src="image 6.jpg" alt="Alt text 6" /></div>
<div style="background-position: -125px -125px;" /><img src="image 7.jpg" alt="Alt text 7" /></div>
<div style="background-position: -250px -125px;" /><img src="image 8.jpg" alt="Alt text 8" /></div>
<div style="background-position: -375px -125px;" /><img src="image 9.jpg" alt="Alt text 9" /></div>
<div style="background-position: -500px -125px;" /><img src="image 10.jpg" alt="Alt text 10" /></div>
<div style="background-position: -0px -250px;" /><img src="image 11.jpg" alt="Alt text 11" /></div>
<div style="background-position: -125px -250px;" /><img src="image 12.jpg" alt="Alt text 12" /></div>
<div style="background-position: -250px -250px;" /><img src="image 13.jpg" alt="Alt text 13" /></div>
<div style="background-position: -375px -250px;" /><img src="image 14.jpg" alt="Alt text 14" /></div>
<div style="background-position: -500px -250px;" /><img src="image 15.jpg" alt="Alt text 15" /></div>
<div style="background-position: -0px -375px;" /><img src="image 16.jpg" alt="Alt text 16" /></div>
<div style="background-position: -125px -375px;" /><img src="image 17.jpg" alt="Alt text 17" /></div>
<div style="background-position: -250px -375px;" /><img src="image 18.jpg" alt="Alt text 18" /></div>
<div style="background-position: -375px -375px;" /><img src="image 19.jpg" alt="Alt text 19" /></div>
<div style="background-position: -500px -375px;" /><img src="image 20.jpg" alt="Alt text 20" /></div>

XML file The image src and alt text come from an XML file:-

<DATASET>
<ITEM>
   <THUMBNAILIMAGE>image1.jpg</THUMBNAILIMAGE>
   <IMAGEALTTEXT>Alt text 1</IMAGEALTTEXT>
</ITEM>
<ITEM>
   <THUMBNAILIMAGE>image2.jpg</THUMBNAILIMAGE>
   <IMAGEALTTEXT>Alt text 2</IMAGEALTTEXT>
</ITEM>
......
<ITEM>
   <THUMBNAILIMAGE>image20.jpg</THUMBNAILIMAGE>
   <IMAGEALTTEXT>Alt text 20</IMAGEALTTEXT>
</ITEM>
</DATASET>
3
So, what is the question? I dont see one. Also, where is the source XML document (as small as possible, please)? What is the meaning of the variables with the shouting names (btw, their values are all set in not the best way)? Finally, what are the requirements for the transformation? Please, edit the question and provide all of this important missing information -- to give it meaning.Dimitre Novatchev
@Dimitre: I had hoped you would respond...but not in that way :(. I thought the question was clear - how do I achieve the calculations via a loop process and write the html in the example shown. What more do I need to provide. There is no XML file as such. The XSLT will be passed three parameters - tw, cols, rows. Sorry I don't understand the transformation requirements question. All I want to do is place the correctly calculated pixel values ROWPOS and COLPOS using correctly written xslt.PBB
Calculate what? Any rules for the calculation? How should the values for the positions be generated? Using what rules? How can you say that " There is no XML file as such" when your code contains the XPath expression DATASET/ITEM ??? Why should people have to ask these questions or to guess? All this important information must be in the question, or else the question is not meaningful.Dimitre Novatchev
PBB: ROWPOS and COLPOS are names that (in the way written) must belong in the source XML document -- this certainly adds more to the confusion.Dimitre Novatchev
PBB: Giving all this necessary information is a good first step. Now, to make this a good question, could you, please, edit and put all of the information from your comments into the text of the question itself? You could additionally read Jon Skeet's blog about how to write a good question -- I think a link to this can be found in the FAQs of SO. One most important thing anyone should learn is how to ask a good question. Many times, just following the guidelines for defining a question in the good way, helps the asker to better understand the problem and even to find a solution.Dimitre Novatchev

3 Answers

3
votes

I think that this could accomplished using the code below (XSLT 1.0). The following transform should go with any XML file.

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

<xsl:output indent="yes" />

<xsl:variable name="TW">125</xsl:variable>
<xsl:variable name="ROWS">4</xsl:variable>
<xsl:variable name="COLS">6</xsl:variable>
<xsl:variable name="TOTAL"><xsl:value-of select="$ROWS * $COLS"/></xsl:variable>

<xsl:template match="/">
    <xsl:call-template name="iterate_rows" />
</xsl:template>

<xsl:template name="iterate_rows">
    <xsl:param name="num">0</xsl:param> 

    <xsl:if test="not($num = $ROWS)">
        <xsl:call-template name="iterate_cols">
            <xsl:with-param name="rows" select="$num"/>
        </xsl:call-template>

        <xsl:call-template name="iterate_rows">
            <xsl:with-param name="num" select="$num + 1"/>
        </xsl:call-template>
    </xsl:if>
</xsl:template>

<xsl:template name="iterate_cols">
    <xsl:param name="num">0</xsl:param> 
    <xsl:param name="rows" />

    <xsl:if test="not($num = $COLS)">
        <xsl:element name="div">
            <xsl:attribute name="style" ><xsl:value-of select="concat('background-position:-', $TW*$num, 'px -', $TW*$rows, 'px;')" /></xsl:attribute>

            <xsl:variable name="number" select="$rows*$COLS + $num + 1" />

            <xsl:element name="img">
                <xsl:attribute name="src">
                    <xsl:value-of select="concat('images/thumbs/', $number, '.jpg')"/>
                </xsl:attribute>
                <xsl:attribute name="alt">
                    <xsl:value-of select="$number"/>
                </xsl:attribute>
            </xsl:element>

        </xsl:element>

        <xsl:call-template name="iterate_cols">
            <xsl:with-param name="num" select="$num + 1"/>
            <xsl:with-param name="rows" select="$rows" />
        </xsl:call-template>
    </xsl:if>
</xsl:template>

</xsl:stylesheet>

EDIT:

In XSLT it is impossible to construct loops similar to those in C++ or Java, because it is impossible to change value of a variable (so it is impossible to have a counter). But it is possible to replace iterations with recurrence.

In my solution, I have defined two recurrently called templates - the first one is named iterate_rows, while the second one is iterate_cols. The entry point for the whole 'program' is:

<xsl:template match="/">
    <xsl:call-template name="iterate_rows" />
</xsl:template>

This is invoked in the first place. The named template iterate_rows has one parameter num and it has got a default value set to 0. The important thing is the <xsl:if ...> condition which checks if we have reached the end of the loop (in this case it would be the value of $ROWS). Inside of the condition block we have two calls: one executes iterate_cols template and the second one is the call to iterate_rows template itself (note: that we call it with incremented parameter num). The second template works in a similar way. Hope this helps.

1
votes

In XSLT 2.0 this is straightforward: it's something like:

<xsl:for-each select="1 to $rows">
  <xsl:variable name="row" select="position()"/>
  <xsl:for-each select="1 to $columns">
     <xsl:variable name="column" select="position()"/>
     <div style="background-position: -{$row*$tw}px -{$column*$tw}px"/>
  </
</

In 1.0 you don't have the luxury of select="1 to $rows", but a popular workaround is to use select="(//*)[position() &lt;= $rows]", which works provided there are at least $rows elements in the source document. You're not actually interested in the nodes being selected, only in how many of them there are, which determines how many times you execute the loop.

0
votes

Here is a complete, shorter and simpler (only a single recursive template), recursive, XSLT 1.0 transformation:

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

     <xsl:param name="pTw" select="125"/>
     <xsl:param name="pNumRows" select="4"/>
     <xsl:param name="pNumCols" select="5"/>

     <xsl:variable name="vImageItems" select="/*/*"/>

     <xsl:template match="/">
      <xsl:call-template name="generateDiv"/>
     </xsl:template>

     <xsl:template name="generateDiv">
      <xsl:param name="pRow" select="1"/>
      <xsl:param name="pCol" select="1"/>

      <xsl:if test="not($pRow > $pNumRows)">
        <xsl:variable name="vImageItem" select=
        "$vImageItems[($pRow -1)*$pNumCols + $pCol]"/>

        <div style="background-position: -{($pCol -1)*$pTw}px -{($pRow -1)*$pTw}px;">
          <img src="{$vImageItem/THUMBNAILIMAGE}"
               alt="{$vImageItem/IMAGEALTTEXT}" />
        </div>

        <xsl:variable name="vnewCol">
         <xsl:choose>
          <xsl:when test="$pCol = $pNumCols">1</xsl:when>
          <xsl:otherwise><xsl:value-of select="$pCol+1"/></xsl:otherwise>
         </xsl:choose>
        </xsl:variable>

        <xsl:variable name="vnewRow">
          <xsl:choose>
            <xsl:when test="$vnewCol = 1"><xsl:value-of select="$pRow+1"/></xsl:when>
            <xsl:otherwise><xsl:value-of select="$pRow"/></xsl:otherwise>
          </xsl:choose>
        </xsl:variable>

        <xsl:call-template name="generateDiv">
         <xsl:with-param name="pRow" select="$vnewRow"/>
         <xsl:with-param name="pCol" select="$vnewCol"/>
        </xsl:call-template>
      </xsl:if>
     </xsl:template>
</xsl:stylesheet>

When this transformation is applied to the provided XML document:

<DATASET>
    <ITEM>
        <THUMBNAILIMAGE>image1.jpg</THUMBNAILIMAGE>
        <IMAGEALTTEXT>Alt text 1</IMAGEALTTEXT>
    </ITEM>
    <ITEM>
        <THUMBNAILIMAGE>image2.jpg</THUMBNAILIMAGE>
        <IMAGEALTTEXT>Alt text 2</IMAGEALTTEXT>
    </ITEM>
    <ITEM>
        <THUMBNAILIMAGE>image3.jpg</THUMBNAILIMAGE>
        <IMAGEALTTEXT>Alt text 3</IMAGEALTTEXT>
    </ITEM>
    <ITEM>
        <THUMBNAILIMAGE>image4.jpg</THUMBNAILIMAGE>
        <IMAGEALTTEXT>Alt text 4</IMAGEALTTEXT>
    </ITEM>
    <ITEM>
        <THUMBNAILIMAGE>image5.jpg</THUMBNAILIMAGE>
        <IMAGEALTTEXT>Alt text 5</IMAGEALTTEXT>
    </ITEM>
    <ITEM>
        <THUMBNAILIMAGE>image6.jpg</THUMBNAILIMAGE>
        <IMAGEALTTEXT>Alt text 6</IMAGEALTTEXT>
    </ITEM>
    <ITEM>
        <THUMBNAILIMAGE>image7.jpg</THUMBNAILIMAGE>
        <IMAGEALTTEXT>Alt text 7</IMAGEALTTEXT>
    </ITEM>
    <ITEM>
        <THUMBNAILIMAGE>image8.jpg</THUMBNAILIMAGE>
        <IMAGEALTTEXT>Alt text 8</IMAGEALTTEXT>
    </ITEM>
    <ITEM>
        <THUMBNAILIMAGE>image9.jpg</THUMBNAILIMAGE>
        <IMAGEALTTEXT>Alt text 9</IMAGEALTTEXT>
    </ITEM>
    <ITEM>
        <THUMBNAILIMAGE>image10.jpg</THUMBNAILIMAGE>
        <IMAGEALTTEXT>Alt text 10</IMAGEALTTEXT>
    </ITEM>
    <ITEM>
        <THUMBNAILIMAGE>image11.jpg</THUMBNAILIMAGE>
        <IMAGEALTTEXT>Alt text 11</IMAGEALTTEXT>
    </ITEM>
    <ITEM>
        <THUMBNAILIMAGE>image12.jpg</THUMBNAILIMAGE>
        <IMAGEALTTEXT>Alt text 12</IMAGEALTTEXT>
    </ITEM>
    <ITEM>
        <THUMBNAILIMAGE>image13.jpg</THUMBNAILIMAGE>
        <IMAGEALTTEXT>Alt text 13</IMAGEALTTEXT>
    </ITEM>
    <ITEM>
        <THUMBNAILIMAGE>image14.jpg</THUMBNAILIMAGE>
        <IMAGEALTTEXT>Alt text 14</IMAGEALTTEXT>
    </ITEM>
    <ITEM>
        <THUMBNAILIMAGE>image15.jpg</THUMBNAILIMAGE>
        <IMAGEALTTEXT>Alt text 15</IMAGEALTTEXT>
    </ITEM>
    <ITEM>
        <THUMBNAILIMAGE>image16.jpg</THUMBNAILIMAGE>
        <IMAGEALTTEXT>Alt text 16</IMAGEALTTEXT>
    </ITEM>
    <ITEM>
        <THUMBNAILIMAGE>image17.jpg</THUMBNAILIMAGE>
        <IMAGEALTTEXT>Alt text 17</IMAGEALTTEXT>
    </ITEM>
    <ITEM>
        <THUMBNAILIMAGE>image18.jpg</THUMBNAILIMAGE>
        <IMAGEALTTEXT>Alt text 18</IMAGEALTTEXT>
    </ITEM>
    <ITEM>
        <THUMBNAILIMAGE>image19.jpg</THUMBNAILIMAGE>
        <IMAGEALTTEXT>Alt text 19</IMAGEALTTEXT>
    </ITEM>
    <ITEM>
        <THUMBNAILIMAGE>image20.jpg</THUMBNAILIMAGE>
        <IMAGEALTTEXT>Alt text 20</IMAGEALTTEXT>
    </ITEM>
</DATASET>

the wanted, correct result is produced:

<div style="background-position: -0px -0px;">
   <img src="image1.jpg" alt="Alt text 1"/>
</div>
<div style="background-position: -125px -0px;">
   <img src="image2.jpg" alt="Alt text 2"/>
</div>
<div style="background-position: -250px -0px;">
   <img src="image3.jpg" alt="Alt text 3"/>
</div>
<div style="background-position: -375px -0px;">
   <img src="image4.jpg" alt="Alt text 4"/>
</div>
<div style="background-position: -500px -0px;">
   <img src="image5.jpg" alt="Alt text 5"/>
</div>
<div style="background-position: -0px -125px;">
   <img src="image6.jpg" alt="Alt text 6"/>
</div>
<div style="background-position: -125px -125px;">
   <img src="image7.jpg" alt="Alt text 7"/>
</div>
<div style="background-position: -250px -125px;">
   <img src="image8.jpg" alt="Alt text 8"/>
</div>
<div style="background-position: -375px -125px;">
   <img src="image9.jpg" alt="Alt text 9"/>
</div>
<div style="background-position: -500px -125px;">
   <img src="image10.jpg" alt="Alt text 10"/>
</div>
<div style="background-position: -0px -250px;">
   <img src="image11.jpg" alt="Alt text 11"/>
</div>
<div style="background-position: -125px -250px;">
   <img src="image12.jpg" alt="Alt text 12"/>
</div>
<div style="background-position: -250px -250px;">
   <img src="image13.jpg" alt="Alt text 13"/>
</div>
<div style="background-position: -375px -250px;">
   <img src="image14.jpg" alt="Alt text 14"/>
</div>
<div style="background-position: -500px -250px;">
   <img src="image15.jpg" alt="Alt text 15"/>
</div>
<div style="background-position: -0px -375px;">
   <img src="image16.jpg" alt="Alt text 16"/>
</div>
<div style="background-position: -125px -375px;">
   <img src="image17.jpg" alt="Alt text 17"/>
</div>
<div style="background-position: -250px -375px;">
   <img src="image18.jpg" alt="Alt text 18"/>
</div>
<div style="background-position: -375px -375px;">
   <img src="image19.jpg" alt="Alt text 19"/>
</div>
<div style="background-position: -500px -375px;">
   <img src="image20.jpg" alt="Alt text 20"/>
</div>

Explanation:

  1. This is a recursive solution. A non-recursive solution (aka the Piez method) is possible if it is guaranteed that the number of iterations doesn't exceed the total number of available nodes (in the source XML documen(s) and the XSLT stylesheet).

  2. Stop condition: The current row-number value becomes greater than the parameter-specified number of rows ($pRow > $pNumRows).

  3. Action: Generate the necessary new div element, then get the values for the column-number and row-number with which to perform the next action.