Search

I posted earlier on the Overture21.com site, and added it here, just-in-case it could be of use to anyone. Please let me know your thoughts. If you know a more efficient and better way to code this for Symphony 2 or at all, please let me know. I'm still really new to XSLT game and want to get better.

PROBLEM

I've been trying to figure out a way to randomly display one photo out of a group of images via XSLT every time I refresh my browser.

MY ATTEMPT AT A SOLUTION

After much thought, I came across this solution. It uses the generate-id() function and a little magic from the mod operator.

NOTE: The generate-id function will return a string like id2115558 when using libXSLT, but for you PHP4 folks that are still using Sablotron (which doesn't apply to the Symphony 2 crowd), when using generate-id() you will return a string like i__9022976_0.

Here's my example XML (which is based off of Mark Lewis's Flickr Campfire service XML // I manually edited the XML for this example to give proper acknowledgement to the Flickr photographers by adding the flickrauthor attribute to each photo node // The photos are buildings from my former University by Flickr users euthman and monochromechicken)...

<?xml version='1.0' encoding='utf-8'?>
<data>
<flickr>
    <photos>
        <photo flickrauthor="monochromechicken">
            <title>Southern Methodist University in Dallas, Texas - Laura Lee Blanton Building</title>
            <date>2007-11-05</date>
            <url>static.flickr.com/2180/2250489626_6d1950ee67_</url>
        </photo>
        <photo flickrauthor="monochromechicken">
            <title>Southern Methodist University in Dallas, Texas - </title>
            <date>2007-11-05</date>
            <url>static.flickr.com/2405/2249691903_ea15523240_</url>
        </photo>
        <photo flickrauthor="euthman">
            <title>Southern Methodist University in Dallas, Texas - Dallas Hall / Dedman College Entrance</title>
            <date>2007-08-30</date>
            <url>static.flickr.com/2301/2344932369_105b601511_</url>
        </photo>
        <photo flickrauthor="euthman">
            <title>Southern Methodist University in Dallas, Texas - Paul B. Loyd, All-Sports Center</title>
            <date>2007-08-30</date>
            <url>static.flickr.com/2238/2344929221_cc08b5edbe_</url>
        </photo>
        <photo flickrauthor="euthman">
            <title>Southern Methodist University in Dallas, Texas - Meadows Museum</title>
            <date>2007-08-30</date>
            <url>static.flickr.com/3088/2345749914_c0f7c142e1_</url>
        </photo>
        <photo flickrauthor="euthman">
            <title>Southern Methodist University in Dallas, Texas - Fondren Science Building</title>
            <date>2007-08-30</date>
            <url>static.flickr.com/2129/2345785026_bcecd5d112_</url>
        </photo>
        <photo flickrauthor="euthman">
            <title>Southern Methodist University in Dallas, Texas - Umphrey Lee Cenotaph</title>
            <date>2006-12-29</date>
            <url>static.flickr.com/2370/1879801672_f60eae6852_</url>
        </photo>
    </photos>
</flickr>

Here's my XSLT (example based off of the libXSLT processor)...

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
    <xsl:output method="xml" 
                            doctype-public="-//W3C//DTD XHTML 1.0 Strict//EN" 
                            doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd" 
                            omit-xml-declaration="yes" 
                            encoding="UTF-8" 
                            indent="yes"/>
    <xsl:template match="/">
        <!-- This variable counts the number of photo nodes -->
        <xsl:variable name="count-flickr" select="count(/data/flickr/photos/photo)"/>

        <!-- This variable is removing the 'id' from the generated id 
             and only selecting the number in the string -->
        <xsl:variable name="new-id" select="substring-after(generate-id(),'id')"/>

        <!-- This is the magic variable we are taking the $new-id variable
           and using the mod (or modulo) operator, which finds the 
           remainder of division of one number by another. Also, I'm 
           adding one, so my result doesn't return 0. -->
        <xsl:variable name="randomnumber" select="($new-id mod $count-flickr) + 1"/>

        <!-- I'm using apply-templates on the photo template below filtering
           off of the variable, $randomnumber, which will return a whole number. 
           That whole number, for instance, 1, will serve as a position filter. So 
           if 1 is the number that is genrated then it will return the first photo
           node in the XML -->
        <xsl:apply-templates select="/data/flickr/photos/photo[$randomnumber]"/>
    </xsl:template>

    <xsl:template match="photo">
        <!-- building a parameter to pull the location of the photo -->
        <xsl:param name="mphoto" select="substring(url,1,44)"/>
        <h2>
            <xsl:value-of select="title"/>
        </h2>
        <img src="http://{$mphoto}.jpg" alt="{title}"/>
    </xsl:template>
</xsl:stylesheet>

It's late and I did not read and understand all of your code, but couldn't you just create a datasource with the sort order (new and lovely in Symphony 2) random?

You could use the built-in "random" sort if your data was stored in a Symphony Section. But if your data source is pulling dynamic XML (as the Flickr example may well be), you don't have the ability to query your data prior to including the XML.

Could you have used the random module from EXSLT?

I'm not 100% sure I follow the code either. Can you give an example of what the generate-id() function returns each time? And this random number, is it unique each time the sequence is run?

Nick, you're still up!

generate-id() will return something like id2115558. The number will be different for every transformation process.

The only guarantee generate-id() provides (and hence why it's useful in other situations) is that it'll be unique for each element within the context of each process. It's mainly used for generating dynamic in-document (#) anchors, but this is a clever use of the function!

Nice! I'll have to try this out.

I was wanting to pull my LastFM feed and display the top albums I've listened to in the last week, but that feed doesn't have album art info in it. The next best feed with album art would be the top 50 albums in the last 3 months, but then it wouldn't change very often because I'd have to limit my output to the first 10 albums or so. This little trick will let me pick a random sampling from the big list and be different every time, which is way more fun than seeing the few albums I tend to always listen to. :-)

Oops ... I see this only returns 1 random number. Is there a way to randomly grab a few photos instead of just 1?

Oops ... I see this only returns 1 random number. Is there a way to randomly grab a few photos instead of just 1?

There's no reason you can't utilize a predicate and position() to get additional nodes from the random starting node. It is probably better to go about it this way so you do not get duplicate random nodes.

There's no reason you can't utilize a predicate and position() to get additional nodes from the random starting node. It is probably better to go about it this way so you do not get duplicate random nodes.

I tried that with the random position plus 9 following siblings. The problem I see is if the random position is less than 9 from the last, I won't get the following siblings. I know I could set the total number parameter to 9 less than the actual total, but I don't think that's very random.

Is there a way to add the generate id as an attribute to each, say 'photo' node, and then do a sort on that attribute before outputting? I tried it but I think the xsl:attribute command only works at the end of a transformation and not in the middle of it.

The problem I see is if the random position is less than 9 from the last, I won't get the following siblings. I know I could set the total number parameter to 9 less than the actual total, but I don't think that's very random.

Good point. I'll have to think about this some more.

So what is this new for Symphony 2 sort via random?

Nevermind, I thought it was new code that S2 was utilising, but I realise now it was an option in the DS. I'm happy now.

I have two additional methods of randomly sorting nodes, useful if you can't use the Random sort order directly from a data source.

1. Generate array of random numbers and write into XML

    protected function __trigger() {

        $result = new XMLElement('random');

        $numbers = range(1, 50);
        shuffle($numbers);          
        foreach ($numbers as $number) {
            if ($number != 0 && $number != "") {
                $node = new XMLElement('number', $number);
                $result->appendChild($node);
            }
        }
        return $result;

    }

Generates the following XML in my page:

<data>
    <events>
    <random>
        <number>17</number>
        <number>34</number>
        <number>15</number>
        <number>24</number>
        <number>22</number>
        <number>10</number>
        <number>38</number>
        <number>16</number>
        ...

And using this:

<xsl:for-each select="/data/events/random/number">
    <xsl:apply-templates select="/data/other-section/entry[position()=number(current()/text())]" />
</xsl:for-each>

Thw downside is that this is a very inefficient method. My example writes in 50 "number" nodes on the offchance that you might need to sort 50 entries. Not particularly elegant. Therefore a more robust solution:

2. Use EXSLT math:random function to generate a random number

Create an EXSLT nodeset of all entries, but add an attribute to each with a random number. Then sort by this attribute. A full example:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet 
    version="1.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:exsl="http://exslt.org/common"
    xmlns:math="http://exslt.org/math"
    extension-element-prefixes="exsl" >

<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>

<xsl:template match="data">

    <xsl:variable name="entries">
            <xsl:for-each select="/data/my-section/entry">
                <entry-container>
                    <xsl:attribute name="random"><xsl:value-of select="floor(math:random() * 10)" /></xsl:attribute>
                    <xsl:copy-of select="." />
                </entry-container>
            </xsl:for-each>
    </xsl:variable>

    <xsl:for-each select="exsl:node-set($entries)/entry-container">
        <xsl:sort select="@random" order="ascending" data-type="number" />      
        <!-- apply-templates here -->
    </xsl:for-each>

</xsl:template>

</xsl:stylesheet>

Create an account or sign in to comment.

Symphony • Open Source XSLT CMS

Server Requirements

  • PHP 5.3-5.6 or 7.0-7.3
  • PHP's LibXML module, with the XSLT extension enabled (--with-xsl)
  • MySQL 5.5 or above
  • An Apache or Litespeed webserver
  • Apache's mod_rewrite module or equivalent

Compatible Hosts

Sign in

Login details