Copied to clipboard

Flag this post as spam?

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


  • Sebastiaan Janssen 5060 posts 15522 karma points MVP admin hq
    Nov 02, 2010 @ 16:26
    Sebastiaan Janssen
    0

    Turning a set of nodes (with doubles) into a unique collection

    So I have a bit of a challange:

    I have nodes that have a treeMultiPicker on them (stores the picked nodes comma seperated: 12,28,22). The picked nodes are for tagging an article.

    When I go to that article I need a list of other articles that are tagged with at least ONE of the current article's tags.

    What I've got so far:

        <xsl:variable name="keywords" select="$currentPage/data [@alias = 'keywordsPicker']"/>
        <xsl:variable name="keywordsNodes" select="umbraco.library:Split($keywords, ',')"/>
    
        <xsl:variable name="keywordsArticlesString">
          <xsl:for-each select="$keywordsNodes/value">
            <xsl:variable name="keywordsNode" select="umbraco.library:GetXmlNodeById(.)"/>
            <xsl:copy-of select="$currentPage/ancestor-or-self::node//node [@nodeTypeAlias = 'Article' and contains(data [@alias = 'keywordsPicker'], $keywordsNode/@id)]"/>
          </xsl:for-each>
          <xsl:comment/>
        </xsl:variable>
    
        <xsl:variable name="keywordsArticleNodes" select="msxml:node-set($keywordsArticlesString)/node"/>
    

    Now I can loop over the "keywordsArticleNodes" just fine, however, there are double values in them, as an article could be linked to multiple keywords that are the same multiple keywords as on the original article.

    So, how do I make a unique collection out of this?

  • Ian Smedley 97 posts 192 karma points
    Nov 02, 2010 @ 16:36
    Ian Smedley
    1

    I'd write some c# to do it, stick it into a class in a DLL, and call it much like you do with umbraco.library.

    RemoveDuplicates() function..

  • Richard Soeteman 4046 posts 12899 karma points MVP 2x
    Nov 02, 2010 @ 16:37
    Richard Soeteman
    1

    Hi Sebastiaan,

    Isn't it better to use a simple XSLT Extension for this. then you can create a list and easily check if an item in the comma seperated list already is added to that list. Was looking if I still had some XSLT Extensions I used in the past to achieve the same goal, but couldn't find it sorry.

    Cheers,

    Richard

  • Jamie Howarth 306 posts 773 karma points c-trib
    Nov 02, 2010 @ 16:44
    Jamie Howarth
    5

    Hi Richard/Sebastiaan,

    Suggested this codeblock inside an XSLT extension (love LINQ):

     param.Split(new char[] {','}).Distinct();

    HTH,

    Benjamin

    EDIT: I thought of an extra bit that makes it useful in XSLT:

    umbraco.library.Split(string.Join(",", pattern.Split(new char[] { ',' }).Distinct().Cast<string>().ToArray()),",");

    IIRC umbraco.library.Split spits out elements around each item wrapped inside an XPathNodeIterator which gives it purpose in XSLT.

  • Lee Kelleher 4026 posts 15836 karma points MVP 13x admin c-trib
    Nov 02, 2010 @ 16:49
    Lee Kelleher
    1

    Awesome one-liner Benjamin!

    Old skool method:

    public static string GetUniqueValues(string csv)
    {
        string[] items = csv.Split(',');
        List<string> unique = new List<string>();
    
        foreach (string item in items)
        {
            if (!unique.Contains(item))
            {
                unique.Add(item);
            }
        }
    
        return string.Join(",", unique.ToArray());
    }

    Cheers, Lee.

  • Lee Kelleher 4026 posts 15836 karma points MVP 13x admin c-trib
    Nov 02, 2010 @ 16:56
    Lee Kelleher
    3

    It's also a shame that the "ExsltSets:distinct()" doesn't work correctly. (Maybe I shouldn't complain and write a patch? ;-P)

  • Christian Wendler 46 posts 155 karma points
    Nov 02, 2010 @ 18:38
    Christian Wendler
    4

    I'm using this to filter for distinct nodes in a home-brewed nodeset:

    <xsl:for-each select="$yournodeset/somenode[not(@name = preceding-sibling::somenode/@name)]">
      ...

    </xsl:for-each>

  • Sebastiaan Janssen 5060 posts 15522 karma points MVP admin hq
    Nov 02, 2010 @ 18:45
    Sebastiaan Janssen
    0

    Thanks guys for great answers!

    Went with Ben's 2nd approach, which returns a nice XPathNodeIterator, so I can easily use it in XSLT again without splitting, yay!

    Lee: Yes please.. ;-)

    Christian: Interesting approach, not sure I understand it yet, need to study it a bit closer!

  • Chriztian Steinmeier 2800 posts 8790 karma points MVP 8x admin c-trib
    Nov 03, 2010 @ 10:45
    Chriztian Steinmeier
    5

     

     

     

    Hi Sebastiaan,

    Here's my version of it - as you'd expect it's all XSLT (partly due to the fact that I'm a lousy .NET hack at best :-)

    And for the record: Benjamin's solution(s) are cool #h5yr

    <xsl:stylesheet
        version="1.0"
        xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        xmlns:umb="urn:umbraco.library"
        xmlns:make="urn:schemas-microsoft-com:xslt"
        exclude-result-prefixes="umb make"
    >
    
        <xsl:param name="currentPage" />
        <xsl:variable name="siteRoot" select="$currentPage/ancestor-or-self::node[@level = 1]" />
    
        <xsl:template match="/">
            <!-- Tokenize the keyword ids (could use exslt:tokenize() too) -->
            <xsl:variable name="keywords" select="umb:Split($currentPage/data[@alias = 'keywordsPicker'], ',')" />
    
            <!-- Create a variable with a copy of all nodes that have one of these keywords  -->
            <xsl:variable name="nodes">
                <xsl:for-each select="$keywords/value">
                    <xsl:copy-of select="$siteRoot//node[@nodeTypeAlias = 'Article'][contains(concat(',', data[@alias = 'keywordsPicker'], ','), concat(',', current(), ','))]" />
                </xsl:for-each>
            </xsl:variable>
            <xsl:variable name="nodeset" select="make:node-set($nodes)" /><!-- Create a nodeset from those -->
    
            <!-- Do stuff with nodes in the set that does not have a preceding sibling with that same id -->
            <xsl:apply-templates select="$nodeset/node[not(@id = preceding-sibling::node/@id)]" />
        </xsl:template>
    
        <!-- Template for Article items -->
        <xsl:template match="node[@nodeTypeAlias = 'Article']">
            <!-- What ever you need to do with them... -->
        </xsl:template>
    
    </xsl:stylesheet>
    

    Notes:

    - Uses the same technique Christian illustrated (if set:distinct() worked in the EXSLT library I'd probably go with that)

    - No need to call GetXmlNodeById() (You do that for apparently no reason, since you're only using the id, which you already have :-)

    - The contains() expression gets clumsy really fast because we need to safeguard against getting matches for a node with id="123" when keyword is "12" or "23"

     

    Cheers,

    Chriztian

     

     

     

  • Sebastiaan Janssen 5060 posts 15522 karma points MVP admin hq
    Nov 03, 2010 @ 18:29
    Sebastiaan Janssen
    0

    Awesomeness Chriztian! Wish I could change the solution vote to your solution, it's elegant and doesn't need an extra xslt extension.

    I really dig the umb and make shortcuts, going to use those more, handy!

    The GetXmlNodeById() was indeed an artifact left behind after some refactors.

    Currently the contains() is perfect, right? Although, I'm not sure if I'm reading this correctly.. Say we have a string:

    1242,1249,1240

    Then you try you contains() method. I see you're adding a comma before and after, so wouldn't asking for1242 and 1240 fail? Because they don't start or end with a comma?

  • Chriztian Steinmeier 2800 posts 8790 karma points MVP 8x admin c-trib
    Nov 03, 2010 @ 19:59
    Chriztian Steinmeier
    1

    Thrilled you like it :-)

    What's going on with that contains(), huh?

    Yes, we put a comma on both ends AND (the crucial part) we put a comma on both ends of the one we're looking for too, so we're actually looking for ",1240," or ",1242," inside the ",1242,1249,1240," string... makes sense? (It's a common trick and Benjamin's doin' it too, I can see :-)

    The "make" prefix has a cool benefit that will probably make more sense when I get around to finishing Part III of my "Entity Tricks Trilogy" :-)

    /Chriztian

  • Sebastiaan Janssen 5060 posts 15522 karma points MVP admin hq
    Nov 03, 2010 @ 20:04
    Sebastiaan Janssen
    0

    Obvious!! I'm tired and missed that one. Excellent little trick indeed! Thanks again!

  • Fredrik Esseen 610 posts 906 karma points
    Jun 28, 2013 @ 10:31
    Fredrik Esseen
    0

    Hi!

    Im trying to use this with Gareth Evans Sniper Systems Tags Control package. Ive adjusted the xsl to the new schema but I get duplicate nodes.

    For each tag the same node is inserted in the nodeset so I assume ive done some mistake in the conversion to the new schema?

    <xsl:stylesheet
    version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:umb="urn:umbraco.library"
    xmlns:make="urn:schemas-microsoft-com:xslt"
    exclude-result-prefixes="umb make"
    >
     
    <xsl:param name="currentPage" />
    <xsl:variable name="siteRoot" select="$currentPage/ancestor-or-self::* [@isDoc][@level = 1]" />
     
    <xsl:template match="/">
    <!-- Tokenize the keyword ids (could use exslt:tokenize() too) -->
    <xsl:variable name="tags" select="umb:Split($currentPage/tags, ',')" />
     
    <!-- Create a variable with a copy of all nodes that have one of these keywords -->
    <xsl:variable name="nodes">
    <xsl:for-each select="$tags/value">
    <xsl:copy-of select="$siteRoot//Artikel [@isDoc][contains(concat(',', tags, ','), concat(',', current(), ','))]" />
     
    </xsl:for-each>
    </xsl:variable>
    <xsl:variable name="nodeset" select="make:node-set($nodes)" /><!-- Create a nodeset from those -->
     
    <!-- Do stuff with nodes in the set that does not have a preceding sibling with that same id -->
    <xsl:apply-templates select="$nodeset/* [@isDoc][not(@id = preceding-sibling::node/@id)]" />
     
    </xsl:template>
     
    <!-- Template for Article items -->
    <xsl:template match="Artikel [@isDoc]">
    <xsl:if test="./tags !='' ">
    <xsl:value-of select="@nodeName"/>
    </xsl:if>
    </xsl:template>
     
    </xsl:stylesheet>
Please Sign in or register to post replies

Write your reply to:

Draft