Into:
Turns out it wasn't difficult at all - once I figured out how to work with CFBuilder's selection/editor support for extensions.
First - before going any further, if you haven't read about how to build extensions with CFBuilder, I'd suggest taking a look at the docs first. I'd also check out Simon Free's article on DevNet. I'm not going to cover the entire process, just the portions that handle working with text selection.
So the first thing we need is our ide_config.xml file. I've pasted it below:
2 <name>convertToCFScript</name>
3 <author>Raymond Camden</author>
4 <version>1</version>
5 <email>ray@camdenfamily.com</email>
6 <description>intro.html</description>
7 <license>license.html</license>
8
9 <menucontributions>
10 <contribution target="editor">
11 <menu name="Convert to CFSCRIPT">
12 <action name="Do It" handlerid="convert" showResponse="false"></action>
13 </menu>
14 </contribution>
15 </menucontributions>
16
17 <handlers>
18 <handler id="convert" type="CFM" filename="convert.cfm" />
19 </handlers>
20
21 </application>
My extension will have one menu contribution, and note that it has a target of editor. This tells CFB that my extension works within an opened file, and not the Navigator or RDS section. In this case, I'll have a new menu, Convert to CFScript, with one item, Do It. (FYI, why "Do It"? Currently you can't add an action to the root menu. You can also add a 'folder' with an action underneath it. Obviously thats a bit silly here. I've filed an ER to make this unnecessary.) The action runs my convert handler. Let's look at that now:
2 <cfset ideData=form.ideEventInfo>
3 <cfset data = xmlParse(ideData)>
4
5 <cfset fileName = data.event.ide.editor.file.xmlAttributes.location>
6 <cfset selectionStartLine = data.event.ide.editor.selection.xmlAttributes.startline>
7 <cfset selectionStartCol = data.event.ide.editor.selection.xmlAttributes.startcolumn>
8 <cfset selectionEndLine = data.event.ide.editor.selection.xmlAttributes.endline>
9 <cfset selectionEndCol = data.event.ide.editor.selection.xmlAttributes.startline>
10 <cfset selectionText = data.event.ide.editor.selection.text.xmlText>
11
12
13 <!--- handle cfsets --->
14 <cfset newText = rereplaceNoCase(selectionText, "<cfset[[:space:]]+(.*?)[[:space:]]*>", "\1;", "all")>
15
16 <cfsavecontent variable="test">
17 <cfoutput>
18 <response>
19 <ide>
20 <commands>
21 <command type="inserttext">
22 <params>
23 <param key="text"><![CDATA[#newText#]]></param>
24 </params>
25 </command>
26 </commands>
27 </ide>
28 </response>
29 </cfoutput>
30 </cfsavecontent>
31
32 <cfheader name="Content-Type" value="text/xml"><cfoutput>#test#</cfoutput>
So first off - when I began development on this little utility, I wasn't sure what the editor was going to send me. I did a bunch of cflogging and after looking at the XML, I saw that I was given:
- The filename (this includes the full path).
- The starting line and column of the selection. I've filed an ER to also include the starting numeric character position.
- The ending line and column of the selection.
- The actual selected text.
The actual meat of my logic is one regex. As you can see, I look for cfsets and simply replace them with a script equivalent. If I decide to keep working on this extension, I could look at supporting loops and other items, but for now I wanted to keep things simple. I'll also recommend doing what I did - testing my regex in another file. That kept CFBuilder out of the picture and let me fine tune my regex until it works like I wanted.
Finally - we need to tell the editor to replace the selection with my new text. I was stuck here for a while until I got help from Evelin Varghese of Adobe. Turns out there is a bug in the documentation. The docs say to use


Comment 1 written by Kevin Penny on 11 January 2010, at 9:53 AM
Comment 2 written by Raymond Camden on 11 January 2010, at 9:55 AM
<!--- aborts, first with no showerror --->
<cfset buffer = rereplaceNoCase(buffer, "<cfabort>", "abort;", "all")>
<!--- aborts, now w/ showerror --->
<cfset buffer = rereplaceNoCase(buffer, "<cfabort[[:space:]]+showerror[[:space:]]*=[[:space:]]*(.*?)[[:space:]]*>", "abort(showerror=\1);", "all")>
<!--- cfinclude --->
<cfset buffer = rereplaceNoCase(buffer, "<cfinclude[[:space:]]+template[[:space:]]*=[[:space:]]*""(.*?)""[[:space:]]*>", "include template=""\1"";", "all")>
Oh - those lines use "buffer" which come from my test script, not the extension. I'll add these in later today and update the zip if folks think it is useful.
Comment 3 written by Sam Farmer on 11 January 2010, at 9:58 AM
Comment 4 written by Gary Funk on 11 January 2010, at 10:36 AM
Comment 5 written by Robert Gatti on 11 January 2010, at 12:24 PM
Comment 6 written by Raymond Camden on 11 January 2010, at 12:31 PM
Comment 7 written by Akbarsait on 11 January 2010, at 12:32 PM
Comment 8 written by John C. Bland II on 11 January 2010, at 11:39 PM
BEFORE:
<cfset arguments.targetUrl = variables.issuesUrl & arguments.targetUrl />
<cfset arguments.targetUrl = rereplace(arguments.targetUrl, "{verb}", arguments.verb, "all") />
<cfreturn arguments.targetUrl />
AFTER:
arguments.targetUrl = variables.issuesUrl & arguments.targetUrl /;
arguments.targetUrl = rereplace(arguments.targetUrl, "{verb}", arguments.verb, "all") /;
<cfreturn arguments.targetUrl />
Notice the /; at the end and no cfreturn converted?
Also, it'd be sweet if you added a second option to wrap in cfscript or in the default option force a wrap. Something there would be nice.
Great idea and work though. I was wondering if someone was going to do this the other day. :-)
Comment 9 written by Peter Boughton on 12 January 2010, at 6:19 AM
However, as you have demonstrated, CFML does share many of the complexities of HTML (and indeed has many more besides), and - unless you're able to restrict yourself to a very strict subset - attempting to parse CFML solely with RegEx will lead to madness. There are just too many valid constructs that will trip you up.
Something as simple as this will trip it up:
<cfset HtmlCode = '<input type="text"/>' />
(And I can provide plenty more real-world examples)
Which isn't to say this might not be useful - for anyone insane enough to actually want CFScript - but it's important to make clear that a converter like this is at best an 80-90% job, and is likely to need human attention afterwards.
Comment 10 written by Raymond Camden on 12 January 2010, at 6:21 AM
Comment 11 written by Peter Boughton on 12 January 2010, at 6:56 AM
Which isn't to say I'm entirely opposed to CFScript, but using it primarily is insane.
Comment 12 written by Raymond Camden on 12 January 2010, at 6:57 AM
Comment 13 written by Marko Simic on 12 January 2010, at 7:40 AM
Good luck with converter. Just thinking of it I foreseen dozens of problems. Afraid to think what will pop up during development :) But, as such it's quite a challenge.
PS
cfscript ftw :)
Comment 14 written by Kevin Penny on 12 January 2010, at 9:05 AM
Maybe something you've thought of before, but I've created and used other cfml parsing engines that attempt to either reformat or generate cfml etc - What I've found seems to work pretty well at times is to treat a tag as a valid xml node - the perfect example might be the cfloop tag -
For example - if you put an ending /> at the end of a cfloop beginning block, you can surround the initial tag and simply parse it
i.e.
<cfloop from="1" to="#arraylen(x)#" index="i">
becomes
<cfloop from="1" to="#arraylen(x)#" index="i"/>
then surrounding becomes:
<tag>
<cfloop from="1" to="#arraylen(x)#" index="i"/>
</tag>
And voila, you have an easy way to parse 'most' cfloops in this fashion, ad can be as dynamic as you want.
Just some gas for the engine -
Comment 15 written by Raymond Camden on 12 January 2010, at 4:24 PM
[Add Comment] [Subscribe to Comments]