|
|
 Rank: Enthusiast
Joined: 4/22/2008 Posts: 33 Location: Plymouth, UK
|
Just as I think I am starting to get to grips with this XSLT stuff I hit another stumbling block!
I need to get a variable number of child nodes that are chosen at random, the number required will be passed in via the macro as will the parent node which I have no problem with its actually returning the unique child nodes where I am getting stuck. I see there is plenty of posts around on returning random images but these all seem to be returning a single random node.
Your help as usual would be appreciated.
Simon
|
|
 Rank: Umbracoholic
Joined: 9/8/2006 Posts: 1,410 Location: KY, USA
|
If you've seen the posts on getting random images then it should be fairly simple to extend those to handle multiple random items (images or pages). I'd take the general approach of calling the random function until I got back a list of unique numbers. For each random number returned you'd want to check that it wasn't already in the list. When the list grows to contain as many numbers as you need, you then just use the position() call to pick out each image or node. Make sense? cheers, doug.
MVP 2007-2009 - Official Umbraco Trainer for North America - Percipient Studios
|
|
 Rank: Enthusiast
Joined: 4/22/2008 Posts: 33 Location: Plymouth, UK
|
Thanks Doug, I am ok up to the point off adding items to a list and how I achieve this in XSLT? Not sure if I am missing the obvious here and being particularly stupid!? In c# I can add items to an array and then loop through them afterwards checking they are unique but not sure in XSLT?
|
|
 Rank: Umbracoholic
Joined: 9/8/2006 Posts: 1,410 Location: KY, USA
|
You're right that this is more awkward to in xslt because an xsl:variable is really more like a constant... you can't change its value after you've set it. The way around that is to make a recursive xsl:template that passes in an xsl:with-param with the previous "list" value, building that up until you've got your full list. It'll take a dozen lines of xslt and is a worthwhile things to learn to do. But as you say, it's easy in c#. So... create an in-line c# function in your xslt to do this part for you. It isn't worth building a whole extension, and performance will still be great because xslt's are compiled. You can see examples of how to do this scattered all over the place. I use this technique for a few helper functions in XSLTsearch, for instance. cheers, doug.
MVP 2007-2009 - Official Umbraco Trainer for North America - Percipient Studios
|
|
 Rank: Enthusiast
Joined: 4/22/2008 Posts: 33 Location: Plymouth, UK
|
I have decided to go the XSLT route for my own education and have made some progress but I am getting some strange results. I have spent hours on this trying to get a working solution and think I can now not see the wood for the trees. Here is my proof of concept before I try and integrate it into the site in question: Code:<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE xsl:Stylesheet [ <!ENTITY nbsp " "> ]> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxml="urn:schemas-microsoft-com:xslt" xmlns:umbraco.library="urn:umbraco.library" xmlns:math="urn:schemas-hizi-nl:math" exclude-result-prefixes="msxml umbraco.library math">
<xsl:output method="xml" omit-xml-declaration="yes"/>
<xsl:param name="currentPage"/> <xsl:variable name="nodes" select="$currentPage/ancestor-or-self::node[@level=2]/descendant::node [@nodeTypeAlias = 'NewsEventsListPage']/node [string(data [@alias='umbracoNaviHide']) != '1'] [string(data [@alias = ' NewsFeatureSidebarImage']) != '']" />
<msxml:script language="JavaScript" implements-prefix="math"> function random(numDie,numMax,numMin){ if (numMin==null){numMin=1;} var sum=0; for (var index=0;index<numDie;index++){ sum+=Math.floor(Math.random()*(numMax-numMin) + numMin); } return "" + sum; } function floorme(numFloor){ return "" + Math.floor(numFloor); } </msxml:script>
<xsl:template match="/"> <xsl:call-template name="panels"> <xsl:with-param name="newnodes" select="$nodes"/> <xsl:with-param name="count" select="1"/> </xsl:call-template> </xsl:template>
<xsl:template name="panels"> <xsl:param name="newnodes" /> <xsl:param name="count"/>
<xsl:if test="$count <= 3">
<xsl:for-each select="$newnodes"> <xsl:if test="position() = math:random(1, count($newnodes), 1)"> <xsl:value-of select="$count"/> / <xsl:value-of select="@id"/><br /> <xsl:call-template name="panels"> <xsl:with-param name="newnodes" select="$newnodes [@id != @id]" /> <xsl:with-param name="count" select="$count+1" /> </xsl:call-template> </xsl:if> </xsl:for-each>
</xsl:if> </xsl:template>
</xsl:stylesheet> This returns some definitely random results as sometimes I get 1 node and sometimes and I get 2 and on other occasions I get none. If anyone can shed some light on my error I would appreciate it.
|
|
Rank: Devotee
Joined: 1/17/2007 Posts: 50 Location: Bergen, Norway
|
Sorry to be a bit off-topic regarding your question, but I'm looking into the same issue for displaying a set of random nodes and the "perfect" solution for solving this would be implementing the random:random-sequence from EXSLT (http://www.exslt.org/random/functions/random-sequence/index.html). Here you can get as many random numbers as needed and then use the position() function to move within the nodeset.
Haven't been able yet to work out how to implement this, but maybe someone else have been down that road already?
|
|
 Rank: Enthusiast
Joined: 4/22/2008 Posts: 33 Location: Plymouth, UK
|
Its on-topic Nikolas so don't worry :) I came across this during my search for a solution but I am also not sure how to implement a solution using it. I have been stuck on this for so long I am surprised it is not something that has come up before on here and if is has I have not been successful in tracking it down.
|
|
Rank: Devotee
Joined: 1/17/2007 Posts: 50 Location: Bergen, Norway
|
There's a working .NET version of EXSLT out there (http://www.codeplex.com/MVPXML) and implementing that was not too hard (on my project I had to use the .NET 1.1 version as my client is still running Umbraco 2.1.7). The following code will nearly(!) work as intended: Code:<xsl:for-each select="Mvp.Xml.Exslt.ExsltRandom:randomSequence($noNodesOut)"> <xsl:value-of select="floor(number(concat('0.',substring-after(.,',')))*$noNodesIn)+1"/> - <xsl:value-of select="."/> <xsl:if test="position() != last()"> <br/> </xsl:if> </xsl:for-each> $noNodesOut = The number of nodes you want to display. $noNodesIn = The total number of nodes to select from. From here on you'll just pick the first n nodes using the position() function. BUT it will not work uniquely as the random function returns a double which when multiplied by the number of nodes and then rounded etc. will sometimes give you the same value. Anyway, this might be a step in the right direction...I hope! ;)
|
|
Rank: Devotee
Joined: 1/17/2007 Posts: 50 Location: Bergen, Norway
|
Got it working by using a combination of the code provided in the last post and a looping template (as Doug mentioned). Let me know if you would like the code!
|
|
 Rank: Enthusiast
Joined: 4/22/2008 Posts: 33 Location: Plymouth, UK
|
nikolas wrote:Got it working by using a combination of the code provided in the last post and a looping template (as Doug mentioned). Let me know if you would like the code! I would certainly appreciate it as this is currently causing me to tear my hair out in order to get a working solution which is currently holding up a project I am working on.
|
|
Rank: Devotee
Joined: 1/17/2007 Posts: 50 Location: Bergen, Norway
|
Simon, Here's the part of my xslt-file you'll be needing. Hopefully it's without errors (I had to remove tons of code as the original file itself is really big). You will have to add the EXSLT .NET version to make it work. Remember to modify your xsltExtensions.xml file in the config folder: Code: <?xml version="1.0" encoding="utf-8" ?> <XsltExtensions> <ext assembly="/bin/Mvp.Xml" type="Mvp.Xml.Exslt.ExsltRandom" alias="Mvp.Xml.Exslt.ExsltRandom"/> </XsltExtensions>
Code:<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE xsl:Stylesheet [ <!ENTITY nbsp " "> ]> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxml="urn:schemas-microsoft-com:xslt" xmlns:umbraco.library="urn:umbraco.library" xmlns:Exslt.ExsltMath="urn:Exslt.ExsltMath" xmlns:Mvp.Xml.Exslt.ExsltRandom="urn:Mvp.Xml.Exslt.ExsltRandom" exclude-result-prefixes="msxml umbraco.library Exslt.ExsltMath Mvp.Xml.Exslt.ExsltRandom">
<!-- Ver. 1.03-080721 -->
<xsl:output method="xml" omit-xml-declaration="yes"/>
<xsl:param name="currentPage"/>
<xsl:variable name="noRandom"> <xsl:choose> <xsl:when test="/macro/noRandom != ''"> <xsl:value-of select="/macro/noRandom"/> </xsl:when> <xsl:otherwise> <xsl:text>12</xsl:text> </xsl:otherwise> </xsl:choose> </xsl:variable> <xsl:variable name="noRandomRecordsDivideBy"> <xsl:choose> <xsl:when test="/macro/noRandomRecordsDivideBy != ''"> <xsl:value-of select="/macro/noRandomRecordsDivideBy"/> </xsl:when> <xsl:otherwise> <xsl:text>4</xsl:text> </xsl:otherwise> </xsl:choose> </xsl:variable> <xsl:variable name="parentNode"> <xsl:choose> <xsl:when test="/macro/parentNode != ''"> <xsl:choose> <xsl:when test="/macro/parentNode > 0"> <xsl:value-of select="/macro/parentNode"/> </xsl:when> <xsl:otherwise> <xsl:value-of select="$currentPage/@id"/> </xsl:otherwise> </xsl:choose> </xsl:when> <xsl:otherwise> <xsl:value-of select="$currentPage/@id"/> </xsl:otherwise> </xsl:choose> </xsl:variable>
<xsl:template match="/"> <!-- Displays random records of a specific group of groups --> <ul class="m-lp-r"> <xsl:call-template name="randomNodes"/> </ul> </xsl:template>
<xsl:template name="randomNodes"> <!-- Check number of available nodes --> <xsl:variable name="noAvailableNodes" select="count(umbraco.library:GetXmlNodeById($parentNode)/descendant-or-self::node [string(data [@alias='umbracoNaviHide']) != '1' and @nodeTypeAlias = 'Product'])"/> <!-- Calculate number of nodes to display --> <!-- This is only done for listing n numbers of products side-by-side --> <xsl:variable name="noNodes"> <xsl:choose> <xsl:when test="number($noAvailableNodes) > number($noRandom)"> <xsl:value-of select="$noRandom"/> </xsl:when> <xsl:otherwise> <xsl:value-of select="number($noAvailableNodes)-(number($noAvailableNodes) mod number($noRandomRecordsDivideBy))"/> </xsl:otherwise> </xsl:choose> </xsl:variable> <!-- Start randomizing - we'll do this even though we'll sometimes be displaying all products due to positioning etc. --> <xsl:variable name="noRandom" select="floor(number(concat('0.',substring-after(Mvp.Xml.Exslt.ExsltRandom:randomSequence(1),',')))*$noAvailableNodes)+1"/> <xsl:call-template name="randomizeNodes"> <xsl:with-param name="i" select="1"/> <xsl:with-param name="i2" select="1"/> <xsl:with-param name="arrayNodes" select="concat(concat('pos',$noRandom),';')"/> <xsl:with-param name="noAvailableNodes" select="$noAvailableNodes"/> <xsl:with-param name="noNodes" select="$noNodes"/> <xsl:with-param name="noRandom" select="$noRandom"/> <xsl:with-param name="printNode" select="1"/> </xsl:call-template> </xsl:template>
<xsl:template name="randomizeNodes"> <xsl:param name="i"/> <xsl:param name="i2"/> <xsl:param name="arrayNodes"/> <xsl:param name="noAvailableNodes"/> <xsl:param name="noNodes"/> <xsl:param name="noRandom"/> <xsl:param name="printNode"/>
<!-- We'll check if we're printing the node pulled out of the hat last time --> <xsl:if test="$printNode=1"> <xsl:variable name="randomNode" select="umbraco.library:GetXmlNodeById($parentNode)/descendant-or-self::node [string(data [@alias='umbracoNaviHide']) != '1' and @nodeTypeAlias = 'Product']"/> <!-- Call template for listing nodes --> <xsl:call-template name="listNodes"> <xsl:with-param name="nodeID" select="$randomNode[$noRandom]/@id"/> <xsl:with-param name="nodeName" select="$randomNode[$noRandom]/@nodeName"/> <xsl:with-param name="nodeParent" select="$randomNode[$noRandom]/@parentID"/> <xsl:with-param name="nodeSummary" select="$randomNode[$noRandom]/data[@alias='bodyText']"/> </xsl:call-template> </xsl:if> <!-- As the randomSequence function() we're using will give you the same number over and over again we'll feed it with a different seed number each time --> <xsl:variable name="seed" select="$noRandom*$noRandom*$noRandom*$i"/> <!-- We'll make a new random number based on the seed --> <xsl:variable name="tempRandomPosition" select="floor(number(concat('0.',substring-after(Mvp.Xml.Exslt.ExsltRandom:randomSequence(1,$seed),',')))*$noAvailableNodes)+1"/> <!-- Now we'll check to see if the number already has been used - we'll surround the number starting with a "pos" and ending with a ";" to make sure we're not thinking the number 1 equals the number 10 etc. --> <xsl:variable name="tempArrayNodes"> <xsl:choose> <xsl:when test="contains($arrayNodes,concat(concat('pos',$tempRandomPosition),';')) = false"> <xsl:value-of select="concat($arrayNodes,concat(concat('pos',$tempRandomPosition),';'))"/> </xsl:when> <xsl:otherwise> <xsl:value-of select="$arrayNodes"/> </xsl:otherwise> </xsl:choose> </xsl:variable> <!-- We're always adding a number to i as to always have the seed number change --> <!-- The iNew is for counting how many random numbers we've actually made --> <xsl:variable name="iNew"> <xsl:choose> <xsl:when test="contains($arrayNodes,concat(concat('pos',$tempRandomPosition),';')) = false"> <xsl:value-of select="$i2+1"/> </xsl:when> <xsl:otherwise> <xsl:value-of select="$i2"/> </xsl:otherwise> </xsl:choose> </xsl:variable> <!-- We're giving the param value printNode a bool value telling it whether to print or not print next time --> <xsl:variable name="positionUnique"> <xsl:choose> <xsl:when test="contains($arrayNodes,concat(concat('pos',$tempRandomPosition),';')) = false"> <xsl:value-of select="1"/> </xsl:when> <xsl:otherwise> <xsl:value-of select="0"/> </xsl:otherwise> </xsl:choose> </xsl:variable> <!-- As long as we need more numbers we're doing it all over again --> <xsl:if test="$i2 < $noNodes"> <xsl:call-template name="randomizeNodes"> <xsl:with-param name="i" select="$i+1"/> <xsl:with-param name="i2" select="$iNew"/> <xsl:with-param name="arrayNodes" select="$tempArrayNodes"/> <xsl:with-param name="noAvailableNodes" select="$noAvailableNodes"/> <xsl:with-param name="noNodes" select="$noNodes"/> <xsl:with-param name="noRandom" select="$tempRandomPosition"/> <xsl:with-param name="printNode" select="$positionUnique"/> </xsl:call-template> </xsl:if> </xsl:template>
</xsl:stylesheet> The variables and code in general has not (yet) been polished, so be gentle! ;)
|
|
|
Guest |