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 »
319 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,238 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,238 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
May 17, 2013 at 7:42 PM
HashKeyCopier - An AngularJS Utility Class For Merging Cached And Live Data
Ben - thanks so much for posting these Angular articles and findings, they've been a huge help towards learning one of the more 'complex' JavaScript frameworks out there (IMO). I have been using Angu ... read »
May 16, 2013 at 5:01 PM
UPDATE: Parsing CSV Data Files In ColdFusion With csvToArray()
Your code was the closest thing I've found to obtaining some direction for converting ISO fields to values that CF can translate properly. Thank you for posting! ... read »
May 15, 2013 at 10:37 PM
Very Simple Pusher And ColdFusion Powered Chat
hi id making plz easy ... read »
May 15, 2013 at 6:07 PM
Making SOAP Web Service Requests With ColdFusion And CFHTTP
Ben, you once again saved my bacon at work. Thank you, thank you, thank you! ... read »
May 15, 2013 at 4:15 PM
What If All User Interface (UI) Data Came In Reports?
@Josh, Thanks! @Ben, I definitely recommend the David West book "Object Thinking" I've been quoting from. It goes deeply into the philosophy and history of OO programming. His breadth ... read »
May 15, 2013 at 11:36 AM
Ask Ben: Print Part Of A Web Page With jQuery
I found this helpfull when you need to keep (refresh) the original parent page after closing the iframe child print dialog (Hoping you're not using a form at this time so it won't submit again): On ... read »
May 14, 2013 at 7:13 PM
What If All User Interface (UI) Data Came In Reports?
@Jonah, If there's any books you'd recommend on the subject of domain modelling, I'd love to hear it. I just downloaded the free PDF of "Domain Driven Design Quickly". Figured I'd give it ... read »
May 14, 2013 at 6:57 PM
The UX Of Prototyping: Low-Fidelity Is The New High-Fidelity
@Phillip, I'm not sure I follow what you mean? Are you saying that you looked at the list of widgets provided by the jQuery UI and let that be your style guide? ... read »
InVision App - Prototyping Made Beautiful With Prototyping Tools