REReplace ColdFusion Custom Tag - Another Regular Expression Experiment

Posted February 2, 2009 at 11:37 AM by Ben Nadel

Tags: ColdFusion

It's no secret - I love regular expressions! They are wicked awesome and make my programming life much easier in many ways. In fact, I love them so much that I've tried to come up with many ways to leverage them in ColdFusion loops. Over the weekend, I had another idea; I wanted to try an experiment with regular expressions, ColdFusion custom tags, and closures. This time, I wanted to try to pass an anonymous function body in the "generated content" of the ColdFusion custom tag, and then use that UDF to perform the regular expression replace on the captured groups.

  • <!--- Store content to replace. --->
  • <cfsavecontent variable="strText">
  •  
  • Have you ever been to www.bennadel.com? It's a wicked cool
  • site. If you haven't you might want to check out
  • http://www.bennadel.com/projects/; it has a list of neat
  • ColdFusion-based projects that Ben is working on.
  •  
  • </cfsavecontent>
  •  
  •  
  •  
  • <cf_rereplace
  • text="#strText#"
  • pattern="(?i)(http://)?(www\.[\w\-\./]+[\w|/])"
  • returnvariable="strText">
  •  
  • function( $0, $1, $2 ){
  • // Check to see if the first group was found (the HTTP)
  • // part of the URL. If not, then we need to add it to
  • // the HREF so that the link doesn't stay internal.
  • if (Len( $1 )){
  •  
  • return( "<a href=""#$0#"" target=""_blank"">#$0#</a>" );
  •  
  • } else {
  •  
  • return(
  • "<a href=""http://#$0#"" target=""_blank"">#$0#</a>"
  • );
  •  
  • }
  • }
  •  
  • </cf_rereplace>
  •  
  •  
  • <!--- Output updated content. --->
  • <cfoutput>
  • #HtmlEditFormat( strText )#
  • </cfoutput>

As you can see here, we are passing content text to the ColdFusion custom tag. Then, in the tag body, we are defining the function that will be used to replace the matched patterns. In this case, we are auto-linking URLs and adding "http://" when it was not provided (don't get distracted by the regular expression - that's not really the point). When we run this code, we get the following output:

Have you ever been to <a href="http://www.bennadel.com" target="_blank">www.bennadel.com</a>? It's a wicked cool site. If you haven't you might want to check out <a href="http://www.bennadel.com/projects/" target="_blank">http://www.bennadel.com/projects/</a>; it has a list of neat ColdFusion-based projects that Ben is working on.

Notice that in the first link, "www.bennadel.com", the A tag that we added includes the "http://" in its HREF attribute. This was done using the logic of the UDF defined in the ColdFusion custom tag body.

The way this works is that the body of the ColdFusion custom tag is actually wrapped in a CFSCRIPT tag, written to a temporary file, and then included into the tag context itself. I believe this is called a Closure (Sean Corfield??). This makes the code available to the inner scope of the custom tag where it is subsequently used to replace the matched patterns in the given text.

  • <!--- Check to see which tag mode we are running. --->
  • <cfif (THISTAG.ExecutionMode EQ "Start")>
  •  
  • <!--- Define attributes. --->
  • <cfparam name="ATTRIBUTES.Text" type="string" />
  • <cfparam name="ATTRIBUTES.Pattern" type="string" />
  • <cfparam name="ATTRIBUTES.ReturnVariable" type="variablename" />
  •  
  • <cfelse>
  •  
  • <!---
  • Create the content for the method. When we do this, we
  • have to replace the generic name of the function with
  • a name to be used internally to this tag.
  • --->
  • <cfsavecontent variable="strMethod">
  • <cfoutput>
  • #("<" & "cfscript>")#
  • #REReplaceNoCase(
  • THISTAG.GeneratedContent,
  • "function\s*\(",
  • "function $(",
  • "one"
  • )#
  • #("</" & "cfscript>")#
  • </cfoutput>
  • </cfsavecontent>
  •  
  • <!--- Get a temporary file. --->
  • <cfset strMethodFile = GetTempFile(
  • ExpandPath( "/" ),
  • "rereplace"
  • ) />
  •  
  • <!--- Write the method to disk. --->
  • <cffile
  • action="write"
  • file="#strMethodFile#"
  • output="#strMethod#"
  • />
  •  
  • <!--- Include the file. --->
  • <cfinclude template="/#GetFileFromPath( strMethodFile )#" />
  •  
  • <!---
  • Now that the file has been include and compiled in the
  • context of this tag, delete the temporary method file.
  • --->
  • <cffile
  • action="delete"
  • file="#strMethodFile#"
  • />
  •  
  •  
  • <!--- Create a Java pattern based on the given expression. --->
  • <cfset objPattern = CreateObject( "java", "java.util.regex.Pattern" ).Compile(
  • JavaCast( "string", ATTRIBUTES.Pattern )
  • ) />
  •  
  • <!--- Get the pattern matcher. --->
  • <cfset objMatcher = objPattern.Matcher(
  • JavaCast( "string", ATTRIBUTES.Text )
  • ) />
  •  
  • <!--- Create a string buffer to hold the updated text. --->
  • <cfset objBuffer = CreateObject( "java", "java.lang.StringBuffer" ).Init() />
  •  
  •  
  • <!---
  • Get the argument names for the temp method. Since we
  • are passing the arguments by name, we need to match
  • them up to the groups.
  • --->
  • <cfset arrParameters = GetMetaData( $ ).Parameters />
  •  
  •  
  • <!--- Loop over the matches. --->
  • <cfloop condition="#objMatcher.Find()#">
  •  
  • <!---
  • Build up the argument collection that will be passed
  • to the temp replace method.
  • --->
  • <cfset objArguments = {} />
  •  
  • <!--- Set the complete string match. --->
  • <cfset objArguments[ arrParameters[ 1 ].Name ] = objMatcher.Group() />
  •  
  • <!--- Add each group to arguments. --->
  • <cfloop
  • index="intGroup"
  • from="1"
  • to="#objMatcher.GroupCount()#"
  • step="1">
  •  
  • <!--- Set the argument. --->
  • <cfset objArguments[ arrParameters[ intGroup + 1 ].Name ] = objMatcher.Group( JavaCast( "int", intGroup ) ) />
  •  
  • <!---
  • Check to see if it was found. If not, then set as
  • empty string. We need to do this because there is
  • no way to say that the arguments are not required.
  • --->
  • <cfif NOT StructKeyExists( objArguments, arrParameters[ intGroup + 1 ].Name )>
  •  
  • <!---
  • Group was not matched. Send to ColdFusion temp
  • method as the empty string.
  • --->
  • <cfset objArguments[ arrParameters[ intGroup + 1 ].Name ] = "" />
  •  
  • </cfif>
  •  
  • </cfloop>
  •  
  •  
  • <!--- Get the new replace content. --->
  • <cfset strNewContent = $(
  • ArgumentCollection = objArguments
  • ) />
  •  
  •  
  • <!---
  • Now, append the new content to the buffer. This will
  • add not only the replacement content but also the
  • rest of the text since the last match. When we do
  • this, we have to be sure to escape back-references.
  • --->
  • <cfset objMatcher.AppendReplacement(
  • objBuffer,
  • REReplace( strNewContent, "([\$\\])", "\\\1", "all" )
  • ) />
  •  
  • </cfloop>
  •  
  •  
  • <!--- Add the rest of the unmatched content. --->
  • <cfset objMatcher.AppendTail( objBuffer ) />
  •  
  •  
  • <!---
  • Store the fully updated content in the return variable
  • of the CALLER scope.
  • --->
  • <cfset CALLER[ ATTRIBUTES.ReturnVariable ] = objBuffer.ToString() />
  •  
  • <!---
  • Reset the generated content. We don't want the body of
  • the tag to show the method details.
  • --->
  • <cfset THISTAG.GeneratedContent = "" />
  •  
  • </cfif>

As with many of experiments, I don't know if I would ever use it; but, I think it is provides a nice example of how and why it would be awesome to have inline-method definitions in ColdFusion.




Reader Comments

Feb 2, 2009 at 2:57 PM // reply »
1 Comments

Ben,

What is with the "$"?
<cfset arrParameters = GetMetaData( $ ).Parameters />


Feb 2, 2009 at 3:08 PM // reply »
11,314 Comments

@Ken,

In the END mode of the custom tag, I replace the "function(" with "function $(" before I write it to file. "$" becomes the name of the replace function that I use in the context of the tag. I suppose I should have come up with a better name for it :)


Feb 2, 2009 at 4:37 PM // reply »
76 Comments

Haha nice job - go regex


Feb 2, 2009 at 4:39 PM // reply »
8 Comments

Ben,

Very well done. This is quite impressive.


Feb 7, 2009 at 11:52 PM // reply »
57 Comments

You know, there's just nothing sexier than a guy that knows his Regex's. ;-)


Feb 8, 2009 at 12:20 PM // reply »
11,314 Comments

@Mary Jo,

That's what I keep trying to tell everyone! :)


Feb 16, 2009 at 4:59 PM // reply »
5 Comments

Ben,

Just curious if you can help me with this....

I have a user enter the following string: website "content approval" new into a text box and need to break it up so that it is 3 separate words eg:

1) website
2) content approval
3) new

I have tried using the following RegEx - "([^\\"]|\\.)*" but this only gets whats enclosed in the double quotes, how can I also select the other 2 words but excluding what I have already picked out within the double quotes?


Feb 19, 2009 at 10:11 AM // reply »
11,314 Comments

@Stuck,

Try looking at this:

http://www.bennadel.com/index.cfm?dax=blog:1504.view


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