REListFind() ColdFusion User Defined Function

Posted August 1, 2007 at 9:37 AM by Ben Nadel

Tags: ColdFusion

I was standing in the deli this morning, checking out the girl next to me, when suddenly it occurred to me: ColdFusion custom tag list! How come there are no list functions that use regular expressions. That sounds confusing, let me explain. ColdFusion has this function, GetBaseTagList(). This will return a list of all parent tags that are open at the point of the GetBaseTagList() function call. To demonstrate:

  • <!---
  • Wrap the following pieces of code in a ColdFusion custom
  • tag (that doesn't do anything). We just want the tag name
  • to show up in the base tag list.
  • --->
  • <cf_customtag>
  •  
  • <!---
  • Create a list using CFSaveContent. We are doing this
  • just so that we can have different tags show up in the
  • base tag list.
  • --->
  • <cfsavecontent variable="lstTags">
  •  
  • <cfoutput>
  • #GetBaseTagList()#
  • </cfoutput>
  •  
  • </cfsavecontent>
  •  
  • </cf_customtag>
  •  
  •  
  • <!--- Output the base tag list. --->
  • <p>
  • #lstTags#
  • </p>

Notice that the GetBaseTagList() is wrapped inside of a ColdFusion CFOutput tag, a CFSaveContent tag, a ColdFusion custom tag (, and then another CFOutput not shown). At this point, the GetBaseTagList() gives us this output:

CFOUTPUT,CFSAVECONTENT,CF_CUSTOMTAG,CFOUTPUT

Notice that the third tag, our ColdFusion custom tag, has a different naming convention; its name comes back with "CF_". All ColdFusion custom tags work that way. That's when it occurred to me - why not have a ListFind() function that uses a regular expression rather than a literal match?!? That way, we could find the index of the first custom tag without having to know what the tag name actually was.

But why stop there? Why not also make the delimiter a potential regular expression? And of course, since our item match is not a literal match, why not give people the option to return both the index AND the matched item? This will be like some super awesome combination of REFind(), ListFind() and ColdFusion 8's REMatch():

  • <cffunction
  • name="REListFind"
  • access="public"
  • returntype="any"
  • output="false"
  • hint="Finds the first index of the item that matches the given regular expression (returns zero if not found).">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="List"
  • type="string"
  • required="true"
  • hint="The delimited list we are searching."
  • />
  •  
  • <cfargument
  • name="RegEx"
  • type="string"
  • required="true"
  • hint="The regular expression pattern that we are searching for."
  • />
  •  
  • <cfargument
  • name="Delimiter"
  • type="string"
  • required="false"
  • default=","
  • hint="The set of characters that will be used to delimit the list items."
  • />
  •  
  • <cfargument
  • name="RegExDelimiter"
  • type="boolean"
  • required="false"
  • default="false"
  • hint="A flag to determine wether the list delimiter is a regular expression or a just a character set."
  • />
  •  
  • <cfargument
  • name="ReturnItem"
  • type="boolean"
  • required="false"
  • default="false"
  • hint="A flag to determine wether the item should be return along with the index (as part of a structure containing Pos and Item)"
  • />
  •  
  • <!--- Define the local scope. --->
  • <cfset var LOCAL = StructNew() />
  •  
  •  
  • <!---
  • Create a default return structure (we might not need
  • it but just create a default structure).
  • --->
  • <cfset LOCAL.Return = StructNew() />
  • <cfset LOCAL.Pos = 0 />
  • <cfset LOCAL.Item = "" />
  •  
  • <!---
  • Check to see if our delimiter is a regular expression
  • or not. Either way, we are going to convert the list
  • to an array, but if it is a regular expression delmiter,
  • we will use Java's String::Split() method as opposed to
  • ColdFusion's ListToArray().
  • --->
  • <cfif ARGUMENTS.RegExDelimiter>
  •  
  • <!---
  • We are using a regular expression delimiter, so
  • split using Java split.
  • --->
  • <cfset LOCAL.Items = ARGUMENTS.List.Split(
  • ARGUMENTS.Delimiter
  • ) />
  •  
  • <cfelse>
  •  
  • <!---
  • We are using a character set of delimiters. Just
  • convert to array using ColdFusion.
  • --->
  • <cfset LOCAL.Items = ListToArray(
  • ARGUMENTS.List,
  • ARGUMENTS.Delimiter
  • ) />
  •  
  • </cfif>
  •  
  •  
  • <!---
  • ASSERT: At this point, regardless of how we split the
  • given list, we now have an array of list items. Be
  • careful! It might not be a "Real" ColdFusion array, bit
  • we can iterate over both objects like they are arrays.
  • --->
  •  
  •  
  • <!--- Loop over items array. --->
  • <cfloop
  • index="LOCAL.ItemIndex"
  • from="1"
  • to="#ArrayLen( LOCAL.Items )#"
  • step="1">
  •  
  • <!--- Check for a regular expression match. --->
  • <cfif REFind(
  • ARGUMENTS.RegEx,
  • LOCAL.Items[ LOCAL.ItemIndex ]
  • )>
  •  
  • <!---
  • We found a list item match. Return the index
  • of the list list item (or, the return structure
  • if required).
  • --->
  • <cfif ARGUMENTS.ReturnItem>
  •  
  • <!---
  • Since we are returning the item structure,
  • set the values and then return.
  • --->
  • <cfset LOCAL.Return.Pos = LOCAL.ItemIndex />
  • <cfset LOCAL.Return.Item = LOCAL.Items[ LOCAL.ItemIndex ] />
  •  
  • <!--- Return struct. --->
  • <cfreturn LOCAL.Return />
  •  
  • <cfelse>
  •  
  • <!--- Just return the index. --->
  • <cfreturn LOCAL.ItemIndex />
  •  
  • </cfif>
  •  
  • </cfif>
  •  
  • </cfloop>
  •  
  •  
  • <!---
  • If we made it this far then we didn't find the
  • matching list item so return zero (or, the structure
  • with item and pos if required).
  • --->
  • <cfif ARGUMENTS.ReturnItem>
  •  
  • <!--- Return item structure. --->
  • <cfreturn LOCAL.Return />
  •  
  • <cfelse>
  •  
  • <!--- Just return index. --->
  • <cfreturn 0 />
  •  
  • </cfif>
  • </cffunction>

The first three arguments, List, RegEx, and Delimiter are just like the standard ListFind() arguments (except for our "item" is a regular expression, not a literal match). The last two arguments, neither of which are required, give us some cool functionality. RegExDelimiter allows us to flag the use of a regular expression as the delimiter rather than the standard character set. ReturnItem allows us to flag the return of a structure containing both the position of the match and the matched item itself, rather than just the index.

Let's give it a test. Using the ColdFusion base tag list from above, let's get the index of the first ColdFusion custom tag as denoted by any item that starts with "cf_":

  • <!--- Find the first occurance of a custom tag. --->
  • #REListFind( lstTags, "(?i)^cf_" )#

This gives us the following output:

3

This, of course, is absolutely correct. CF_CUSTOMTAG is the third element in the list. Notice that our regular expression starts with a (?i). This is the case-INsensitive regular expression flag. Using this methodology stops me from having to create a redundant REListFindNoCase() tag. I am OK with because I feel that anyone using this kind of Find will probably also be comfortable using a case-insensitive expression.

Ok, but 3 doesn't really help us too much if we are trying to figure out what the closest ColdFusion custom tag is. So now, let's run the same thing, but this time, let's get back both the index and the matched item. To do this, we can use either ordered arguments with ALL the arguments, or named arguments, only passing in the ones we need. I chose named arguments cause it's smaller:

  • <!---
  • Find both the index and that value of the closest
  • ColdFusion custom tag in the list.
  • --->
  • <cfset objMatch = REListFind(
  • List = lstTags,
  • RegEx = "(?i)^cf_",
  • ReturnItem = true
  • ) />
  •  
  • <!--- Dump out the returned structure. --->
  • <cfdump
  • var="#objMatch#"
  • label="REListFind( CF_ )"
  • />

Running this, we get the following CFDump output:


 
 
 

 
REListFind() Gets Both Index And Match Of ColdFusion Custom Tag  
 
 
 

Now, we get both the position of the match, 3, and the item that was matched, CF_CUSTOMTAG. Very Cool!

But wait, there's more! The function, REListFind(), also gives us the ability to use a regular expression delimiter. This is not as awesome as the stuff above, but I thought I would throw it in there. Let's take a quick look at how this can be used:

  • <!---
  • Build a list of items using various white space
  • as delimiters (returns, new lines, tabs).
  • --->
  • <cfsavecontent variable="lstGirls">
  • Maria Bellow
  • Christina Cox
  • Julia Stiles
  • Julia Ormond
  • Minnie Driver
  • </cfsavecontent>
  •  
  •  
  • <!---
  • Find the first julia. For this list, we will be using
  • the new line, carriage return, and tabs as delimiters.
  • --->
  • <cfset objMatch = REListFind(
  • List = lstGirls,
  • RegEx = "(?i)^julia",
  • ReturnItem = true,
  • Delimiter = "[\r\n\t]+",
  • RegExDelimiter = true
  • ) />
  •  
  • <!--- Dump out the returned structure. --->
  • <cfdump
  • var="#objMatch#"
  • label="REListFind( julia )"
  • />

Running this, we get the following CFDump output:


 
 
 

 
REListFind() Returns Both The Index And Item Of The Match For Julia  
 
 
 

Notice that here, we are using [\r\n\t]+ as are delimiter. Of course, we could have done this with a standard delimiter of "#Chr( 13 )##Chr( 10 )##Chr( 9 )#", but come on! That looks no good! The regular expression delimiter gives us some cool flexibility, I just didn't provide the best example.



Reader Comments

Aug 1, 2007 at 11:01 AM // reply »
321 Comments

Submit to CFLib.org please.

(I like that phrase - its like Submit to Zod please, or die)


Aug 1, 2007 at 11:26 AM // reply »
11,314 Comments

OK cool - this might have to be the first UDF I submit :) I have gone to submit before, and each time, it talks about cleaning up the file and stuff and I always have to scrap it (cause i am work billing clients ;))... but, at lunch, I might just go ahead and do this :)


Aug 1, 2007 at 1:29 PM // reply »
95 Comments

Nice work! This will be useful. Good to know that (?i) means case insensitive. It does make me wonder why I can't just use "/i" though (like most other languages) and while on that point, why not stick to the regex syntax of /blah/i.

Anyway, unrelated thought, how the heck do you go from checking out the girl next to you to custom tags list?! Was the girl wearing a CF shirt or something.


Aug 1, 2007 at 1:35 PM // reply »
11,314 Comments

@Boyan,

I have learned not to question the way my brain works. My roommate in college use to give the "Non sequitur" of the day award all the time, cause the connections I make in my head follow no rational pattern at times :)

As far as the whole /regex/ syntax, I know Javascript can do that, but it always confuses me. I see that first "/" and it's like my brain just shuts down. Can't seem to read past it. That's why I always have to use new RegExp() in Javascript.


Post A Comment

Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.

Please review the following issues:

Author Name:


Author Email:

Author Website:

Comment:

Supported HTML tags for formatting: <strong>bold</strong>   <em>italic</em>   <code>code</code>







  • Help Wanted - Find Your Next ColdFusion Job
Ben Nadel's Company - Epicenter Consulting Recent Blog Comments
Jun 19, 2013 at 2:01 PM
Experimenting With The Amazon Simple Storage Service (S3) API Using ColdFusion
I have coincidentally been beating my head against the S3 API for the last week or so. One big "gotcha" I had to work around was file names and paths containing spaces. Remember to URL Enco ... read »
Jun 19, 2013 at 1:27 PM
Using Slice(), Substring(), And Substr() In Javascript
very good article. By the way IE supports negative values in substr or slice in verson 10. ... read »
Jun 19, 2013 at 11:33 AM
Filter vs. ngHide With ngRepeat In AngularJS
In your assessment, is it correct to say that given a list of say 500 items its more performant to use the `ngHide` method over the `filter` method? ... read »
Jun 19, 2013 at 10:18 AM
ColdFusion Path Usage And Manipulation Overview
Anyone happen to know if the file created by getTempFile will be automatically removed at any point? Nothing mentioned in the docs, and restarting CF doesn't remove them, so it seems it needs manu ... read »
Jun 19, 2013 at 9:41 AM
Working With Inherited Collections In AngularJS
I actually just ran into this same situation with a demo I was putting together. Your implementation of multi-lvl $scope's > Mine :) ... read »
Jun 19, 2013 at 8:17 AM
My Experience With AngularJS - The Super-heroic JavaScript MVW Framework
@Prateek, to match a word or text you should use .toContain('word') that's a jasmine reference. website is : http://pivotal.github.io/jasmine/ ... read »
Jun 19, 2013 at 8:10 AM
My Experience With AngularJS - The Super-heroic JavaScript MVW Framework
Hi Guys, Actually i am doing e2e test of angular js of my project but i am not getting one thing that is how to press enter key through the test when my form is filled as i am not using a button but ... read »
Jun 18, 2013 at 9:20 PM
Mapping AngularJS Routes Onto URL Parameters And Client-Side Events
I couldn't find examples of passing multiple arguments using the when() routing statement so figured out through trial and error that you can pass multiple arguments using the following format: .whe ... read »
InVision App - Prototyping Made Beautiful With Prototyping Tools