Color Coding Sample Code Getting Much Closer

Posted August 30, 2006 at 10:22 AM

Tags: ColdFusion

I am almost done figuring out the code I need to color code my code sample. Color coding will make things much easier to read for you guys. The problem is, this is REALLY freakin' hard. I have looked at Ray Camden's color coding and some other coding, and it just doesn't apply here. The problem I have is that my code is structured in lists rather than just plain text or <pre> tags. I do this to exert more control over the layout, display, and wrapping of the code. So far, I am really pleased with it.

So, to start off, let's take a look at the code that I would need to color code:

 Launch code in new window » Download code as text file »

  • <div class="code">
  • <ul>
  • <li>&lt;!--- Set up my occupation. ---&gt;</li>
  • <li>&lt;cfset strMyOccupation = "Mail man" /&gt;</li>
  • <li></li>
  • <li></li>
  • <li>&lt;!--- Create a new query. ---&gt;</li>
  • <li>&lt;cfset qGirls = QueryNew( "id, name, fantasy" ) /&gt;</li>
  • <li></li>
  • <li>&lt;!--- Add rows to the query. ---&gt;</li>
  • <li>&lt;cfset QueryAddRow( qGirls, 2 ) /&gt;</li>
  • <li></li>
  • <li>&lt;!--- Populate query. ---&gt;</li>
  • <li>&lt;cfset qGirls[ "id" ][ 1 ] = 1 /&gt;</li>
  • <li>&lt;cfset qGirls[ "name" ][ 1 ] = "Julia" /&gt;</li>
  • <li>&lt;cfset qGirls[ "fantasy" ][ 1 ] = "Cabana boy" /&gt;</li>
  • <li>&lt;cfset qGirls[ "id" ][ 2 ] = 2 /&gt;</li>
  • <li>&lt;cfset qGirls[ "name" ][ 2 ] = "Katie" /&gt;</li>
  • <li>&lt;cfset qGirls[ "fantasy" ][ 2 ] = "Mail man" /&gt;</li>
  • <li></li>
  • <li></li>
  • <li>&lt;!--- Loop over the query. ---&gt;</li>
  • <li>&lt;cfloop query="qGirls"&gt;</li>
  • <li class="tab1"></li>
  • <li class="tab1">&lt;!--- Check to see if I am the fantasy. ---&gt;</li>
  • <li class="tab1">&lt;cfif (qGirls.fantasy EQ strMyOccupation)&gt;</li>
  • <li class="tab2"></li>
  • <li class="tab2">&lt;!--- This is the girl for me!! ---&gt;</li>
  • <li class="tab2">&lt;cfmail</li>
  • <li class="tab3">to="xxx@yyy.zzz"</li>
  • <li class="tab3">from="xxx@yyy.zzz"</li>
  • <li class="tab3">subject="Dude! Email this chick now!!"</li>
  • <li class="tab3">type="HTML"&gt;</li>
  • <li class="tab3"></li>
  • <li class="tab3">Ben,&lt;br /&gt;</li>
  • <li class="tab3">This girl is just your type... or rather</li>
  • <li class="tab3">you might just be her's. Giver a call or</li>
  • <li class="tab3">email here ASAP.&lt;br /&gt;</li>
  • <li class="tab3">&lt;br /&gt;</li>
  • <li class="tab3"></li>
  • <li class="tab3">Regards,&lt;br /&gt;</li>
  • <li class="tab3">Your better half.</li>
  • <li class="tab2"></li>
  • <li class="tab2">&lt;/cfmail&gt;</li>
  • <li class="tab1"></li>
  • <li class="tab1">&lt;/cfif&gt;</li>
  • <li></li>
  • <li>&lt;/cfloop&gt;</li>
  • </ul>
  • </div>

As you can see, each line of code is broken up into it's own LI element that is contained within a DIV of class "code". The major problem is that a single tag (such as CFMail in my example), can wrap multiple lines and therefore be spread across several LI tags. Heck yeah! I love a challenge.

So, the hardest part in is figuring out how to start and end color code span tags such that they get the correct code and still comply with XHTML compliance. My function is NOT quite there, but is definitely pretty close. Currently, it has some limitations:

  1. Cannot handle comments spread across several lines.
  2. Does not color encode free standing numbers (as in <cfset x = 2 />).
  3. Cannot handle quotes inside quotes (as in <cfset x = "te""st" />).
  4. Does not work inside CFScript blocks (as you can see in the function below).

There are probably a few other limitation, but as you will see soon, I am almost there. Let's take a look at the function which makes this all possible:

 Launch code in new window » Download code as text file »

  • <cffunction
  • name="ColorCode"
  • access="public"
  • returntype="string"
  • output="false"
  • hint="This takes code samples and color codes for display.">
  •  
  • <!--- Define arguments. --->
  • <cfargument name="Code" type="string" required="true" />
  •  
  • <cfscript>
  •  
  • // Define the local scope.
  • var LOCAL = StructNew();
  •  
  • // Define some constants. At times, we will want to
  • // replace common characters with special characters
  • // so that they will not be manipulated until we
  • // want them to.
  • LOCAL.OpenTag = Chr( 500 );
  • LOCAL.CloseTag = Chr( 501 );
  • LOCAL.OpenTag2 = Chr( 600 );
  • LOCAL.CloseTag2 = Chr( 601 );
  • LOCAL.OpenAttribute = Chr( 700 );
  • LOCAL.CloseAttribute = Chr( 701 );
  • LOCAL.RealOpenAttribute = Chr( 800 );
  • LOCAL.RealCloseAttribute = Chr( 801 );
  • LOCAL.LineBreak = Chr( 900 );
  •  
  • // Set up the span classes to be used for colors.
  • LOCAL.Classes.CFML = "cfmlcodecolor";
  • LOCAL.Classes.Comment = "commentcodecolor";
  • LOCAL.Classes.Attribute = "attributecodecolor";
  •  
  •  
  • // Replace out line breaks. We are doing this so that
  • // our regular expression never need to worry about
  • // matching new line and carriage return charactrers.
  • // This may or may not be needed.
  • ARGUMENTS.Code = ARGUMENTS.Code.ReplaceAll(
  • "#Chr( 13 )##Chr( 10 )#",
  • LOCAL.LineBreak
  • );
  •  
  •  
  • // We have to escape the attribute quotes that we do
  • // NOT want to mess with - those ones that are in
  • // standard html tags that ARE parts of code, not
  • // code samples.
  • while( true ){
  •  
  • // Get the new code with replace quotes.
  • LOCAL.EscapedAttributes = ARGUMENTS.Code.ReplaceFirst(
  • "(<[\w]+[^"">]+)("")([^""]*)("")([^>]*>)",
  • (
  • "$1" &
  • LOCAL.RealOpenAttribute &
  • "$3" &
  • LOCAL.RealCloseAttribute &
  • "$5"
  • ));
  •  
  • // Check to see if our last replace did anything.
  • if (LOCAL.EscapedAttributes.Equals( ARGUMENTS.Code )){
  • ARGUMENTS.Code = LOCAL.EscapedAttributes;
  •  
  • // The last replace didn't do anything,
  • // so break out of this loop.
  • break;
  • } else {
  • ARGUMENTS.Code = LOCAL.EscapedAttributes;
  • }
  •  
  • }
  •  
  •  
  • // Replace the regular tags with special characters
  • // so that we don't mess with them. These are tags
  • // that are part of the code, NOT the example code.
  • ARGUMENTS.Code = ARGUMENTS.Code.ReplaceAll(
  • "<",
  • LOCAL.OpenTag
  • );
  •  
  • ARGUMENTS.Code = ARGUMENTS.Code.ReplaceAll(
  • ">",
  • LOCAL.CloseTag
  • );
  •  
  •  
  • // Replace the sample code (that we want to color
  • // code) with real tags so that we can parse it.
  • ARGUMENTS.Code = ARGUMENTS.Code.ReplaceAll(
  • "<",
  • "<"
  • );
  •  
  • ARGUMENTS.Code = ARGUMENTS.Code.ReplaceAll(
  • ">",
  • ">"
  • );
  •  
  •  
  • // Now, we have to esacpe the attribute valus of
  • // that tags we just put in.
  • // NOTE: We already escaped the REAL attribute
  • // values, so we do not have to worry about these.
  • while( true ){
  •  
  • // Get the new code with replace quotes.
  • LOCAL.EscapedAttributes = ARGUMENTS.Code.ReplaceFirst(
  • "(<[\w]+[^"">]+)("")([^""]*)("")([^>]*>)",
  • (
  • "$1" &
  • LOCAL.OpenAttribute &
  • "$3" &
  • LOCAL.CloseAttribute &
  • "$5"
  • ));
  •  
  • // Check to see if our last replace did anything.
  • if (LOCAL.EscapedAttributes.Equals( ARGUMENTS.Code )){
  • ARGUMENTS.Code = LOCAL.EscapedAttributes;
  •  
  • // The last replace didn't do anything,
  • // so break out of this loop.
  • break;
  • } else {
  • ARGUMENTS.Code = LOCAL.EscapedAttributes;
  • }
  •  
  • }
  •  
  •  
  • // Surround the comments with span tag.
  • ARGUMENTS.Code = ARGUMENTS.Code.ReplaceAll(
  • "(<!---((?!--->).)*--->)",
  • (
  • LOCAL.OpenTag &
  • "span class=""" &
  • LOCAL.Classes.Comment &
  • """" &
  • LOCAL.CloseTag &
  • "$1" &
  • LOCAL.OpenTag &
  • "/span" &
  • LOCAL.CloseTag
  • ));
  •  
  • // Surround the cf code with span.
  • ARGUMENTS.Code = ARGUMENTS.Code.ReplaceAll(
  • "(</?cf+[^>]+?>)",
  • (
  • LOCAL.OpenTag &
  • "span class=""" &
  • LOCAL.Classes.CFML &
  • """" &
  • LOCAL.CloseTag &
  • "$1" &
  • LOCAL.OpenTag &
  • "/span" &
  • LOCAL.CloseTag
  • ));
  •  
  • // Surround the attributes with span.
  • ARGUMENTS.Code = ARGUMENTS.Code.ReplaceAll(
  • "(#LOCAL.OpenAttribute#[^#LOCAL.CloseAttribute#]*#LOCAL.CloseAttribute#)",
  • (
  • LOCAL.OpenTag &
  • "span class=""" &
  • LOCAL.Classes.Attribute &
  • """" &
  • LOCAL.CloseTag &
  • "$1" &
  • LOCAL.OpenTag &
  • "/span" &
  • LOCAL.CloseTag
  • ));
  •  
  •  
  • // Replace in attribute quotes.
  • ARGUMENTS.Code = ARGUMENTS.Code.ReplaceAll(
  • "(#LOCAL.OpenAttribute#|#LOCAL.RealOpenAttribute#)",
  • """"
  • );
  •  
  • ARGUMENTS.Code = ARGUMENTS.Code.ReplaceAll(
  • "(#LOCAL.CloseAttribute#|#LOCAL.RealCloseAttribute#)",
  • """"
  • );
  •  
  •  
  • // Our cfml tags might wrap around some other tags (ex.
  • // LIs in an unordered list). To make sure that this
  • // doesn't break the color coding, we have to end the
  • // CFML span before the tag, then continue it after
  • // the matching closing tag.
  • while( true ){
  •  
  • // Get the new code with around-tag spans.
  • LOCAL.NewSpans = ARGUMENTS.Code.ReplaceFirst(
  • "(<cf[^#LOCAL.OpenTag#>]+)(#LOCAL.OpenTag#)([^#LOCAL.CloseTag#]+)(#LOCAL.CloseTag#)([^#LOCAL.OpenTag#]*)(#LOCAL.OpenTag#)([^#LOCAL.CloseTag#]+)(#LOCAL.CloseTag#)([^>]*>)",
  • (
  • "$1" &
  • LOCAL.OpenTag2 &
  • "/span" &
  • LOCAL.CloseTag2 &
  • LOCAL.OpenTag2 &
  • "$3" &
  • LOCAL.CloseTag2 &
  • "$5" &
  • LOCAL.OpenTag2 &
  • "$7" &
  • LOCAL.CloseTag2 &
  • LOCAL.OpenTag2 &
  • "span class=""" &
  • LOCAL.Classes.CFML &
  • """" &
  • LOCAL.CloseTag2 &
  • "$9"
  • ));
  •  
  • // Check to see if our last replace did anything.
  • if (LOCAL.NewSpans.Equals( ARGUMENTS.Code )){
  • ARGUMENTS.Code = LOCAL.NewSpans;
  •  
  • // The last replace didn't do anything,
  • // so break out of this loop.
  • break;
  • } else {
  • ARGUMENTS.Code = LOCAL.NewSpans;
  • }
  •  
  • }
  •  
  •  
  • // Replace back in the old tags constants.
  • ARGUMENTS.Code = ARGUMENTS.Code.ReplaceAll(
  • "#LOCAL.OpenTag2#{1}",
  • LOCAL.OpenTag
  • );
  •  
  • ARGUMENTS.Code = ARGUMENTS.Code.ReplaceAll(
  • "#LOCAL.CloseTag2#{1}",
  • LOCAL.CloseTag
  • );
  •  
  • // Replace out brackets.
  • ARGUMENTS.Code = ARGUMENTS.Code.ReplaceAll(
  • "<",
  • "<"
  • );
  •  
  • ARGUMENTS.Code = ARGUMENTS.Code.ReplaceAll(
  • ">",
  • ">"
  • );
  •  
  •  
  • // Replace in original tag brackets.
  • ARGUMENTS.Code = ARGUMENTS.Code.ReplaceAll(
  • "(#LOCAL.OpenTag#|#LOCAL.OpenTag2#)",
  • "<"
  • );
  •  
  • ARGUMENTS.Code = ARGUMENTS.Code.ReplaceAll(
  • "(#LOCAL.CloseTag#|#LOCAL.CloseTag2#)",
  • ">"
  • );
  •  
  •  
  • // Replace the original line breaks.
  • ARGUMENTS.Code = ARGUMENTS.Code.ReplaceAll(
  • LOCAL.LineBreak,
  • Chr( 13 ) & Chr( 10 )
  • );
  •  
  • // Return color coded text.
  • return( ARGUMENTS.Code.ToString() );
  •  
  • </cfscript>
  • </cffunction>

Yeah, it IS a beefy function. As you can see, I take full advantage of the very sexy Java string regular expressions which can be access via the Java methods that live underneath the ColdFusion string objects. Now, if I were to run the sample code listed above through this, I would get the following:

 Launch code in new window » Download code as text file »

  • <!--- Set up my occupation. --->
  • <cfset strMyOccupation = "Mail man" />
  •  
  •  
  • <!--- Create a new query. --->
  • <cfset qGirls = QueryNew( "id, name, fantasy" ) />
  •  
  • <!--- Add rows to the query. --->
  • <cfset QueryAddRow( qGirls, 2 ) />
  •  
  • <!--- Populate query. --->
  • <cfset qGirls[ "id" ][ 1 ] = 1 />
  • <cfset qGirls[ "name" ][ 1 ] = "Julia" />
  • <cfset qGirls[ "fantasy" ][ 1 ] = "Cabana boy" />
  •  
  • <cfset qGirls[ "id" ][ 2 ] = 2 />
  • <cfset qGirls[ "name" ][ 2 ] = "Katie" />
  • <cfset qGirls[ "fantasy" ][ 2 ] = "Mail man" />
  •  
  •  
  • <!--- Loop over the query. --->
  • <cfloop query="qGirls">
  •  
  • <!--- Check to see if I am the fantasy. --->
  • <cfif (qGirls.fantasy EQ strMyOccupation)>
  •  
  • <!--- This is the girl for me!! --->
  • <cfmail
  • to="xxx@yyy.zzz"
  • from="xxx@yyy.zzz"
  • subject="Dude! Email this chick now!!"
  • type="HTML">
  •  
  • Ben,<br />
  • This girl is just your type... or rather
  • you might just be her's. Giver a call or
  • email here ASAP.<br />
  • <br />
  •  
  • Regards,<br />
  • Your better half.
  •  
  • </cfmail>
  •  
  • </cfif>
  •  
  • </cfloop>

So, as you can see, I AM getting very close. There are some more kinks to work out, but oh man, I am hot on the trail of greatness.

Download Code Snippet ZIP File

Post Comment  |  Ask Ben  |  Permalink  |  Other Searches  |  Print Page




Learning ColdFusion 9 - ColdFusion 9 tutorials, samples, examples, demos

Reader Comments

Aug 30, 2006 at 12:43 PM // reply »
2 Comments

Surely you want to use CSS and styles to do this? Or am I not understanding the problem? If you used a combination of DIV's and CSS selectors wouldn't that be easier?


Aug 30, 2006 at 12:48 PM // reply »
74 Comments

Khalid,

I am using Divs and CSS. The problem is that the sample code is escaped and is not actually spans. So, even where I have <cfset ../> in the code, behind the scenes, I actually have <cfset ... /> which does not provide me with any CSS hooks, unless I am unfamiliar with them.


Aug 30, 2006 at 5:59 PM // reply »
5 Comments

If you would use a ordered list instead of a unordered list, you could easily add a little js and css to have a show/hide line numbers method on the site (would work without a page refresh).


Aug 30, 2006 at 6:06 PM // reply »
74 Comments

Trond,

I am not sure how an ordered vs. unordered list would help any? In the HTML, they still both have LI elements. As far as Javascript, I was hoping to have all the "code crunching" done before the viewing of this page. I would rather format the code pre-Database insertion rather than on display. That way, I never have to worry about processing of code, or load time or anything like that.

But, like I said, I am just trying stuff here. I am always open to new suggestions. If I see something better demonstrated, I will adopt it for sure.


Aug 31, 2006 at 5:36 AM // reply »
34 Comments

Well . . . I use NetNewsWire. Your code in the News Item tab really does come out as a list, with everything lined up at the left edge, which kind of makes it difficult to read your code without index. I'd have to say that I prefer the usage of CODE or PRE tag for formatting the code - much more readable and easier to copy-paste.

LI should be reserved for text that really does appear as a list, ordered or unordered.


Aug 31, 2006 at 5:37 AM // reply »
34 Comments

Index = indenting


Aug 31, 2006 at 6:41 AM // reply »
5 Comments

Hi Ben,

I put together a little demo.

What I did was the following.
Changed the <ul> to <ol>.
A search and replace in css: replaced "code ul" with "code ol"
Removed the "list-style-type: none;" property from the css.
Added "margin-left: 30px" on the code ol element in the css.
Added a little js in the head section and a little a element to trigger the js.

The demo is here: http://cfskill.com/colorcode/


Aug 31, 2006 at 10:06 AM // reply »
74 Comments

Lola Lee,

Yeah, I see that on Feed-Squirrel also. I don't really ever read a blog post outside of the parent site, so I don't have too much experience with display without proper CSS and what not.

How does the PRE tag display? Does it display as indented? I might end up switching.


Aug 31, 2006 at 10:08 AM // reply »
74 Comments

Trond,

That is a pretty sexy demo. Thanks for putting that together. However, when you copy and paste the text from the browser, the line numbers still display on the pasted copy page. I am considering switching over to the PRE tag as Lola Lee (and others) have discussed.


Sep 1, 2006 at 5:37 PM // reply »
5 Comments

hmm - in firefox it does indeed copy the linenumbers :(

In Opera (my main browser) and IE (which I only use for test purposes) does not.

Wonder if it's possible to get around this in Firefox somehow. I think it's really helpful to have linenumbers in code examples.


Sep 1, 2006 at 5:41 PM // reply »
6,516 Comments

Ahhh, gotcha. Makes sense. I am not sure if you can get around that in FireFox.


Post Comment  |  Ask Ben

Recent Blog Comments
Nov 20, 2009 at 11:32 PM
Five Months Without Hungarian Notation And I'm Loving It
I've used headless camel case for years for not only ColdFusion variables, but also SQL tables and fields... pretty much everything involving code. I also subscribe to the "don't abbreviate and clea ... read »
Nov 20, 2009 at 11:00 PM
Five Months Without Hungarian Notation And I'm Loving It
@Marcel, Yeah, I always err on the side of longer but more readable variable names. As for the camel casing of CF methods and the headless camel casing of custom items, I get around this by always ... read »
Nov 20, 2009 at 10:56 PM
Five Months Without Hungarian Notation And I'm Loving It
I use the following and love it: my.namespace.MyComponents.functionMethodsOrUDF() CONSTANT_VALUES_OR_PROPERTIES One thing I always try is to CamelCaseBuiltInColdFusionFunctions() so others can tell ... read »
Nov 20, 2009 at 5:38 PM
Learning ColdFusion 8: CFImage Part I - Reading And Writing Images
Hi Ben, Great article. I've been looking around to see if ColdFusion image engine can programatically create the following "wrap around" effect: http://www.creativepro.com/article/photoshop-s-she ... read »
Nov 20, 2009 at 5:35 PM
Maintaining ColdFusion Sessions Across SMS Text Message Requests Without Cookies
@Dave: I talked to Gert he suggested: <cfhttp method="get" url="http://{some cf website}" result="stuff" addtoken="yes" /> Note the addition of cfhttp attribute addtoken. That should persist y ... read »
Nov 20, 2009 at 5:23 PM
Maintaining ColdFusion Sessions Across SMS Text Message Requests Without Cookies
@Todd, Ahh, gotcha, yeah that makes sense. ... read »
Nov 20, 2009 at 5:17 PM
Maintaining ColdFusion Sessions Across SMS Text Message Requests Without Cookies
Ben, sorry if I didn't make this clear. You can make it work like that if you want, just put <cfset session.foo = 1> (and <cfset application.foo = 1>) in your OnRequestStart() and it reve ... read »