Archive for Tools

Help Decide Sandcastle’s Fate

// June 10th, 2008 // 11 Comments » // Tools

Sandcastle – the .Net community equivalent of RDoc/JavaDoc and the spiritual successor of the now defunct NDochas been removed from CodePlex. It’s fate is currently up in the air and Microsoft is asking you for input on what should happen next. It was pulled down after the community at-large expressed reservations about Sandcastle being hosted on CodePlex while not being published under an Open Source license or even being posted in source form at all. According to the announcement on MSDN, the possible future for Sandcastle involves two scenarios:

  1. Publish the source code for Sandcastle and revive this project in Codeplex

  2. Migrate sandcastle to MSDN Code gallery at http://code.msdn.microsoft.com

I for one would love to see Sandcastle released as an open source project on CodePlex. It seems to me that Microsoft has had a great deal of success lately with releasing the MVC Framework as an open source project and I hope that they continue the trend. If you have used Sandcastle or foresee the need to have automatic code documentation generation on a project in the future, please do not hesitate to leave a comment letting the powers that be know how you feel.

Kick It on DotNetKicks.comShout It on DotNetShoutOuts.com

BlogEngine Extension: Copy Code to Clipboard

// January 7th, 2008 // 23 Comments » // Blogging, Tools

Since I first started this blog I've been using Colin Coller's CopySourceAsHtml plugin for Visual Studio (which Guy Burstein updated for Visual Studio 2008's RTM). It was a nice little tool but as I started posting more and more code snippets to the blog I found myself wanting some additional features, specifically:

  • Easy to use. While it was perfectly simple to copy the source as html, it was a bit more cumbersome to paste it properly into Windows Live Writer. I wound up in HTML mode in WLW everytime I needed to paste some code.
  • CSS support. The Html generated by the VS plugin inlines all of the styles and makes it difficult to change the presentation of your code without tweaking the settings of the plugin at posting time.
  • Scrollable. This is mostly handled by the above feature, but I wanted to be able to limit the amount of screen real estate taken up by the code while still allowing my readers to view more of the code without having to download any files.
  • Copyable. Sometimes you see a piece of code that fits your situation perfectly, but when you copy it you wind up with a bunch of extra Html guck and line numbers at the front of every line you have to manually remove. I want to allow my readers to copy the code without any of that mess.

I found syntaxhighlighter on Google Code pretty quickly and was impressed. I'd seen it around on other people's blogs and loved the "copy to clipboard" feature, but I really wanted a server-side (or pre-posting) highlighter that would take some of the strain off the browser and wanted something with richer WLW integration.

After looking at a few more options I finally found Leo Vildosola's Code Snippet plugin, which matched up with all of my requirements except for the lack of a built-in "copy to clipboard" feature. With some help from clipboard copying javascript posted at WebChicanery I set out to build an extension for BlogEngine.net that would automatically tack on the copy feature to any code I posted via the Code Snippet plugin.

A few hours later, I present the CopyCodeToClipboard extension for BlogEngine.net! Below you will find an example of the extension displaying it's own code (mmm… dogfood) and the css I've used to style the code on my site. I've tried to make the extension css friendly so you can change it's presentation a great deal without modifying the code.

CopyCodeToClipboard.zipDownload (3.82 kb)
Contains all of the below files in one convenient package.

CopyCodeToClipboard.csDownload (7.36 kb)
This is the actual extension and should be placed into your root/App_Code/Extensions/ folder.

   1: using System;
   2: using System.Text.RegularExpressions;
   3: using BlogEngine.Core;
   4: using BlogEngine.Core.Web.Controls;
   5: 
   6: [Extension("Adds a link to syntax highlighted code to copy it to the clipboard.", "1.0", @"<a href=""http://www.squaredroot.com"">Troy Goode</a>")]
   7: public class CopyCodeToClipboard
   8: {
   9: 
  10:     private ExtensionSettings settings;
  11: 
  12:     public CopyCodeToClipboard()
  13:     {
  14: 
  15:         Post.Serving += new EventHandler<ServingEventArgs>(Post_PostServing);
  16:         Post.Saving += new EventHandler<SavedEventArgs>(Post_Saving);
  17: 
  18:         ExtensionSettings initialSettings = new ExtensionSettings(GetType().Name);
  19:         initialSettings.Help = "This extension is written to work with the <a href="http://lvildosola.blogspot.com/2007/02/code-snippet-plugin-for-windows-live.html">Code Snippet Plugin for Windows Live Writer</a> and will not work with other syntax highlighting tools.";
  20:         initialSettings.AddParameter( "copyText", "Text for 'Copy to Clipboard' button?", 255, true );
  21:             initialSettings.AddValue( "copyText", "Copy To Clipboard" );
  22:         initialSettings.AddParameter( "popupText", "Text for 'View in Popup Window' button?", 255, true );
  23:             initialSettings.AddValue( "popupText", "View Plain" );
  24:         initialSettings.AddParameter("aboveBelow", "Display above or below code?", 5, true);
  25:             initialSettings.AddValue("aboveBelow", "Below");
  26:         initialSettings.AddParameter( "style", "Any additional styling?", int.MaxValue, false );
  27:             initialSettings.AddValue( "style", "" );
  28:         initialSettings.AddParameter( "flashFile", "Path to '_clipboard.swf' Flash file?", 255, true );
  29:             initialSettings.AddValue( "flashFile", "~/_clipboard.swf" );
  30:         initialSettings.IsScalar = true;
  31:         ExtensionManager.ImportSettings(initialSettings);
  32: 
  33:         settings = ExtensionManager.GetSettings(GetType().Name);
  34: 
  35:     }
  36: 
  37:     private void Post_Saving(object sender, SavedEventArgs e)
  38:     {
  39:         //### executing here will only run the code once per post, but will modify the post itself
  40:         //InsertCopyCodeLink(e);
  41:     }
  42: 
  43:     private void Post_PostServing(object sender, ServingEventArgs e)
  44:     {
  45:         //### executing here will execute the code on every iteration, but will not modify your post
  46:         InsertCopyCodeLink(e);
  47:     }
  48: 
  49:     private void InsertCopyCodeLink( ServingEventArgs e)
  50:     {
  51:         if( !string.IsNullOrEmpty(e.Body) )
  52:         {
  53: 
  54:             //### find code-div
  55:             string postID = Guid.NewGuid().ToString().Replace( "{", "" ).Replace( "}", "" ).Replace( "-", "" );
  56:             string toFind = "<div class="csharpcode-wrapper">";
  57:             int index = e.Body.IndexOf(toFind);
  58:             while( index != -1 )
  59:             {
  60: 
  61:                 //### grab code out of code-div
  62:                 int end = e.Body.IndexOf( "</div>", index ); //### the first </div> should be the end of "csharpcode"
  63:                 end = e.Body.IndexOf( "</div>", end ); //### the next </div> should be the end of "csharpcode-wrapper"
  64:                 string code = e.Body.Substring(index, end - index);
  65: 
  66:                 //### parse code out of code-div
  67:                 int codeStart = code.IndexOf("<pre ");
  68:                 int codeEnd = code.LastIndexOf("</pre>") + 6;
  69:                 code = code.Substring(codeStart, codeEnd - codeStart);
  70:                 code = Regex.Replace( code, @"<(.|n)*?>", string.Empty ); //### strip html
  71:                 code = code.Replace( "&#160;", "" ); //### remove unnecessary &#160;s from the blank lines
  72:                 code = code.Replace( "&nbsp;", " " ); //### convert &nbsp;s to spaces
  73:                 code = Regex.Replace( code, @"^(s*)(d+): ", "" ); //### remove line numbers on first line
  74:                 code = Regex.Replace( code, @"(n)(s*)(d+): ", "n" );  //### remove line numbers on subsequent lines
  75: 
  76:                 //### create copy link
  77:                 string insertScript = @"
  78:                     <script type=""text/javascript"">
  79:                         var copyToClipboard@INDEX = CopyToClipboard_Strip('@CODE');
  80:                     </script>";
  81:                 string insertDiv = @"<div class=""CopyToClipboard"" style=""@STYLE""><div><a href=""javascript:void(0);"" onclick=""CopyToClipboard_ViewPlain(copyToClipboard@INDEX);"">@POPUPTEXT</a> | <a href=""javascript:void(0);"" onclick=""CopyToClipboard_Copy(copyToClipboard@INDEX);"">@COPYTEXT</a></div></div>";
  82: 
  83:                 //### set values for copy link and insert above/below code
  84:                 string insert = insertDiv + OutputCommonMethods() + insertScript;
  85:                 insert = insert.Replace( "@STYLE", settings.GetSingleValue("style") );
  86:                 insert = insert.Replace( "@POPUPTEXT", settings.GetSingleValue("popupText") );
  87:                 insert = insert.Replace( "@COPYTEXT", settings.GetSingleValue("copyText") );
  88:                 insert = insert.Replace( "@INDEX", postID + "_" + index.ToString() ); //### use index of code-div as a unique ID to allow multiple code-divs on this post
  89:                 insert = insert.Replace( "@CODE", code.Replace( "", "" ).Replace( "'", "'" ).Replace( "rn", "rn" ).Replace( "rnrnrn", "rn" ) );
  90:                 if( settings.GetSingleValue("aboveBelow").ToLower() == "above" )
  91:                     e.Body = e.Body.Insert( index, insert );
  92:                 else
  93:                     e.Body = e.Body.Insert( e.Body.IndexOf( "</div>", end + 1 ) + 6, insert );
  94: 
  95:                 //### prep index to find next code-div
  96:                 index = index + insert.Length + 1; //### ensure we don't find this same code-div again
  97:                 if( index > e.Body.Length ) break;
  98:                 index = e.Body.IndexOf( toFind, index ); //### find any other code divs
  99: 
 100:             }
 101:         }
 102:     }
 103: 
 104:     private string OutputCommonMethods()
 105:     {
 106: 
 107:         //### only output the following once per page
 108:         if( System.Web.HttpContext.Current.Items["CopyToClipboard_JSOutput"] == null )
 109:         {
 110:             //### add shared javascript
 111:             string flashPath = System.Web.VirtualPathUtility.ToAbsolute(settings.GetSingleValue("flashFile"));
 112:             string commonMethods = @"
 113:                 <div id=""CopyToClipboard_Hidden"" style=""display:none;""></div>
 114:                 <div id=""CopyToClipboard_FlashContainer""></div>
 115:                 <script type=""text/javascript"">
 116: 
 117:                     function CopyToClipboard_Strip( text ){
 118:                         text = text.replace( /&nbsp;/g, ' ' );
 119:                         text = text.replace( /&quot;/g, '""' );
 120:                         text = text.replace( /&#39;/g, '""' );
 121:                         text = text.replace( /&amp;/g, '&' );
 122:                         text = text.replace( /&lt;/g, String.fromCharCode(60) );
 123:                         text = text.replace( /&gt;/g, String.fromCharCode(62) );
 124:                         return text;
 125:                     }
 126: 
 127:                     function CopyToClipboard_Copy( text ){
 128: 
 129:                         //### get reference to utility div
 130:                         var ele = document.getElementById('CopyToClipboard_Hidden');
 131: 
 132:                         //### the following taken from: http://webchicanery.com/2006/11/14/clipboard-copy-javascript/
 133:                         if (false && window.clipboardData) {
 134:                             window.clipboardData.setData( ""Text"", text );
 135:                         } else {
 136:                             document.getElementById('CopyToClipboard_FlashContainer').innerHTML = '';
 137:                             var divinfo = '<embed id=""CopyToClipboard_FlashFile"" src=""" + flashPath + @""" FlashVars=""clipboard=' + encodeURIComponent(text) + '"" width=""0"" height=""0"" type=""application/x-shockwave-flash""></embed>';
 138:                             document.getElementById('CopyToClipboard_FlashContainer').innerHTML = divinfo;
 139:                         }
 140: 
 141:                     }
 142: 
 143:                     function CopyToClipboard_ViewPlain( text ){
 144:                         var win = window.open( '', 'CopyToClipboard_Window', 'width=480, height=480, toolbar=no, menubar=no, scrollbars=auto, resizable=yes, location=no, directories=no, status=no' );
 145:                         win.document.write( '<html><head><title>Code</title><body style=""margin:0;padding:0;""><textarea style=""width:100%;height:100%;border:0;"">' + text + '</textarea></body></html>' );
 146:                     }
 147: 
 148:                 </script>
 149:             ";
 150:             System.Web.HttpContext.Current.Items["CopyToClipboard_JSOutput"] = true;
 151:             return commonMethods;
 152:         }
 153:         else
 154:             return "";
 155: 
 156:     }
 157: 
 158: }

WLWSyntaxHighlighter.cssDownload (1.26 kb)

The Css generated by the Windows Live Writer Code Snippet plugin, modified to fit into my blog's colors.

   1: .csharpcode-wrapper, .csharpcode-wrapper pre {
   2:   clear: both;
   3:   background-color: #f5f5f5;
   4:   border: solid 2px #A0410D;
   5:   font-family: Consolas, 'Courier New', Courier, Monospace;
   6:   font-size: 8pt;
   7:   line-height: 12pt;
   8:   margin: 15px 0 0 0px;
   9:   max-height: 227px;
  10:   overflow: auto;
  11:   padding: 0px 0px 0px 0px;
  12:   width: 454px;
  13: }
  14: .csharpcode-wrapper pre {
  15:   border-style: none;
  16:   margin: 0px 0px 0px 0px;
  17:   overflow: visible;
  18:   padding: 0px 0px 0px 0px;
  19: }
  20: .csharpcode, .csharpcode pre, .csharpcode .alt {
  21:   background-color: #f5f5f5;
  22:   border-style: none;
  23:   color: black;
  24:   font-family: Consolas, 'Courier New', Courier, Monospace;
  25:   font-size: 8pt;
  26:   line-height: 12pt;
  27:   overflow: visible;
  28:   padding: 0px 0px 0px 0px;
  29:   width: 100%;
  30: }
  31: .csharpcode pre {
  32:   margin: 0em;
  33: }
  34: .csharpcode .alt {
  35:   background-color: #f4f2df;
  36: }
  37: .csharpcode .asp {
  38:   background-color: #ffff00;
  39: }
  40: .csharpcode .attr {
  41:   color: #ff0000;
  42: }
  43: .csharpcode .cls {
  44:   color: #cc6633;
  45: }
  46: .csharpcode .html {
  47:   color: #800000;
  48: }
  49: .csharpcode .kwrd {
  50:   color: #0000ff;
  51: }
  52: .csharpcode .lnum {
  53:   color: #f1c969;
  54: }
  55: .csharpcode .op {
  56:   color: #0000c0;
  57: }
  58: .csharpcode .preproc {
  59:   color: #cc6633;
  60: }
  61: .csharpcode .rem {
  62:   color: #008000;
  63: }
  64: .csharpcode .str {
  65:   color: #006080;
  66: }

CopyCodeToClipboard.cssDownload (588 bytes)

The Css I use to style the little red tab below each code area. This is what you would change to match your blog.

   1: .CopyToClipboard{
   2:     width: 458px;
   3:     text-align: right;
   4:     margin: 0 0 5px 0;
   5: }
   6: .CopyToClipboard div{
   7:     margin-left: 248px;
   8:     background-color: #A0410D;
   9:     padding: 0px 5px 2px 5px;
  10:     text-align: center;
  11:     color: #F1C969;
  12:     width: 200px;
  13:     font-size: 9px;
  14: }
  15: .CopyToClipboard a{
  16: }
  17: .CopyToClipboard a:link{
  18:     text-decoration: none;
  19:     color: #f5f5f5;
  20: }
  21: .CopyToClipboard a:visited{
  22:     text-decoration: none;
  23:     color: #f5f5f5;
  24: }
  25: .CopyToClipboard a:hover{
  26:     text-decoration: underline;
  27:     color: #f5f5f5;
  28: }
  29: .CopyToClipboard a:active{
  30:     text-decoration: underline;
  31:     color: #f5f5f5;
  32: }

_clipboard.swfDownload (109 bytes)

A Macromedia Flash file used to circumvent Firefox & Safari's nasty habit of blocking javascript from interacting with the clipboard. Created (as best I can tell) by Mark O'Sullivan of http://lussumo.com/.

Update (July 11, 2008):
A release of the Code Snippet plug hasn't been made since the last changes to its source was checked in (in late 2007), so I went ahead and downloaded & built the source. To install the plug place the two DLLs in the zip file from below into Windows Live Writer's /Plugins/ directory.

CodeSnippetPlugin.zip (64.91 kb)

Kick It on DotNetKicks.comShout It on DotNetShoutOuts.com

CssHandler: CSS + Variables

// November 16th, 2007 // 9 Comments » // Tools

It’s been more than three and a half years since Rory Blyth said “screw standards — let’s add variable support to CSS right this minute.” In retrospect, adding variables to CSS seems obvious; everyone is asking about it. There have even been one or two other .Net solutions developed, though Rory’s solution has remained my favorite. With CSS2 barely working and CSS3 not addressing the issue, those of us who are tired of repeating ourselves throughout our CSS files have no choice but to take matters into our own hands.

With that in mind I set out to update Rory’s code. Not that there was anything wrong with it in the first place, of course (this is where I try to tactfully avoid being drawn as a jerkasaurus in one of Rory’s comics), but Rory did whip the code up for a PADNUG meeting and that was about 3.5 million years ago (give or take many orders of magnitude). It was time to bring the CssHandler into 2007! Conveniently right before 2008.

Lo and behold, someone else had beat me to it. Gabe Moothart had already uploaded the CssHandler to CodePlex, and even improved upon it. While Rory’s code originally only allowed variable declarations & references and stripped comments, Gabe’s version also resolved application-relative paths (e.g.: “~/blah/blah.gif”) into browser friendly paths. I had further goals in mind though, and Gabe was kind enough to add me onto the project.

With my updates complete, I am now proud to present…

CssHandler 1.0

Features:

  • Define variables for later reference.
  • Resolve application relative paths.
  • Only link to one CSS file from your HTML page. Let the CssHandler combine additional CSS files at runtime to limit HTTP connections and share variable definitions across files.
  • All comments are stripped before render.
  • Most white-space is stripped before render.
  • HttpHandler can be mapped to *.css or can be referenced as CssHandler.axd and passed a CSS file in the query string.

Bugs fixed:

  • The @define{…} block is no longer sent down to the client.
  • Similarly named variables no longer present a problem.

Example:

The HTML references “styles.css”, which looks like:

CssHandler_stylecss

As seen above, “styles.css” references “styles2.css” via the @import statement, which looks like:

CssHandler_style2css

The CssHandler then:

  1. Intercepts the browser’s request for “styles.css”.
  2. Resolves the two URLs that are using application relative paths.
  3. Replaces the @import directive with the text from “styles2.css”.
  4. Strips all comments from the CSS.
  5. Replaces all referenced variables with their defined values.
  6. Compresses most of the CSS’s white-space.
  7. Renders the following to the browser:

CssHandler_rendered

I hope you find it as useful as I have. Many thanks to Rory Blyth and Gabe Moothart. Hopefully CSS4 will add variables and we’ll never have to use this again!

Visit the project’s CodePlex site to download.

Update (Dec. 2, 2007): Rory has posted and given his blessing, so to speak. Thanks Rory!

Kick It on DotNetKicks.comShout It on DotNetShoutOuts.com