Color Coding Sample Code Getting Much Closer

Posted August 30, 2006 at 10:22 AM by Ben Nadel

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:

  • <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:

  • <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:

  • <!--- 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.




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 »
148 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 »
148 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 »
11,246 Comments

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


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
May 24, 2013 at 5:13 PM
Ask Ben: Manually Enforcing Basic HTTP Authorization In ColdFusion
Hi Jason, Thanks for checking up on that, but I still stand firm on my position. :) There are actually two listLast()'s in use, and you're right that the one using a space as a delimiter is fine. ... read »
May 24, 2013 at 4:45 PM
Ask Ben: Manually Enforcing Basic HTTP Authorization In ColdFusion
@Ben I have been lurking your site for quite some time, and haven't stepped up to comment until today. Thanks for all the great info - keep it up! @Adam I believe you are mistaken... as the commen ... read »
May 24, 2013 at 11:21 AM
Strange Interaction Between DeserializeJson(), ArrayContains(), And Database Values In ColdFusion
@WebManWalking, Ha ha, let's us never speak of justifying "##" notation again :P ... read »
May 24, 2013 at 11:18 AM
Strange Interaction Between DeserializeJson(), ArrayContains(), And Database Values In ColdFusion
@Ben, Ah, so it was indeed how I vaguely remembered it to be: A direct assignment value = users.id[ i ] causes value to retain the sticky datatype of the query column. Although unnecessary in ... read »
May 24, 2013 at 9:11 AM
Preventing Links In Standalone iPhone Applications From Opening In Mobile Safari
@Brandon, Hi, No, I haven't been able to do that. I have just kept it as it is. ... read »
May 23, 2013 at 9:52 PM
Preventing Links In Standalone iPhone Applications From Opening In Mobile Safari
@Muhmmadibn Did you figure out a solution to launching PDFs? I am running into the same issues myself. There is no way to close the PDF or go back once you launch it. Thanks in advance! ... read »
May 23, 2013 at 6:06 PM
The Girl Who Broke My Heart, And Made Me A Better Person
Good day,ladies and gentle men, my name is Dr AMADI the great spell caster in Africa, i have help so many people for different kind of problems,who say there is no solution to problems on earth, that ... read »
May 23, 2013 at 4:26 PM
ColdFusion QueryAppend( qOne, qTwo )
@Heather, Glad people are still getting value out of this! ... read »
InVision App - Prototyping Made Beautiful With Prototyping Tools