Copied to clipboard

Flag this post as spam?

This post will be reported to the moderators as potential spam to be looked at


  • BenH 59 posts 199 karma points
    Dec 17, 2014 @ 18:31
    BenH
    0

    Filtering nodes based on multiple tags XSLT

    I'm using the tags datatype, URL parameters /?filter=a,b,c and XSLT to display the results. I'm using the example here but it returns nodes that match any of the tags instead of all of the tags.  

    If a user selects a and c, I still want to show nodes that contain those tags, even if they also contain b.  Or if they select a,b,c just show nodes that match all three.  

    Node structure is:

    Resource
    -Webinar
    -Node 
    -Node
    -Brochure
    -Node 
    -Node
    -Video
    -Node 
    -Node

    I just don't know enough XSLT to customize the logic enough.  I'm on Umbraco 6.1.6.  Any other info I need to provide?

    Here is the XSLT I'm working with: Thanks for any advice or assitance! 

    <xsl:param name="currentPage"/>
    
    <xsl:template match="/">
    
    <!-- Query-string in the browser -->
    <xsl:variable name="filter" select="umbraco.library:RequestQueryString('filter')"/>
    <!-- run a for-each to split the tags -->  
    <xsl:for-each select="umbraco.library:Split($filter, ',')/value">
    
    <!-- Save the values in a variable -->
    <xsl:variable name="ct" select="."/>
    
    <!-- Run a for-each to over the nodes we want to "search" --> 
    <!-- We will look for pages underneath node id 1279 - change this to fit your hierachy -->
    <xsl:for-each select="umbraco.library:GetXmlNodeById(1279)/descendant-or-self::*">    
    
    <!-- Check if our variable is not empty -->
    <xsl:if test="$ct != ''">  
    <!-- Check to if any of these pages contains the tags from our ct-variable -->
    <xsl:if test="contains(tags, $ct)">
    
    
    
        </xsl:if>
    </xsl:if>
    
    </xsl:for-each>
    
    </xsl:for-each>
    
    </xsl:template>
    
    </xsl:stylesheet>
  • Chriztian Steinmeier 2798 posts 8788 karma points MVP 7x admin c-trib
    Dec 17, 2014 @ 23:24
    Chriztian Steinmeier
    1

    Hi BenH,

    The fact that the tags are stored in a CSV list makes this a bit harder, but here's something to play with — hopefully you can understand what's going on:

    <!-- Grab the tags from QueryString -->
    <xsl:variable name="query" select="umbraco.library:RequestQueryString('tags')" />
    <!-- Create a nodeset from them -->
    <xsl:variable name="queryTags" select="umbraco.library:Split($query, ',')" />
    <!-- Get the number of tags -->
    <xsl:variable name="tagCount" select="count($queryTags/value)" />
    
    <!-- Grab and cache all the relevant nodes to test against, e.g., only include ones that are actually
         tagged with something. Be as specific as possible here to improve performance. -->
    <xsl:variable name="nodesToMatch" select="$currentPage//*[@isDoc][normalize-space(tags)]" />
    
    <xsl:template match="/">
        <p>
            <xsl:value-of select="concat('Tags: ', $query)" />
        </p>
    
        <h3>Nodes that match, using ANY</h3>
        <!-- This is pretty straightforward - a single XPath will do -->
        <p><xsl:apply-templates select="$nodesToMatch[umbraco.library:Split(tags, ',')/value = $queryTags/value]"  /></p>
    
        <h3>Nodes that match, using ALL</h3>
        <!--
            For this, we need to first collect the nodes that match, by building a
            temp variable which will look like this:
            <nodes>
                <tagged tag="xslt">
                    <nodeId>1234</nodeId>
                    <nodeId>2132</nodeId>
                </tagged>
                <tagged tag="basic">
                    <nodeId>4536</nodeId>
                    <nodeId>1655</nodeId>
                    <nodeId>2132</nodeId>
                </tagged>
            </nodes>
            If a node has ALL tags, it's present in all <tagged> groups,
            like the node "2132" in the above sample.
        -->
        <xsl:variable name="matchALLproxy">
            <nodes>
                <xsl:for-each select="$queryTags/value">
                    <xsl:variable name="currentTag" select="." />
                    <tagged tag="{$currentTag}">
                        <xsl:apply-templates select="$nodesToMatch[umbraco.library:Split(tags, ',')/value = $currentTag]" mode="collect" />
                    </tagged>
                </xsl:for-each>
            </nodes>
        </xsl:variable>
        <xsl:variable name="matchALL" select="msxml:node-set($matchALLproxy)/nodes" />
    
        <p>
            <!-- Go through all nodes that match at least one tag -->
            <xsl:for-each select="$nodesToMatch[@id = $matchALL/tagged/nodeId]">
                <xsl:variable name="id" select="@id" />
                <xsl:if test="count($matchALL/tagged[nodeId = $id]) = $tagCount">
                    <!-- This was tagged with all the tags so render it -->
                    <xsl:apply-templates select="." />
                </xsl:if>
            </xsl:for-each>
        </p>
    </xsl:template>
    
    <!-- Helper template to collect a node's id -->
    <xsl:template match="*" mode="collect">
        <nodeId><xsl:value-of select="@id" /></nodeId>
    </xsl:template>
    
    <!-- The generic template to render a matching document -->
    <xsl:template match="*[@isDoc]">
        <p>
            <xsl:value-of select="@nodeName" />
            <xsl:text /> [<xsl:value-of select="tags" />]<xsl:text />
        </p>
    </xsl:template>
    

    Let me know if you need more help,

    /Chriztian

  • BenH 59 posts 199 karma points
    Dec 17, 2014 @ 23:37
    BenH
    0

    I was hoping you would reply, I'll get this in and reply back!  Thank you! 

  • BenH 59 posts 199 karma points
    Dec 18, 2014 @ 15:21
    BenH
    0

    That works perfectly for what we are doing! You are awesome Chriztian, thank you very much.  I've got some work to do for our display of the returns but the core functionality is dead on.  Thank you so much! 

    By the way, we are only looping through less than 200 nodes, so this is pretty performant too.  I'm removing the ANY match and just leaving the ALL.  I'll post my final code including diplay in a few days in case anyone else can use it!

  • BenH 59 posts 199 karma points
    Dec 18, 2014 @ 18:24
    BenH
    0

    How would I compare the value of a nodes tags to the tagged tag="value" so that I could sort the results into groups based on each tag? So all nodes tagged with brochures would be grouped in one loop, all nodes tagged with videos would be grouped into another section and display like:

    Brochures:

    -node 1

    -node 2

    Video:

    -node3 

    -node 4

     

    Would you use an if statement in the render portion of this part? Or a choose? 

    <xsl:for-each select="$nodesToMatch[@id = $matchALL/tagged/nodeId]">
                <xsl:variable name="id" select="@id" />
                <xsl:if test="count($matchALL/tagged[nodeId = $id]) = $tagCount">
    
                    <!-- This was tagged with all the tags so render it -->
                    <xsl:apply-templates select="." />
                </xsl:if>
            </xsl:for-each>
  • Chriztian Steinmeier 2798 posts 8788 karma points MVP 7x admin c-trib
    Dec 18, 2014 @ 18:41
    Chriztian Steinmeier
    0

    I'm not sure that makes sense to me - you've performed a search for nodes matching ALL tags - you should get the same output regardless how you sort?

    /Chriztian

  • BenH 59 posts 199 karma points
    Dec 18, 2014 @ 19:07
    BenH
    0

    Sorry, I should give more context! 

    We are giving the user 3 dropdowns (product, topic, type) to filter by and we want to group the results by one of the choices in the type dropdown.  So the results that are returned will always have a type tag that is unique, ie brochure, or webinar, or tool.

    We want to display the results of the filtering based on those types. Because I user might not use the type filter and the results for the first 2 filters may include several types. 

    Brochures:

    -nodes returned containing "brochure" tag
    -nodes returned containing "brochure" tag

    Webinars

    -nodes returned containing "Webinar" tag

    -nodes returned containing "Webinar" tag

    Tools:

    -nodes returned containing "Tools" tag

    -nodes returned containing "Tools" tag

     

    Apologies if I'm not explaining this clearly!! 

  • BenH 59 posts 199 karma points
    Dec 18, 2014 @ 20:49
    BenH
    0

    I think I have it, I stuck the heading outside the $nodesToMatch for-each loop and then inside each one, added a test for a string value in the tags and then print those nodes. Probably not the best way to do it, but I'm on a tight deadline unfortunately. 

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE xsl:stylesheet [ <!ENTITY nbsp "&#x00A0;"> ]>
    <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.ExsltCommon="urn:Exslt.ExsltCommon" xmlns:Exslt.ExsltDatesAndTimes="urn:Exslt.ExsltDatesAndTimes" xmlns:Exslt.ExsltMath="urn:Exslt.ExsltMath" xmlns:Exslt.ExsltRegularExpressions="urn:Exslt.ExsltRegularExpressions" xmlns:Exslt.ExsltStrings="urn:Exslt.ExsltStrings" xmlns:Exslt.ExsltSets="urn:Exslt.ExsltSets" xmlns:Examine="urn:Examine" xmlns:applied="urn:applied" xmlns:umbraco.contour="urn:umbraco.contour" xmlns:ucomponents.cms="urn:ucomponents.cms" xmlns:ucomponents.dates="urn:ucomponents.dates" xmlns:ucomponents.email="urn:ucomponents.email" xmlns:ucomponents.io="urn:ucomponents.io" xmlns:ucomponents.media="urn:ucomponents.media" xmlns:ucomponents.members="urn:ucomponents.members" xmlns:ucomponents.nodes="urn:ucomponents.nodes" xmlns:ucomponents.random="urn:ucomponents.random" xmlns:ucomponents.request="urn:ucomponents.request" xmlns:ucomponents.search="urn:ucomponents.search" xmlns:ucomponents.strings="urn:ucomponents.strings" xmlns:ucomponents.urls="urn:ucomponents.urls" xmlns:ucomponents.xml="urn:ucomponents.xml" 
        exclude-result-prefixes="msxml umbraco.library Exslt.ExsltCommon Exslt.ExsltDatesAndTimes Exslt.ExsltMath Exslt.ExsltRegularExpressions Exslt.ExsltStrings Exslt.ExsltSets Examine applied umbraco.contour ucomponents.cms ucomponents.dates ucomponents.email ucomponents.io ucomponents.media ucomponents.members ucomponents.nodes ucomponents.random ucomponents.request ucomponents.search ucomponents.strings ucomponents.urls ucomponents.xml ">
    
    
    <xsl:output method="xml" omit-xml-declaration="yes"/>
    
    <xsl:param name="currentPage"/>
    
    <!-- Grab the tags from QueryString -->
    <xsl:variable name="query" select="umbraco.library:RequestQueryString('filter')" />
    
    <!-- Create a nodeset from them -->
    <xsl:variable name="queryTags" select="umbraco.library:Split($query, ',')" />
    
    <!-- Get the number of tags -->
    <xsl:variable name="tagCount" select="count($queryTags/value)" />
    
    <!-- Grab and cache all the relevant nodes to test against, e.g., only include ones that are actually
         tagged with something. Be as specific as possible here to improve performance. -->
    <xsl:variable name="nodesToMatch" select="$currentPage//*[@isDoc][normalize-space(tags)]" />
    
    
    <xsl:template match="/">
        <p>
            <xsl:value-of select="concat('Tags: ', $query)" />
        </p>
    
    <!--     <h3>Nodes that match, using ANY</h3>
         This is pretty straightforward - a single XPath will do 
        <p><xsl:apply-templates select="$nodesToMatch[umbraco.library:Split(tags, ',')/value = $queryTags/value]"  /></p> -->
    
        <!-- <h3>Nodes that match, using ALL</h3> -->
        <!--
            For this, we need to first collect the nodes that match, by building a
            temp variable which will look like this:
            <nodes>
                <tagged tag="xslt">
                    <nodeId>1234</nodeId>
                    <nodeId>2132</nodeId>
                </tagged>
                <tagged tag="basic">
                    <nodeId>4536</nodeId>
                    <nodeId>1655</nodeId>
                    <nodeId>2132</nodeId>
                </tagged>
            </nodes>
            If a node has ALL tags, it's present in all <tagged> groups,
            like the node "2132" in the above sample.
        -->
        <xsl:variable name="matchALLproxy">
            <nodes>
                <xsl:for-each select="$queryTags/value">
                    <xsl:variable name="currentTag" select="." />
                    <tagged tag="{$currentTag}">
                        <xsl:apply-templates select="$nodesToMatch[umbraco.library:Split(tags, ',')/value = $currentTag]" mode="collect" />
                    </tagged>
                </xsl:for-each>
            </nodes>
        </xsl:variable>
        <xsl:variable name="matchALL" select="msxml:node-set($matchALLproxy)/nodes" />
    
        <h3><xsl:text>Brochure</xsl:text></h3>
            <!-- Go through all nodes that match at least one tag -->
            <xsl:for-each select="$nodesToMatch[@id = $matchALL/tagged/nodeId]">            
                    <xsl:variable name="id" select="@id" />
                         <xsl:if test="count($matchALL/tagged[nodeId = $id]) = $tagCount">                          
                                <xsl:variable name="list" select="umbraco.library:Split(tags, ',')/value" />                           
                                    <xsl:if test="contains($list, 'Brochure')">                             
                                                <!-- This was tagged with all the tags so render it -->
                                                <xsl:apply-templates select="." />                                      
                                    </xsl:if>                            
    
    
                </xsl:if>
            </xsl:for-each>
    
    <h3><xsl:text>Webinar</xsl:text></h3>
            <!-- Go through all nodes that match at least one tag -->
            <xsl:for-each select="$nodesToMatch[@id = $matchALL/tagged/nodeId]">            
                    <xsl:variable name="id" select="@id" />
                         <xsl:if test="count($matchALL/tagged[nodeId = $id]) = $tagCount">                          
                                <xsl:variable name="list" select="umbraco.library:Split(tags, ',')/value" />                           
                                    <xsl:if test="contains($list, 'webinarsondemand')">                             
                                                <!-- This was tagged with all the tags so render it -->
                                                <xsl:apply-templates select="." />                                      
                                    </xsl:if>                            
    
    
                </xsl:if>
            </xsl:for-each>
    
    <h3><xsl:text>Demos</xsl:text></h3>
            <!-- Go through all nodes that match at least one tag -->
            <xsl:for-each select="$nodesToMatch[@id = $matchALL/tagged/nodeId]">            
                    <xsl:variable name="id" select="@id" />
                         <xsl:if test="count($matchALL/tagged[nodeId = $id]) = $tagCount">                          
                                <xsl:variable name="list" select="umbraco.library:Split(tags, ',')/value" />                           
                                    <xsl:if test="contains($list, 'Demos')">                             
                                                <!-- This was tagged with all the tags so render it -->
                                                <xsl:apply-templates select="." />                                      
                                    </xsl:if>                            
    
    
                </xsl:if>
            </xsl:for-each>
    
    </xsl:template>
    
    <!-- Helper template to collect a node's id -->
    <xsl:template match="*" mode="collect">
        <nodeId><xsl:value-of select="@id" /></nodeId>
    </xsl:template>
    
    <!-- The generic template to render a matching document -->
    <xsl:template match="*[@isDoc]">
    
    
    
           <p>
    
            <xsl:value-of select="@nodeName" />
            <xsl:text /> [<xsl:value-of select="tags" />]<xsl:text />
        </p>
    
    
    
            <!-- <xsl:value-of select="@nodeName" />
            <xsl:text /> [<xsl:value-of select="tags" />]<xsl:text /> -->
    
    </xsl:template>
    </xsl:stylesheet>
  • BenH 59 posts 199 karma points
    Jan 02, 2015 @ 16:03
    BenH
    0

    Chriztian, how could I go through the for-each loop only three times?  Say I have 10 nodes that match all but only want to display the first three?  

     

Please Sign in or register to post replies

Write your reply to:

Draft