Update: Please see this post on my new blog, which includes a demo of the REMatch function:
REMatch (ColdFusion).
Following are some UDFs I wrote recently to make using regexes in ColdFusion a bit easier. The biggest deal here is my reMatch() function.
reMatch(), in its most basic usage, is similar to JavaScript's String.match() method. Compare getting the first number in a string using reMatch() vs. built-in ColdFusion functions:
reMatch():
<cfset num = reMatch("\d+", string) />
reReplace():
<cfset num = reReplace(string, "\D*(\d+).*", "\1") />
reFind():
<cfset matchInfo = reFind("\d+", string, 1, TRUE) />
<cfset num = mid(string, matchInfo.pos[1], matchInfo.len[1]) />
All of the above would return the same result, unless a number wasn't found in the string, in which case the reFind()-based method would throw an error since the mid() function would be passed a start value of 0. I think it's pretty clear from the above which approach is easiest to use for a situation like this.
Still, that's just the beginning of what reMatch() can do. Change the scope argument from the default of "ONE" to "ALL" (to follow the convention used by reReplace(), etc.), and the function will return an array of all matches. Finally, set the returnLenPos argument to TRUE and the function will return either a struct or array of structs (based on the value of scope) containing the len, pos, AND value of each match. This is very different from how the returnSubExpressions argument of reFind() works. When using returnSubExpressions, you get back a struct containing arrays of the len and pos (but not value) of each backreference from the first match.
Here's the code, with four additional UDFs (reMatchNoCase(), match(), matchNoCase(), and escapeReChars()) added for good measure:
<cffunction name="reMatch" output="FALSE">
<cfargument name="regEx" type="string" required="TRUE" />
<cfargument name="string" type="string" required="TRUE" />
<cfargument name="start" type="numeric" required="FALSE" default="1" />
<cfargument name="scope" type="string" required="FALSE" default="ONE" />
<cfargument name="returnLenPos" type="boolean" required="FALSE" default="FALSE" />
<cfargument name="caseSensitive" type="boolean" required="FALSE" default="TRUE" />
<cfset var thisMatch = "" />
<cfset var matchInfo = structNew() />
<cfset var matches = arrayNew(1) />
<cfset var timeout = now() />
<cfloop condition="TRUE">
<cfif caseSensitive>
<cfset thisMatch = reFind(regEx, string, start, TRUE) />
<cfelse>
<cfset thisMatch = reFindNoCase(regEx, string, start, TRUE) />
</cfif>
<cfif thisMatch.pos[1] EQ 0>
<cfbreak />
<cfelseif returnLenPos>
<cfset matchInfo.value = mid(string, thisMatch.pos[1], thisMatch.len[1]) />
<cfset matchInfo.len = thisMatch.len[1] />
<cfset matchInfo.pos = thisMatch.pos[1] />
<cfset arrayAppend(matches, matchInfo) />
<cfelse>
<cfset arrayAppend(matches, mid(string, thisMatch.pos[1], thisMatch.len[1])) />
</cfif>
<cfif scope IS "ONE">
<cfbreak />
<cfelseif thisMatch.pos[1] + thisMatch.len[1] GT start>
<cfset start = thisMatch.pos[1] + thisMatch.len[1] />
<cfelse>
<cfset start = start + 1 />
</cfif>
<cfif dateDiff("s", timeout, now()) GTE 20>
<cfthrow message="Processing too long. Optimize regular expression for better performance" />
</cfif>
</cfloop>
<cfif scope IS "ONE">
<cfparam name="matches[1]" default="" />
<cfreturn matches[1] />
<cfelse>
<cfreturn matches />
</cfif>
</cffunction>
<cffunction name="reMatchNoCase" output="FALSE">
<cfargument name="regEx" type="string" required="TRUE" />
<cfargument name="string" type="string" required="TRUE" />
<cfargument name="start" type="numeric" required="FALSE" default="1" />
<cfargument name="scope" type="string" required="FALSE" default="ONE" />
<cfargument name="returnLenPos" type="boolean" required="FALSE" default="FALSE" />
<cfreturn reMatch(regEx, string, start, scope, returnLenPos, FALSE) />
</cffunction>
<cffunction name="match" output="FALSE">
<cfargument name="substring" type="string" required="TRUE" />
<cfargument name="string" type="string" required="TRUE" />
<cfargument name="start" type="numeric" required="FALSE" default="1" />
<cfargument name="scope" type="string" required="FALSE" default="ONE" />
<cfargument name="returnLenPos" type="boolean" required="FALSE" default="FALSE" />
<cfreturn reMatch(escapeReChars(substring), string, start, scope, returnLenPos, TRUE) />
</cffunction>
<cffunction name="matchNoCase" output="FALSE">
<cfargument name="substring" type="string" required="TRUE" />
<cfargument name="string" type="string" required="TRUE" />
<cfargument name="start" type="numeric" required="FALSE" default="1" />
<cfargument name="scope" type="string" required="FALSE" default="ONE" />
<cfargument name="returnLenPos" type="boolean" required="FALSE" default="FALSE" />
<cfreturn reMatch(escapeReChars(substring), string, start, scope, returnLenPos, FALSE) />
</cffunction>
<cffunction name="escapeReChars" returntype="string" output="FALSE">
<cfargument name="string" type="string" required="TRUE" />
<cfreturn reReplace(string, "[.*+?^${}()|[\]\\]", "\\\0", "ALL") />
</cffunction>
Now that I've got a deeply featured match function, all I need Adobe to add to ColdFusion in the way to regex support is lookbehinds, atomic groups, possessive quantifiers, conditionals, balancing groups, etc., etc. :-)