Web Page Syntax Highlighting for ColdFusion - Brush for SyntaxHighlighter
I have been working on a implementing a solution to highlight ColdFusion code snippets when they are being posted on your web site/blog. Highlighting the code according to the appropriate language can make it much easier to read and comprehend (and make it pretty of course). To do this, I have created a ColdFusion syntax "brush" for the JavaScript highlighter SyntaxHighlighter (formally dp.SyntaxHighlighter). In this context, a "brush" is a set of language specific settings for highlighting/prettifying code. SyntaxHighlighter is a pretty cool piece of software and I had some fun and faced some challenges while trying to create ColdFusion specific highlighting brush. Here is a demo of the highlighter at work when given ColdFusion code snippet:
<cfcomponent displayname="applicationController" extends="ModelGlue.unity.controller.Controller" output="false">
<cfscript>
// Set component variables
variables.appConfig = 0;
variables.modelGlueConfig = 0;
variables.rolePermissions = structnew();
variables.userIDWithAdminRights = "";
</cfscript>
<!---
Function: name="changeUserSiteRole"
Created on: 09.25.2007
Updated on: 09.25.2007
Author: Boyan Kostadinov
Arguments: event(ModelGlue.Core.Event)
Return Value: none
Description:
--->
<cffunction name="changeUserSiteRole" access="public" returntype="void" output="false">
<cfargument name="event" type="ModelGlue.Core.Event">
<cfset var userID = arguments.event.getValue("userID", 0) />
<cfset var siteID = arguments.event.getValue("siteID", 0) />
<cfset var roleID = arguments.event.getValue("roleID", 0) />
<cfset var showAllUsers = arguments.event.getValue("showAllUsers", false) />
<cfif userID neq 0 and siteID neq 0 and roleID neq 0>
<!--- Call the changeUserSiteRole method in the UsersGateway to change the user's
role in the specified siteID --->
<cfset variables.usersGateway.changeUserSiteRole(userID, siteID, roleID) />
</cfif>
<cfif showAllUsers>
<cfset arguments.event.addResult("allUsers") />
<cfelse>
<cfset arguments.event.addResult("singleUser") />
</cfif>
</cffunction>
<cffunction name="onQueueComplete" access="public" returntype="void" output="false">
<cfargument name="event" type="any">
<cfset arguments.event.setValue("defaultEvent", getModelGlue().getBean("modelGlueConfiguration").getdefaultEvent()) />
<cfset arguments.event.setValue("appConfig", variables.appConfig) />
<cfset arguments.event.setValue("ApplicationVersion", variables.appConfig.GetConfigSetting("ApplicationVersion")) />
<cfset arguments.event.setValue("ApplicationLastRevisionDate", variables.appConfig.GetConfigSetting("ApplicationLastRevisionDate")) />
<cfset arguments.event.setValue("urlPrefix", variables.defaultTemplate & "?" & arguments.event.getValue("eventValue") & "=") />
<cfset arguments.event.setValue("CurrentPageUrl", getfilefrompath(cgi.script_name) & "?" & cgi.query_string) />
</cffunction>
<cffunction name="onRequestEnd" access="public" returntype="void" output="false">
<cfargument name="event" type="any">
</cffunction>
</cfcomponent>
To install the SyntaxHighlighter on your page:
- Get it http://code.google.com/p/syntaxhighlighter/
- Extract it somewhere accessable from the web
- Add the following code
<!-- Include the SyntaxHighlighter stylesheet --> <style type="text/css" media="screen">@import url("pathToSyntaxHighlighterStyle/syntaxHighlighter.css");</style> <!-- Include the core SyntaxHighlighter library --> <script language="javascript" src="pathToSyntaxHighlighter/scripts/shCore.js"></script> <!-- Include the ColdFusion brush --> <script language="javascript" src="pathToSyntaxHighlighter/scripts/shBrushColdFusion.js"></script> <script language="javascript"> window.onload = function () { // Set the path to the flash component to enable 'copy to clipboard' in firefox dp.SyntaxHighlighter.ClipboardSwf = 'pathToSyntaxHighlighter/scripts/clipboard.swf'; // Enable blogger mode (if using Blogger) dp.SyntaxHighlighter.BloggerMode(); // Highlight page elements with the name "code" // For configuration options see http://code.google.com/p/syntaxhighlighter/wiki/HighlightAll dp.SyntaxHighlighter.HighlightAll('code', false, true, false, 1, false); } </script> - Modify the css for your preferences (Firebug is really handy here to figure out what classes you want to modify). Here are my changes:
<style type="text/css"> .dp-highlighter ol li, .dp-highlighter .columns div { padding: 0px 3px 0px 0px !important; font-size: 12px !important; font-family: 'Lucida Console', 'Bitstream Vera Sans Mono', 'Courier New', Monaco, Courier, monospace; } .dp-highlighter .tools { padding-left: 0px; } .dp-highlighter .tools a { color: #000; text-decoration: underline; } .dp-highlighter .tools a:hover { margin-right: 10px; } </style> - Add some more brushes. For supported languages are C++, C#, CSS, Delphi, Java, JavaScript, PHP, Pythod, Ruby, SQL, VB, XML, HTML, XLST and now ColdFusion :-). More information on the languages at http://code.google.com/p/syntaxhighlighter/wiki/Languages.Here are the brushes I am using:
<script language="javascript" src="scripts/shBrushCSharp.js"></script> <script language="javascript" src="scripts/shBrushCss.js"></script> <script language="javascript" src="scripts/shBrushJScript.js"></script> <script language="javascript" src="scripts/shBrushSql.js"></script> <script language="javascript" src="scripts/shBrushVb.js"></script> <script language="javascript" src="scripts/shBrushXml.js"></script>
- Post your code enclosed in "<pre>" tags as follows:
<!-- For xml/html/xslt code --> <pre class="xml" name="code"> <!-- For css code --> <pre class="css" name="code"> <!-- For coldfusion code --> <pre class="cf" name="code"> <!-- For sql code --> <pre class="sql" name="code">
- Highlights ColdFusion functions, tags, attributes, strings, numbers, comments, cfscript comments
- Highlights a very limited set of Model-Glue specific keywords (I got adventures)
- Uses the Dreamweaver color shema to apply the syntax highlighting
- To just use it (without modifications), just include the compressed version from the svn location at http://opensourceprojects.googlecode.com/svn/dpSyntaxHighlighterColdFusionBrush/trunk/compressed/shBrushColdFusion.js
- To use it but modify the css styles, get the uncompressed version from http://opensourceprojects.googlecode.com/svn/dpSyntaxHighlighterColdFusionBrush/trunk/uncompressed/shBrushColdFusion.js and modify the css definitions in "this.Style"
- Include it as a brush in your page:
<script language="javascript" src="pathTo_shBrushColdFusion.js"></script>
dp.sh.Brushes.ColdFusion = function()
{
this.CssClass = 'dp-coldfusion';
this.Style = '.dp-coldfusion { font: 13px "Courier New", Courier, monospace; }' +
'.dp-coldfusion .tag, .dp-coldfusion .tag-name { color: #990033; }' +
'.dp-coldfusion .attribute { color: #990033; }' +
'.dp-coldfusion .attribute-value { color: #0000FF; }' +
'.dp-coldfusion .cfcomments { background-color: #FFFF99; color: #000000; }' +
'.dp-coldfusion .cfscriptcomments { color: #999999; }' +
'.dp-coldfusion .keywords { color: #0000FF; }' +
'.dp-coldfusion .mgkeywords { color: #CC9900; }' +
'.dp-coldfusion .numbers { color: #ff0000; }' +
'.dp-coldfusion .strings { color: green; }';
this.mgKeywords = 'setvalue getvalue addresult viewcollection viewstate';
this.keywords = 'var eq neq gt gte lt lte not and or true false ' +
'abs acos addsoaprequestheader addsoapresponseheader ' +
'arrayappend arrayavg arrayclear arraydeleteat arrayinsertat ' +
'arrayisempty arraylen arraymax arraymin arraynew ' +
'arrayprepend arrayresize arrayset arraysort arraysum ' +
'arrayswap arraytolist asc asin atn binarydecode binaryencode ' +
'bitand bitmaskclear bitmaskread bitmaskset bitnot bitor bitshln ' +
'bitshrn bitxor ceiling charsetdecode charsetencode chr cjustify ' +
'compare comparenocase cos createdate createdatetime createobject ' +
'createobject createobject createobject createobject createodbcdate ' +
'createodbcdatetime createodbctime createtime createtimespan ' +
'createuuid dateadd datecompare dateconvert datediff dateformat ' +
'datepart day dayofweek dayofweekasstring dayofyear daysinmonth ' +
'daysinyear de decimalformat decrementvalue decrypt decryptbinary ' +
'deleteclientvariable directoryexists dollarformat duplicate encrypt ' +
'encryptbinary evaluate exp expandpath fileexists find findnocase ' +
'findoneof firstdayofmonth fix formatbasen generatesecretkey ' +
'getauthuser getbasetagdata getbasetaglist getbasetemplatepath ' +
'getclientvariableslist getcontextroot getcurrenttemplatepath ' +
'getdirectoryfrompath getencoding getexception getfilefrompath ' +
'getfunctionlist getgatewayhelper gethttprequestdata gethttptimestring ' +
'getk2serverdoccount getk2serverdoccountlimit getlocale ' +
'getlocaledisplayname getlocalhostip getmetadata getmetricdata ' +
'getpagecontext getprofilesections getprofilestring getsoaprequest ' +
'getsoaprequestheader getsoapresponse getsoapresponseheader ' +
'gettempdirectory gettempfile gettemplatepath gettickcount ' +
'gettimezoneinfo gettoken hash hour htmlcodeformat htmleditformat ' +
'iif incrementvalue inputbasen insert int isarray isbinary isboolean ' +
'iscustomfunction isdate isdebugmode isdefined isk2serverabroker ' +
'isk2serverdoccountexceeded isk2serveronline isleapyear islocalhost ' +
'isnumeric isnumericdate isobject isquery issimplevalue issoaprequest ' +
'isstruct isuserinrole isvalid isvalid isvalid iswddx isxml ' +
'isxmlattribute isxmldoc isxmlelem isxmlnode isxmlroot javacast ' +
'jsstringformat lcase left len listappend listchangedelims listcontains ' +
'listcontainsnocase listdeleteat listfind listfindnocase listfirst ' +
'listgetat listinsertat listlast listlen listprepend listqualify ' +
'listrest listsetat listsort listtoarray listvaluecount ' +
'listvaluecountnocase ljustify log log10 lscurrencyformat lsdateformat ' +
'lseurocurrencyformat lsiscurrency lsisdate lsisnumeric lsnumberformat ' +
'lsparsecurrency lsparsedatetime lsparseeurocurrency lsparsenumber ' +
'lstimeformat ltrim max mid min minute month monthasstring now ' +
'numberformat paragraphformat parameterexists parsedatetime pi ' +
'preservesinglequotes quarter queryaddcolumn queryaddrow querynew ' +
'querysetcell quotedvaluelist rand randomize randrange refind ' +
'refindnocase releasecomobject removechars repeatstring replace ' +
'replacelist replacenocase rereplace rereplacenocase reverse right ' +
'rjustify round rtrim second sendgatewaymessage setencoding ' +
'setlocale setprofilestring setvariable sgn sin spanexcluding ' +
'spanincluding sqr stripcr structappend structclear structcopy ' +
'structcount structdelete structfind structfindkey structfindvalue ' +
'structget structinsert structisempty structkeyarray structkeyexists ' +
'structkeylist structnew structsort structupdate tan timeformat ' +
'tobase64 tobinary toscript tostring trim ucase urldecode urlencodedformat ' +
'urlsessionformat val valuelist week wrap writeoutput xmlchildpos ' +
'xmlelemnew xmlformat xmlgetnodetype xmlnew xmlparse xmlsearch xmltransform ' +
'xmlvalidate year yesnoformat';
// Array to hold the possible string matches
this.stringMatches = new Array();
this.attributeMatches = new Array();
}
dp.sh.Brushes.ColdFusion.prototype = new dp.sh.Highlighter();
dp.sh.Brushes.ColdFusion.Aliases = ['coldfusion', 'cf'];
dp.sh.Brushes.ColdFusion.prototype.ProcessRegexList = function()
{
function push(array, value)
{
array[array.length] = value;
}
function find(array, element)
{
for(var i = 0; i < array.length; i++){
if(array[i] == element){
return i;
}
}
return -1;
}
var match = null;
var regex = null;
// Match numbers
// (\\d+)
this.GetMatches(new RegExp('\\b(\\d+)', 'gm'), 'numbers');
// Match mg keywords
this.GetMatches(new RegExp(this.GetKeywords(this.mgKeywords), 'igm'), 'mgkeywords');
// Match single line comments via the built in single line regex (for cfscript)
this.GetMatches(dp.sh.RegexLib.SingleLineCComments, 'cfscriptcomments');
// Match multi line comments via the built in multi line regex (for cfscript)
this.GetMatches(dp.sh.RegexLib.MultiLineCComments, 'cfscriptcomments');
// Match tag based comments (including multiline comments)
// (\<|<)!---[\\s\\S]*?---(\>|>)
this.GetMatches(new RegExp('(\<|<)!---[\\s\\S]*?---(\>|>)', 'gm'), 'cfcomments');
// Match attributes and their values excluding cfset tags
// (cfset\\s*)?([:\\w-\.]+)\\s*=\\s*(".*?"|\'.*?\')*
regex = new RegExp('(cfset\\s*)?([:\\w-\.]+)\\s*=\\s*(".*?"|\'.*?\')*', 'gm');
while((match = regex.exec(this.code)) != null)
{
// If there is match in element 1 (the tag is cfset), continute to the next match
if (match[1] != undefined && match[1] != '')
{
continue;
}
// Add the atribute to the matches only if it has a matching value (dbtype="query")
// and the match is not an empty string
if (match[3] != undefined && match[3] != '' && match[3] != '""' && match[3] != "''")
{
push(this.matches, new dp.sh.Match(match[2], match.index, 'attribute'));
push(this.matches, new dp.sh.Match(match[3], match.index + match[0].indexOf(match[3]), 'attribute-value'));
// Add the attribute value to the array of string matches
push(this.stringMatches, match[3]);
// Add the attribute to the array of attribute matches
push(this.attributeMatches, match[2]);
}
}
// Match opening and closing tag brackets
// (\<|<)/*\?*(?!\!)|/*\?*(\>|>)
this.GetMatches(new RegExp('(\<|<)/*\\?*(?!\\!)|/*\\?*(\>|>)', 'gm'), 'tag');
// Match tag names
// (\<|<)/*\?*\s*(\w+)
regex = new RegExp('(?:\<|<)/*\\?*\\s*([:\\w-\.]+)', 'gm');
while((match = regex.exec(this.code)) != null)
{
push(this.matches, new dp.sh.Match(match[1], match.index + match[0].indexOf(match[1]), 'tag-name'));
}
// Match keywords
regex = new RegExp(this.GetKeywords(this.keywords), 'igm');
while((match = regex.exec(this.code)) != null)
{
// if a match exists (there is a value for the attribute)
if (find(this.attributeMatches, match[0]) == -1)
{
push(this.matches, new dp.sh.Match(match[0], match.index, 'keywords'));
}
}
// Match cfset tags and quoated attributes
regex = new RegExp('cfset\\s*.*(".*?"|\'.*?\')', 'gm');
while((match = regex.exec(this.code)) != null)
{
// if a match exists (there is a value for the attribute)
if(match[1] != undefined && match[1] != '')
{
push(this.matches, new dp.sh.Match(match[1], match.index + match[0].indexOf(match[1]), 'strings'));
// Add the attribute to the array of string matches
push(this.stringMatches, match[1]);
}
}
// Match string enclosed in double quoats
while((match = dp.sh.RegexLib.DoubleQuotedString.exec(this.code)) != null)
{
//if (this.stringMatches.indexOf(match[0]) == -1)
if (find(this.stringMatches, match[0]) == -1)
push(this.matches, new dp.sh.Match(match[0], match.index, 'strings'));
}
// Match string enclosed in single quoats
while((match = dp.sh.RegexLib.SingleQuotedString.exec(this.code)) != null)
{
//if (this.stringMatches.indexOf(match[0]) == -1)
if (find(this.stringMatches, match[0]) == -1)
push(this.matches, new dp.sh.Match(match[0], match.index, 'strings'));
}
}
Questions/comments? You can leave a comment here or use my contact form to send me your thoughts.
5 Comments:
Now, that is neat! Cant wait to implement this on our documentation site and my blog! Kudos Boyan!
This is a very great brush! Thanks. Except, when I use it, the cfcomments seems not rendered properly.
Eg.
<[!]--- some comments --->
Becomes
<[!]--- some comments -->
(1 ending dash is missing)
And it is not wrapped with the cfcomments class (means not highlighted in yellow bg). However:
<[!]--- some comments ---->
is highlighted properly with 4 ending dashes.
Did you encouter this before? Is it a bug?
(I have to use [!] instead of ! to pass the HTML tag validation on this blog)
Thanks
Khoa
Great job! Thanks for all the hard work on this, I am finding it very useful.
Does this work with 2.0?
2.0 what?
Post a Comment