Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at Scotch On The Rocks (SOTR) 2011 (Edinburgh) with:

REListFind() ColdFusion User Defined Function

By Ben Nadel on
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.

Tweet This Groovy post by @BenNadel - REListFind() ColdFusion User Defined Function Thanks my man — you rock the party that rocks the body!


Reader 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 :)

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.

@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.