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 »
304 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 »
10,640 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 »
10,640 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
InVision App - Prototyping Made Beautiful With Prototyping Tools Ben Nadel's Company - Epicenter Consulting Recent Blog Comments
Feb 10, 2012 at 7:21 PM
jQuery AJAX Strips Script Tags And Inserts Them After Parent-Most Elements
Update! Instead of $(eval(options.insertAfter)).after(data['insertData']); I now use: var ajaxNode = document.createElement('span'); var parent = $(eval(options.insertAfter))[0].parentNode; ... read »
Feb 10, 2012 at 6:18 PM
jQuery AJAX Strips Script Tags And Inserts Them After Parent-Most Elements
encountered this same, what I consider, jQuery bug last week. I'm building a site in which I load some content via AJAX. This content contains Linkedin share button placeholders which Linkedin API ne ... read »
Feb 10, 2012 at 11:30 AM
Cross-Origin Resource Sharing (CORS) AJAX Requests Between jQuery And Node.js
After you understand the concepts here, this is an awesome cheatsheet for enabling CORS in just about anything http://enable-cors.org/ ... read »
JM
Feb 10, 2012 at 9:10 AM
My Safari Browser SQLite Database Hello World Example
@Amy, Here is a very good tutorial on how to use JOIN: http://www.sqltutorial.org/sqljoin-innerjoin.aspx ... read »
Feb 10, 2012 at 4:42 AM
Building A Twitter-Inspired RESTful API Architecture In ColdFusion
This is great, very useful Ben. I spotted a small typo in the api.cgm listing: <cfthrow type="Unauthroized" /> Cheers Stefan ... read »
Feb 9, 2012 at 10:35 PM
CFDirectory Filtering Uses Pipe Character For Multiple Filters (Thanks Steve Withington)
I was wondering if there would be a filter you could apply so that you got everything but what you included in the filter. As in show me all docs that are not a .pdf. ... read »
Feb 9, 2012 at 10:29 PM
Learning ColdFusion 9: Application-Specific Data Sources
@Ben, No offence, but if people were really wanting advanced features they would be using a platform like ASP.NET MVC. CFML is so structurally compromised as a tag-based scripting language that ... read »
Feb 9, 2012 at 10:03 PM
Subversion - Cleanup Failed To Process The Following Paths
@Leviaguirre, do you still have problems with this? ... read »