Monday, September 5, 2016

Simple and functional code syntax highlighter gadget for the Blogger, part 1

When I was writing one of my previous posts to this blog I faced with quite a typical problem of any developer who wanted to publish a source code to a web resource in a familiar and "easy to read" format. I have quickly tried a number of online and offline tools but found most of them having specific issues and missing a certain functionality.

My expectations of the highlighting functionality for the posted source code
  1. The code must be formatted to look like in the familiar or standard development tools, i.e. to contain easily recognizable and readable code pieces and properly colored items.
  2. Row numbers should be present on the left side of each line. This allows readers to refer to a particular line of the posted source code easily.
  3. The posted source code should be easily selectable on the page as a complete area without a necessity to drag a mouse throughout multiple screens. When I make a "copy-paste"of  a selected code snippet the copied content should not contain any extra characters like row numbers or contaminating HTML-tags. In the perfect case, the inserted code could be immediately evaluated and executed by a reader without any additional cleaning.
  4. Alternation of line background color should be supported although I do not consider this option as absolutely necessary.
  5. The absence of visual problems in the highlighted code that may turn a simple attempt to highlight a code into a very time-consuming task for a blogger. Example: annoying and difficult to fix wraps to other lines that make the code uneasy to read.
  6. Lightweight of the used components with quick time to load and to apply highlighting to multiple posts on the same page.
  7. Free of charge, possibly with an option to donate the author if I liked the functionality. As a good practice, the component should not display any annoying visual signs that would remind readers to make a donation.

Easy to find known free tools that can be used to highlight the posted source code dynamically

GitHub Gist

This tool provides a rich highlighting functionality that includes line numbers, line selection, and alternation but requires adding your code to a repository (secret anonymous or named public). It also adds quite a clumsy "iframe" element to your blog post, which means loading time can be occasionally poor.

ToHtml

An online code highlighting engine that wraps your code into nice looking lightweight HTML tags to be directly inserted into your post. This tool offers relatively simple formatting options without row numbers and alternation. I have also noticed some bugs of this tool in my tests, for example, when the first part of my code was highlighted correctly but the residual part was not (after the tool could not correctly handle a specific character in the middle of the code area). Anyway, this can be a fair choice for simple code snippets.

hilite.me

One more online tool that produces lightweight HTML out of your source code, supports row numbers in a separate column (important for excluding it from selection), can render the classic styles of Visual Studio, and does not seem to have obvious bugs like the previous one. Excellent although requires manual copy paste and possible cleaning of undesired elements; I slightly dislike its red highlighting of some punctuation elements.

Syntax Highlighter

This is the old but still very functional tool that provides a rich highlighting functionality with row numbers. Surprisingly enough, it does not seem to support alternation of line background colors. On the other hand, it supports lightweight extendable "look and feels" (so called brushes) that cover most of the code languages. I found this tool also contains non-significant tiny visual bugs, for example, it adds unexpected extra characters to the last line of Powershell comments. It also displays an annoying "ad" in the right top corner of the formatted code snippet. Anyway, all issues were easy to fix.

My choice

After conducting a number of tests, I have finally decided to use the last tool in this blog (Syntax Highlighter). It fits most of my mentioned expectations except for line background color alternation, provides fairly rich coloring schemes, and has very moderate loading time.

Usage of the tool is not intuitive but still relatively simple.
  • First of all, wrap your source code that you insert into your blog post in <pre>...</pre> tags.
  • After that add a class "brush: <acronym>" into <pre>, for example, <pre class="brush: html">. Unfortunately, I could not quickly find a way to apply multiple brushes to the same code area, which can definitely contain a mix of several code snippets, typically, HTML + JavaScript + CSS.
  • Add a number of references to .css and .js files of the "Syntax Highlighter" available from any fast hosting provider, for example, from a CDN or the site of the tool's author. I provide more specific details below.

Custom gadget for highlighting source code in all my blog posts

Fairly, I have been pleasantly surprised by the functionality of the "Syntax Highlighter". So I have immediately developed a simple custom gadget for my own blog that seamlessly ensures the code highlighting options to all my posts automatically on the first load. All I need to do when I create a new blog post with code snippets, to place my code inside the tag <pre></pre> and specify a brush in its class settings.
  • In addition, my gadget renders the buttons "Select the code" above and below the code blocks that allow selecting the complete block on a single click. Syntax Highlighter definitely lacks this option.
  • The gadget also slightly widens a visible code area to make it better readable and hides the annoying tool's "big green dot" ad that appeared in the right top corner of every formatted code snippet.
Complete code of the gadget

I used a CDN cdnjs.cloudflare.com in my examples (found it in Google). I have no information who maintains this CDN but it works faster than the original site of the tool.

<!-- The gadget that refines the formatted output produced by the Syntax Highlighter. Author: Paul Borisov, http://paulborisov.blogspot.fi/2016/09/simple-and-functional-source-code.html -->
<link href="https://cdnjs.cloudflare.com/ajax/libs/SyntaxHighlighter/3.0.83/styles/shCore.css" rel="stylesheet" type="text/css" />
<link href="https://cdnjs.cloudflare.com/ajax/libs/SyntaxHighlighter/3.0.83/styles/shThemeDefault.css" rel="stylesheet" type="text/css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.0/jquery.min.js" type="text/javascript"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/SyntaxHighlighter/3.0.83/scripts/shCore.js" type="text/javascript"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/SyntaxHighlighter/3.0.83/scripts/shAutoloader.js" type="text/javascript"></script>
<script type="text/javascript">
  jQuery(document).ready(function() {
    SyntaxHighlighter.autoloader(
      "C# c-sharp csharp https://cdnjs.cloudflare.com/ajax/libs/SyntaxHighlighter/3.0.83/scripts/shBrushCSharp.js",
      "PowerShell ps powershell https://cdnjs.cloudflare.com/ajax/libs/SyntaxHighlighter/3.0.83/scripts/shBrushPowerShell.js",
      "CSS css https://cdnjs.cloudflare.com/ajax/libs/SyntaxHighlighter/3.0.83/scripts/shBrushCss.js",
      "JavaScript js jscript javascript https://cdnjs.cloudflare.com/ajax/libs/SyntaxHighlighter/3.0.83/scripts/shBrushJScript.js",
      "Java java https://cdnjs.cloudflare.com/ajax/libs/SyntaxHighlighter/3.0.83/scripts/shBrushJava.js",
      "SQL sql https://cdnjs.cloudflare.com/ajax/libs/SyntaxHighlighter/3.0.83/scripts/shBrushSql.js",
      "XML xml xhtml xslt html xhtml https://cdnjs.cloudflare.com/ajax/libs/SyntaxHighlighter/3.0.83/scripts/shBrushXml.js",
      "ActionScript3 as3 actionscript3 https://cdnjs.cloudflare.com/ajax/libs/SyntaxHighlighter/3.0.83/scripts/shBrushAS3.js",
      "Bash/shell bash shell https://cdnjs.cloudflare.com/ajax/libs/SyntaxHighlighter/3.0.83/scripts/shBrushBash.js",
      "ColdFusion cf coldfusion https://cdnjs.cloudflare.com/ajax/libs/SyntaxHighlighter/3.0.83/scripts/shBrushColdFusion.js",
      "C++ cpp c https://cdnjs.cloudflare.com/ajax/libs/SyntaxHighlighter/3.0.83/scripts/shBrushCpp.js",
      "CSS css https://cdnjs.cloudflare.com/ajax/libs/SyntaxHighlighter/3.0.83/scripts/shBrushCss.js",
      "Delphi delphi pas pascal https://cdnjs.cloudflare.com/ajax/libs/SyntaxHighlighter/3.0.83/scripts/shBrushDelphi.js",
      "Diff diff patch https://cdnjs.cloudflare.com/ajax/libs/SyntaxHighlighter/3.0.83/scripts/shBrushDiff.js",
      "Erlang erl erlang https://cdnjs.cloudflare.com/ajax/libs/SyntaxHighlighter/3.0.83/scripts/shBrushErlang.js",
      "Groovy groovy https://cdnjs.cloudflare.com/ajax/libs/SyntaxHighlighter/3.0.83/scripts/shBrushGroovy.js",
      "JavaFX jfx javafx https://cdnjs.cloudflare.com/ajax/libs/SyntaxHighlighter/3.0.83/scripts/shBrushJavaFX.js",
      "Perl perl pl https://cdnjs.cloudflare.com/ajax/libs/SyntaxHighlighter/3.0.83/scripts/shBrushPerl.js",
      "PHP php https://cdnjs.cloudflare.com/ajax/libs/SyntaxHighlighter/3.0.83/scripts/shBrushPhp.js",
      "PlainText plain text https://cdnjs.cloudflare.com/ajax/libs/SyntaxHighlighter/3.0.83/scripts/shBrushPlain.js",
      "Python py python https://cdnjs.cloudflare.com/ajax/libs/SyntaxHighlighter/3.0.83/scripts/shBrushPython.js",
      "Ruby rails ror ruby https://cdnjs.cloudflare.com/ajax/libs/SyntaxHighlighter/3.0.83/scripts/shBrushRuby.js",
      "Scala scala https://cdnjs.cloudflare.com/ajax/libs/SyntaxHighlighter/3.0.83/scripts/shBrushScala.js",
      "VisualBasic vb vbnet https://cdnjs.cloudflare.com/ajax/libs/SyntaxHighlighter/3.0.83/scripts/shBrushVb.js"
    );
    SyntaxHighlighter.config.bloggerMode = true;
    SyntaxHighlighter.all();
    
    // I noticed a small bug of the highliter and fixed it (Paul Borisov, 4.9.2016).
    jQuery("code[class*='comments']:last").each(function() {
      jQuery(this).text(jQuery(this).text().replace(/((<)|(<))\/input\.\+\?\\((>)|(>))/,""));
    });
    var tmp = setInterval(function() {
      jQuery("code[class*='plain']:last").each(function() {
        if( jQuery(this).parent().html().toLowerCase().match(/<code.+>((<)|(<))\/input\.\+\?\\((>)|(>))<\/code>/) != null ) {
          jQuery(this).parent().detach();
          clearInterval(tmp);
        }
      });
    }, 200);
    // Remove the ugly looking "big green dot" component's ad
    jQuery("div.syntaxhighlighter > div.toolbar").detach();
    
    jQuery.fn.selTextShl = function() {
      if( this.length === 0 ) return;
      var doc = document
        , text = this[0]
        , range, selection
      ;    
      if (doc.body.createTextRange) {
        range = document.body.createTextRange();
        range.moveToElementText(text);
        range.select();
      } else if (window.getSelection) {
        selection = window.getSelection();        
        range = document.createRange();
        range.selectNodeContents(text);
        selection.removeAllRanges();
        selection.addRange(range);
      }
    }

    jQuery("pre[class*='brush:']").each(function() {
      var uniqueId = Math.random().toString(16).slice(2);
      var idTop = "btnSelectCode" + uniqueId + "top";
      var idBottom = "btnSelectCode" + uniqueId + "bottom";
      if( !jQuery(this).parent().hasClass("code-block-container-shl") ) {
        var htmlButton = '<div class="code-selection-container-shl"><div id="{0}">Select the code</div></div>';
        jQuery(this).wrap('<div class="code-block-container-shl"></div>');
        if( !jQuery(this).hasClass("hide-top-button") ) {
          var buttonTop = htmlButton.replace("{0}", idTop);
          jQuery(this).before(buttonTop);
        }
        if( !jQuery(this).hasClass("hide-bottom-button") ) {
          var buttonBottom = htmlButton.replace("{0}", idBottom);
          jQuery(this).after(buttonBottom);
        }

        function SelectTextInCodeContainerShl(codeContent) {
          var codeTopContainer = codeContent.closest(".syntaxhighlighter");
          // Get and keep scroll positions
          var scrollLeft = codeTopContainer.scrollLeft();
          var scrollTop = jQuery(document).scrollTop();
          codeContent.selTextShl();
          // Next line eliminates unpleasant bug with changed scroll positions in IE.
          codeTopContainer.scrollLeft(0);
          jQuery(document).scrollTop(scrollTop);       
        }
        jQuery("#" + idTop + ",#" + idBottom).click(function() {
          var codeContainer = jQuery(this).parents(".code-block-container-shl");
          var codeContent = codeContainer.find("td.code:first");
          if( codeContent.length === 0 ) {
            codeContainer.selTextShl();  // In the case the highlighter has failed to load just select the content of <pre>...</pre>
          } else {
            SelectTextInCodeContainerShl(codeContent);
          }
        });
      }
    });
  });
</script>
<style type="text/css">
  .code-selection-container-shl {text-align: left;}
  .code-selection-container-shl > div {margin-left:5px;cursor:pointer;border:1px #ccc solid;padding:2px 2px 2px 8px;width:100px;display:inline-block;border-radius:7px 7px;}
  .code-block-container-shl {width:875px !important;overflow:hidden;margin-left:-5px;}
  div.syntaxhighlighter {padding-top:10px;padding-bottom:10px;}
  div.syntaxhighlighter > div.toolbar {display: none !important;}
  div.syntaxhighlighter table td.gutter .line {padding:0px 5px 0px 0px !important;}
</style>

Split into separate code blocks for HTML, JavaScript, and CSS to demonstrate different brushes

HTML

<link href="https://cdnjs.cloudflare.com/ajax/libs/SyntaxHighlighter/3.0.83/styles/shCore.css" rel="stylesheet" type="text/css" />
<link href="https://cdnjs.cloudflare.com/ajax/libs/SyntaxHighlighter/3.0.83/styles/shThemeDefault.css" rel="stylesheet" type="text/css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.0/jquery.min.js" type="text/javascript"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/SyntaxHighlighter/3.0.83/scripts/shCore.js" type="text/javascript"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/SyntaxHighlighter/3.0.83/scripts/shAutoloader.js" type="text/javascript"></script>

JavaScript

<script type="text/javascript">
  jQuery(document).ready(function() {
    SyntaxHighlighter.autoloader(
      "C# c-sharp csharp https://cdnjs.cloudflare.com/ajax/libs/SyntaxHighlighter/3.0.83/scripts/shBrushCSharp.js",
      "PowerShell ps powershell https://cdnjs.cloudflare.com/ajax/libs/SyntaxHighlighter/3.0.83/scripts/shBrushPowerShell.js",
      "CSS css https://cdnjs.cloudflare.com/ajax/libs/SyntaxHighlighter/3.0.83/scripts/shBrushCss.js",
      "JavaScript js jscript javascript https://cdnjs.cloudflare.com/ajax/libs/SyntaxHighlighter/3.0.83/scripts/shBrushJScript.js",
      "Java java https://cdnjs.cloudflare.com/ajax/libs/SyntaxHighlighter/3.0.83/scripts/shBrushJava.js",
      "SQL sql https://cdnjs.cloudflare.com/ajax/libs/SyntaxHighlighter/3.0.83/scripts/shBrushSql.js",
      "XML xml xhtml xslt html xhtml https://cdnjs.cloudflare.com/ajax/libs/SyntaxHighlighter/3.0.83/scripts/shBrushXml.js",
      "ActionScript3 as3 actionscript3 https://cdnjs.cloudflare.com/ajax/libs/SyntaxHighlighter/3.0.83/scripts/shBrushAS3.js",
      "Bash/shell bash shell https://cdnjs.cloudflare.com/ajax/libs/SyntaxHighlighter/3.0.83/scripts/shBrushBash.js",
      "ColdFusion cf coldfusion https://cdnjs.cloudflare.com/ajax/libs/SyntaxHighlighter/3.0.83/scripts/shBrushColdFusion.js",
      "C++ cpp c https://cdnjs.cloudflare.com/ajax/libs/SyntaxHighlighter/3.0.83/scripts/shBrushCpp.js",
      "CSS css https://cdnjs.cloudflare.com/ajax/libs/SyntaxHighlighter/3.0.83/scripts/shBrushCss.js",
      "Delphi delphi pas pascal https://cdnjs.cloudflare.com/ajax/libs/SyntaxHighlighter/3.0.83/scripts/shBrushDelphi.js",
      "Diff diff patch https://cdnjs.cloudflare.com/ajax/libs/SyntaxHighlighter/3.0.83/scripts/shBrushDiff.js",
      "Erlang erl erlang https://cdnjs.cloudflare.com/ajax/libs/SyntaxHighlighter/3.0.83/scripts/shBrushErlang.js",
      "Groovy groovy https://cdnjs.cloudflare.com/ajax/libs/SyntaxHighlighter/3.0.83/scripts/shBrushGroovy.js",
      "JavaFX jfx javafx https://cdnjs.cloudflare.com/ajax/libs/SyntaxHighlighter/3.0.83/scripts/shBrushJavaFX.js",
      "Perl perl pl https://cdnjs.cloudflare.com/ajax/libs/SyntaxHighlighter/3.0.83/scripts/shBrushPerl.js",
      "PHP php https://cdnjs.cloudflare.com/ajax/libs/SyntaxHighlighter/3.0.83/scripts/shBrushPhp.js",
      "PlainText plain text https://cdnjs.cloudflare.com/ajax/libs/SyntaxHighlighter/3.0.83/scripts/shBrushPlain.js",
      "Python py python https://cdnjs.cloudflare.com/ajax/libs/SyntaxHighlighter/3.0.83/scripts/shBrushPython.js",
      "Ruby rails ror ruby https://cdnjs.cloudflare.com/ajax/libs/SyntaxHighlighter/3.0.83/scripts/shBrushRuby.js",
      "Scala scala https://cdnjs.cloudflare.com/ajax/libs/SyntaxHighlighter/3.0.83/scripts/shBrushScala.js",
      "VisualBasic vb vbnet https://cdnjs.cloudflare.com/ajax/libs/SyntaxHighlighter/3.0.83/scripts/shBrushVb.js"
    );
    SyntaxHighlighter.config.bloggerMode = true;
    SyntaxHighlighter.all();
    
    // I noticed a small bug of the highliter and fixed it (Paul Borisov, 4.9.2016).
    jQuery("code[class*='comments']:last").each(function() {
      jQuery(this).text(jQuery(this).text().replace(/((<)|(<))\/input\.\+\?\\((>)|(>))/,""));
    });
    var tmp = setInterval(function() {
      jQuery("code[class*='plain']:last").each(function() {
        if( jQuery(this).parent().html().toLowerCase().match(/<code.+>((<)|(<))\/input\.\+\?\\((>)|(>))<\/code>/) != null ) {
          jQuery(this).parent().detach();
          clearInterval(tmp);
        }
      });
    }, 200);
    // Remove the ugly looking "big green dot" component's ad
    jQuery("div.syntaxhighlighter > div.toolbar").detach();
    
    jQuery.fn.selTextShl = function() {
      if( this.length === 0 ) return;
      var doc = document
        , text = this[0]
        , range, selection
      ;    
      if (doc.body.createTextRange) {
        range = document.body.createTextRange();
        range.moveToElementText(text);
        range.select();
      } else if (window.getSelection) {
        selection = window.getSelection();        
        range = document.createRange();
        range.selectNodeContents(text);
        selection.removeAllRanges();
        selection.addRange(range);
      }
    }

    jQuery("pre[class*='brush:']").each(function() {
      var uniqueId = Math.random().toString(16).slice(2);
      var idTop = "btnSelectCode" + uniqueId + "top";
      var idBottom = "btnSelectCode" + uniqueId + "bottom";
      if( !jQuery(this).parent().hasClass("code-block-container-shl") ) {
        var htmlButton = '<div class="code-selection-container-shl"><div id="{0}">Select the code</div></div>';
        jQuery(this).wrap('<div class="code-block-container-shl"></div>');
        if( !jQuery(this).hasClass("hide-top-button") ) {
          var buttonTop = htmlButton.replace("{0}", idTop);
          jQuery(this).before(buttonTop);
        }
        if( !jQuery(this).hasClass("hide-bottom-button") ) {
          var buttonBottom = htmlButton.replace("{0}", idBottom);
          jQuery(this).after(buttonBottom);
        }

        function SelectTextInCodeContainerShl(codeContent) {
          var codeTopContainer = codeContent.closest(".syntaxhighlighter");
          // Get and keep scroll positions
          var scrollLeft = codeTopContainer.scrollLeft();
          var scrollTop = jQuery(document).scrollTop();
          codeContent.selTextShl();
          // Next line eliminates unpleasant bug with changed scroll positions in IE.
          codeTopContainer.scrollLeft(0);
          jQuery(document).scrollTop(scrollTop);       
        }
        jQuery("#" + idTop + ",#" + idBottom).click(function() {
          var codeContainer = jQuery(this).parents(".code-block-container-shl");
          var codeContent = codeContainer.find("td.code:first");
          if( codeContent.length === 0 ) {
            codeContainer.selTextShl();  // In the case the highlighter has failed to load just select the content of <pre>...</pre>
          } else {
            SelectTextInCodeContainerShl(codeContent);
          }
        });
      }
    });
  });
</script>

CSS

<style type="text/css">
  .code-selection-container-shl {text-align: left;}
  .code-selection-container-shl > div {margin-left:5px;cursor:pointer;border:1px #ccc solid;padding:2px 2px 2px 8px;width:100px;display:inline-block;border-radius:7px 7px;}
  .code-block-container-shl {width:875px !important;overflow:hidden;margin-left:-5px;}
  div.syntaxhighlighter {padding-top:10px;padding-bottom:10px;}
  div.syntaxhighlighter > div.toolbar {display: none !important;}
  div.syntaxhighlighter table td.gutter .line {padding:0px 5px 0px 0px !important;}
</style>

How to use my custom gadget

Obviously, this custom gadget is free. You can add it to your own blog as well if you wish. Just open your blog settings, change to Layout, add an "HTML/JavaScript gadget" into a section "footer-<n>" of your blog's layout, copy-paste the complete source code of the gadget shown above, and save changes..
  • Use the button "Select the code", copy-paste and save changes.

Next add your source code into <pre class="brush; <acronym>">...</pre> as described above in the chapter "My choice" and save the post. After that, your inserted source code should be highlighted.

No comments:

Post a Comment