Copied to clipboard

Flag this post as spam?

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


  • Nigel Wilson 945 posts 2077 karma points
    Jun 07, 2010 @ 20:40
    Nigel Wilson
    0

    Compare 2 comma separated strings

    I have modified XSLT search to include 4 sets of checkboxes as well as the textbox - see http://our.umbraco.org/forum/developers/xslt/9534-Modify-XSLT-Search-to-Read-External-XML-File

    My XSLT now completes a filter on the 4 sets of checboxes and then a filter on the search string.

    The problem I have is the sets of checkboxes are doing an "AND" filter rather than an "OR" filter. So as an example if you tick Company A and Company B in the Company set you will only get matches where employees work at both companies rather than either company. Ooops !

    My problem is similar to the following post, however I have not been able to get it to work.

    http://our.umbraco.org/forum/developers/xslt/7981-Matching-multiple-comma-separated-values

    My code is as follows:

    <xsl:template name="checkBoxMatches">

    <!-- temporary nodeset -->
    <xsl:param name="nodeSetOne"/>

    <!-- comma separated list of checkboxes -->
    <xsl:param name="selCheckboxes"/>

    <!-- the field to search within the XML file (<data alias='fieldToSearch'> -->
    <xsl:param name="fieldToSearch"/>

    <xsl:variable name="possibleNodes" select="$nodeSetOne [contains(umbraco.library:Split(data [@alias=$fieldToSearch],','), umbraco.library:Split($selCheckboxes,','))]" />

    <!-- return a list of the attribute @id's of the currently possible nodes as the final set of matched nodes -->
    <xsl:variable name="nodeIDListFilter">
    <xsl:text>;</xsl:text>

    <xsl:for-each select="$possibleNodes">
    <!-- @id for this node -->
    <xsl:value-of select="@id"/>
    <xsl:text>;</xsl:text>
    </xsl:for-each>
    </xsl:variable>

    <!-- return the actual list of id's -->
    <xsl:value-of select="$nodeIDListFilter"/>

    </xsl:template>

    Is this even possible ?

    The above template is called 4 times within the search so am trying to make it reuseable and for performance sakes hoping I can avoid looping / etc.

    Thanks

    Nigel

  • Josh Townson 67 posts 162 karma points
    Jun 08, 2010 @ 14:55
    Josh Townson
    0

    Can I suggest using the function Exslt.ExsltSets:hassamenode(node-set 1, node-set 2). You would need to turn your comma separated list into node-sets first, but that is simple with another exslt function Exslt.ExsltStrings:tokenize(string,delimiters).

    Not sure how it would exaclty work in your set up, I think something like

     <xsl:variable name="possibleNodes" select="$nodeSetOne [Exslt.ExsltSets:hassamenode(Exslt.ExsltStrings:tokenize($selCheckboxes,','),Exslt.ExsltStrings:tokenize(data[@alias=$fieldToSearch],',')) = true()]" />

    For performance, it might be worth turning the comma separated list of selected checkboxes into a node-set in a variable outside of the template for re-use.

  • Nigel Wilson 945 posts 2077 karma points
    Jun 09, 2010 @ 19:22
    Nigel Wilson
    0

    Hi Josh

    Sorry for the delay in responding.

    I have just tried your suggestion and at the moment it doesn't seem to be working.

    I am not getting an error but the script does not error so maybe that is a positive.

    I will do some testing / outputting of data and go from there.

    Thanks again

    Nigel

  • Josh Townson 67 posts 162 karma points
    Jun 10, 2010 @ 13:36
    Josh Townson
    0

    HI Nigel - I guess it could now be a problem with the actual nodes, rather than the content - the test will only match if the nodes are the same. The exslt tokenize calls its nodes tokens, and umbraco.library:Split (which does the same thing calls its nodes value so

    umbraco.library:Split('Coke,RedBull,Orange Juice,Beer',',')

    produces a node set like so:

    <values>
       <value>Coke</value>
       <value>RedBull</value>
       <value>Orange Juice</value>
       <value>Beer</value>
    </values>

    and to equivalent exslt function

    Exslt.ExsltStrings:Tokenize('Coke,RedBull,Orange Juice,Beer',',')

    produces a node set like so:

    <token>Coke</token>
    <token>RedBull</token>
    <token>Orange Juice</token>
    <token>Beer</token>

    So you should use the same tokenize function for doing both lists - or you need to do some extra work to make sure the node sets can be the same! It all depends on what your temporary nodeset looks like.

    /Josh

  • Josh Townson 67 posts 162 karma points
    Jun 10, 2010 @ 18:37
    Josh Townson
    0

    Nigel - just had another thought - is this a checkbox list in umbraco being compared to a string in a separate XML file? if so the checkbox list comes out as a list of ids, rather than a comma separated list of strings so:

    <xsl:value-of select="$selCheckboxes"/>

    probably produces something along the lines of

    1104,1105,1110,1134

    in which case you need to loop through them and use

    umbraco.library:GetPreValueAsString(1104);

    something like this to get a semi-colon separated list into a variable which you can manipulate

        <xsl:variable name="checkboxValues">
    <xsl:text>;</xsl:text>
    <xsl:for-each select="umbraco.library:Split(data [@alias=$fieldToSearch],',')//value">
    <xsl:value-of select="umbraco.library:GetPreValueAsString(./text())"/>
    <xsl:text>;</xsl:text>
    <xsl:for-each>
    <xsl:variable>
  • Nigel Wilson 945 posts 2077 karma points
    Jun 14, 2010 @ 20:26
    Nigel Wilson
    0

    Hi Josh

    Delayed response again - only just getting back onto this task now . . .

    Still no joy but keen to persevere.

    To clarify:

    My XML data is as follows:

    <node id="7713" parentID="-1" path="-1" level="1">
        <data alias="Name">Joe Bloggs</data>
        <data alias="EmployerID">7323</data>
         ...
    </node>

    The string being passed in from the checkboxes selected as part of the form is as follows:

    7323,1788

    So I am trying to determine if compare the EmployerID node contains one of the selected checkbox values.

    So from your posts are you indicating the the string of checkboxes is being treated differently by the XSLT ?

    I'll keep scratching . . .

    Cheers

    Nigel

  • Nigel Wilson 945 posts 2077 karma points
    Jun 14, 2010 @ 20:42
    Nigel Wilson
    0

    Just realised the above example was a bad one to quote - you can only ever have 1 Employer (at least for the purposes of what I am working on!)

    So a better example would be:

    <node id="7713" parentID="-1" path="-1" level="1">
       
    <data alias="Name">Joe Bloggs</data>
       
    <data alias="BannerGroups">3451,5612,1873</data>
         ...
    </node>

    And so the checkbox comma separated list of ID's needs to do a compare to the BannerGroups node data

    Cheers

    Nigel

  • Josh Townson 67 posts 162 karma points
    Jun 15, 2010 @ 02:16
    Josh Townson
    1

    Hi Nigel - turns out to be worse than I thought - the HasSameNode function of EXSLT is checking to see if it is the exact same node from the same node-set - rather than if it appears to be the same node from its content, so it is of no use to use whatsoever.

    Anyway - my best solution is to add in some javascript to the xml file, and use that (make sure to add the implements prefix to the namespace declarations, and to exclude the prefix from the output):

    <xsl:template name="checkBoxMatches">
    <xsl:param name="nodeSetOne"/>
    <xsl:param name="selCheckboxes"/>
    <xsl:param name="fieldToSearch"/>

    <xsl:variable name="possibleNodes" select="$nodeSetOne[bc:compareCommaStrings(string(./data[@alias=$fieldToSearch]),string($selCheckboxes)) = true()]"/>
    <xsl:variable name="nodeIDListFilter">
    <xsl:text>;</xsl:text>
    <xsl:for-each select="$evenYetPossibleNodes">
    <xsl:value-of select="@id"/>
    <xsl:text>;</xsl:text>
    </xsl:for-each>
    </xsl:variable>
    <xsl:value-of select="$nodeIDListFilter"/>
    </xsl:template>


    <msxml:script language="javascript" implements-prefix="bc">
    <![CDATA[
    function compareCommaStrings(s,t) {
    var sarr = s.split(',');
    var tarr = t.split(',');
    var match = false;
    for (var i=0; i<sarr.length;i++) {
    for (var j=0; j<tarr.length;j++) {
    if (tarr[j] == sarr[i]) {
    match = true;
    }
    }
    }
    return match;
    }
    ]]>
    </msxml:script>
  • Nigel Wilson 945 posts 2077 karma points
    Jun 17, 2010 @ 22:40
    Nigel Wilson
    0

    Hi Josh

    Wicked wicked wicked . . . It seems to be working perfectly !

    The only remaining issue I need to sort out is if there are no checkboxes ticked - I've no problems dealing with this aspect.

    Your assistance is greatly appreciated - a huge thanks for your help.

    Nigel

     

  • Nigel Wilson 945 posts 2077 karma points
    Jun 18, 2010 @ 22:55
    Nigel Wilson
    0

    OK - here is my amended code to get around the issue of no results being returned when no checkboxes are selected:

    <!-- Template to filter matches on checkbox selections -->
    <xsl:template name="checkBoxMatches">
    <xsl:param name="nodeSetOne"/>
    <xsl:param name="selCheckboxes"/>
    <xsl:param name="fieldToSearch"/>

    <xsl:variable name="nodeIDListFilter">
    <xsl:choose>
    <!-- 1 or more checkboxes have been selected -->
    <xsl:when test="string($selCheckboxes) != ''">
    <xsl:variable name="possibleNodes" select="$nodeSetOne[bc:compareCommaStrings(string(./data[@alias=$fieldToSearch]),string($selCheckboxes)) = true()]" />
    <xsl:text>;</xsl:text>
    <xsl:for-each select="$possibleNodes">
    <xsl:value-of select="@id"/>
    <xsl:text>;</xsl:text>
    </xsl:for-each>
    </xsl:when>
    <!-- No checkbxoes have been selected -->
    <xsl:otherwise>
    <xsl:text>;</xsl:text>
    <xsl:for-each select="$nodeSetOne">
    <xsl:value-of select="@id"/>
    <xsl:text>;</xsl:text>
    </xsl:for-each>
    </xsl:otherwise>
    </xsl:choose>
    </xsl:variable>

    <xsl:value-of select="$nodeIDListFilter"/>
    </xsl:template>


    I am going to have up to 5 sets of checkboxes on my search form and ideally the above template should not need to be called if no checkboxes are selected, but for now this is the only way I have been able to get it working.

    For reference purposes here is the code that calls the template - if anyone can suggest how to avoid calling the template when no checkboxes are selected ($selectedCompanies) I'd be very grateful.

    <xsl:template name="filterEmployees">
        <!-- Initial parameters -->
        <xsl:param name="searchTermList" />
        <xsl:param name="possibleNodes" />

        <!-- Filter on group companies -->
        <xsl:variable name="possibleCompanyMatches">
            <xsl:call-template name="checkBoxMatches">
                <xsl:with-param name="nodeSetOne" select="$possibleNodes"/>
                <xsl:with-param name="selCheckboxes" select="$selectedCompanies"/>
                <xsl:with-param name="fieldToSearch">EmployerID</xsl:with-param>
            </xsl:call-template>
        </xsl:variable>

        <!-- get the actual matching nodes as a nodeset -->
        <xsl:variable name="matchedNodesCompanyFilter" select="$possibleNodes[contains($possibleCompanyMatches, concat(';', concat(@id, ';')))]" />
    ...


    I have tried altering the above as follows but get an error message (Error parsing XSLT file: \xslt\XSLTEmployeeSearchMembers.xslt) and have not been able to figure out where the error is caused.

    <!-- Filter on group companies -->
    <xsl:variable name="matchedNodesCompanyFilter">
    <xsl:choose>       
    <xsl:when test="string($selectedCompanies) != ''">
            <xsl:variable name="possibleCompanyMatches">
                <xsl:call-template name="checkBoxMatches">
                  <xsl:with-param name="nodeSetOne" select="$possibleNodes"/>
                  <xsl:with-param name="selCheckboxes" select="$selectedCompanies"/>
                   <xsl:with-param name="fieldToSearch">EmployerID</xsl:with-param>
    </xsl:call-template>
    </xsl:variable>
    <xsl:value-of select="$possibleNodes[contains($possibleCompanyMatches, concat(';', concat(@id, ';')))]" />
    </xsl:when>

    <xsl:otherwise>
    <xsl:value-of select="$possibleNodes/descendant-or-self::node"/>
    </xsl:otherwise>
    </xsl:choose>
    </xsl:variable>

    Any suggestions ?

    Thanks

    Nigel

  • Josh Townson 67 posts 162 karma points
    Jun 18, 2010 @ 23:34
    Josh Townson
    0

    I'm not sure can can declare a variable inside a variable. I suggest the best thing to do is to adjust possibleCompanyMatches:

    <xsl:variable name="possibleCompanyMatches">
    <xsl:choose>
    <xsl:when test="string($selectedCompanies) != ''">
    <xsl:call-template name="checkBoxMatches">
    <xsl:with-param name="nodeSetOne" select="$possibleNodes"/>
    <xsl:with-param name="selCheckboxes" select="$selectedCompanies"/>
    <xsl:with-param name="fieldToSearch">EmployerID</xsl:with-param>
    </xsl:call-template>
    </xsl:when>
    <xsl:otherwise>
    <xsl:text>;</xsl:text>
    <xsl:for-each select="$possibleNodes">
    <xsl:value-of select="@id"/>
    <xsl:text>;</xsl:text>
    </xsl:for-each>
    </xsl:otherwise>
    </xsl:variable>
    <!-- get the actual matching nodes as a nodeset -->
    <xsl:variable name="matchedNodesCompanyFilter" select="$possibleNodes[contains($possibleCompanyMatches, concat(';', concat(@id, ';')))]" />

    Thats the way I did it for xslt search, and it doesn't seem too bad in terms of performance

  • Rob Watkins 369 posts 701 karma points
    Feb 14, 2011 @ 18:41
    Rob Watkins
    1

    Just found this post after coming a cropper on this...

    "HasSameNode function of EXSLT is checking to see if it is the exact same node from the same node-set - rather than if it appears to be the same node from its content, so it is of no use to use whatsoever"

    ..myself, while matching tags :o)

    I don't really like extension functions so here is a VERY QUICK AND DIRTY matching template. You could improve this in all manner of ways; probably using recursion to be able to quit the loop on the first match would be my first change, but the site this is for is very small, there will not be many tags to match and I'm on a deadline...

    <xsl:template name="tokensMatch">
      <xsl:param name="set1"/>
      <xsl:param name="set2"/>
      
      <xsl:for-each select="$set1">
        <xsl:variable name="v1" select="."/>
        
        <xsl:for-each select="$set2">
          <xsl:variable name="v2" select="."/>      
          <xsl:if test="string($v1) = string($v2)">X</xsl:if>
        </xsl:for-each>
      </xsl:for-each>
    </xsl:template>

    Use with the split function like so:

    <xsl:template name="displayTaggedItems">
      <!-- Tags we are searching for - this would be passed in in the actual macro -->
      <xsl:param name="tags" select="Exslt.ExsltStrings:split($currentPage/tagsToMatchAgainst, ',')" /> 
      
      <ul class="portfolio_examples">
        <xsl:for-each select="umbraco.library:GetXmlAll()/yourTaggedDocumentType">
          <xsl:variable name="item" select="."/>
          
          <!-- Tags on current matched document -->
          <xsl:variable name="itemTags" select="Exslt.ExsltStrings:split($item/tags, ',')" />
          
          <!-- Run match against node sets from split() -->
          <xsl:variable name="isMatch">
            <xsl:call-template name="tokensMatch">
              <xsl:with-param name="set1" select="$tags"/>
              <xsl:with-param name="set2" select="$itemTags"/>
            </xsl:call-template>
          </xsl:variable>
          
          <!--
             tokensMatch template will return a string with an X for each tag matched -
             so an empty string is no match, any non-empty string a match
          -->
          <xsl:if test="string($isMatch) != ''">
            <xsl:call-template name="displayItem">
              <xsl:with-param name="item" select="$item"/>
            </xsl:call-template>
          </xsl:if>
        </xsl:for-each>
      </ul>
    </xsl:template>

    Hope someone finds this helpful!

  • Paul A 133 posts 368 karma points
    Oct 20, 2011 @ 13:43
    Paul A
    0

    I found it useful so thanks for posting!

    One problem I'm having though,,, When you get to displaying the matched nodes, how do you limit the number outputted? You can't use position() because that refers to position further up the logic - it might be 7, 12, 24, 29 ...

    Any ideas?

  • Rob Watkins 369 posts 701 karma points
    Oct 24, 2011 @ 11:31
    Rob Watkins
    0

    If I understand you correctly, you should be able to put the whole outer for-each loop in a variable, then do a for-each with position() on that - although I suspect you may need to use the node-set() function.

Please Sign in or register to post replies

Write your reply to:

Draft