Sunday, July 8, 2012

Syntax highlighting of source code samples on Blogger - for a single article only


I wanted to integrate Alex Gorbatchev's SyntaxHighlihter to a blog article on Blogger. There is a multitude of instructions available but all of them have one thing in common: modification of the blog page template to include all necessary scripts and stylesheets. It will enable the syntax highlighter on every page but also make every page always load it.

Well, if you don't use a code excerpt in every post, why should your every page load the syntax highlighting engine? It should be possible to enable it just for a particular post, shouldn't it?

Yes, it is possible - with an additional JavaScript code in your blog post. If you are familiar with the usual blog template modification the following idea adds just inserting stylesheets and scripts to the page head on-demand. When you're writing a blog article with some source code you'll enter the HTML editing mode, create a <script type="text/javascript">...</script> element in and put the following code to its body: (a double-click within the code will select it all to enable putting it to clipboard easily)

(function() {

function loadCss(url){
  var head = document.getElementsByTagName("head")[0]
  var styles = head.getElementsByTagName("link")
  for (var i = 0; i < styles.length; ++i)
    if (styles[i].href.indexOf(url) >= 0)
      return
  var style = document.createElement("link")
  style.rel = "stylesheet"
  style.type = "text/css"
  style.href = url
  head.appendChild(style)
}

function loadJs(url, after){
  var head = document.getElementsByTagName("head")[0]
  var scripts = head.getElementsByTagName("script")
  var script
  for (var i = 0; i < scripts.length; ++i) {
    var current = scripts[i]
    if (current.src.indexOf(url) >= 0) {
      script = current
      break
    }
  }
  if (!script) {
    script = document.createElement('script')
    script.type = "text/javascript"
  }
  if (after)
    if (script.readyState)
      if (script.readyState == "loaded" ||
          script.readyState == "complete") {
        after()
      } else {
        var oldreadystatechange = script.onreadystatechange
        script.onreadystatechange = function() {
          if (script.readyState == "loaded" ||
              script.readyState == "complete")
            after()
          if (oldreadystatechange)
            oldreadystatechange()
        }
      }
    else {
      if (script.getAttribute("data-loaded")) {
        after()
      } else {
        script.async = false
        var oldload = script.onload
        script.onload = function() {
          script.setAttribute("data-loaded", "true")
          after()
          if (oldload)
            oldload()
        }
      }
    }
  if (!script.src) {
    script.src = url
    head.appendChild(script)
  }
}

function highlight(tags) {
  SyntaxHighlighter.config.bloggerMode = true
  for (var i = 0; i < tags.length; ++i) {
    var tag = document.getElementById(tags[i])
    SyntaxHighlighter.highlight(undefined, tag)
  }
}

// Use a public hosting site of your choice instead of "...".
loadCss(".../shCore.css")
loadCss(".../shThemeDefault.css")
loadJs(".../shCore.js", function() {
  // Add other brushes to this chain as necessary.
  loadJs(".../shBrushJScript.js", function() {
    // List IDs of elements to highlight their content.
    highlight([ "js-sample1", "js-sample2" ])
  })
})

}());

Notice the three comments above - those places you are supposed to modify according to your blog article.

1. The stylesheets and scripts must be loaded from a publicly accessible URL. You can use Alex's hosting but if you have a possibility to put the files onto your site you should prefer it; the bandwith of Alex's site is not unlimited... For example, if you have your pages on Google Sites, which may be likely because both Blogger and Sites are provided by Google, you can upload the SyntaxHighlighter support there and refer to them from the script, for example:

https://sites.google.com/site/your_name/css/shCore.css
https://sites.google.com/site/your_name/css/shThemeDefault.css
https://sites.google.com/site/your_name/js/shCore.js
https://sites.google.com/site/your_name/js/shBrushJScript.js

2. If you need multiple languages (brushes) in your article you'll load them in a chain of calls one after another. The following script is loaded first when the previous one has been finished; it would be done so anyway by the browser but it allows essentially the last expression that starts the highlighting to be executed after all scripts were processed: (see the latest list of built-in brushes and also additional ones)

loadJs(".../shCore.js", function() {           // Common
  loadJs(".../shBrushJScript.js", function() { // JavaScript
    loadJs(".../shBrushCss.js", function() {   // CSS
      // Highlight the code; all brushes are loaded now.
      highlight([ "js-sample1", "js-sample2" ])
    })
  })
})

3. When placing a code excerpt to the article, in addition to the usual attributes, you'll give the pre element a unique ID that you include in the array of element IDs sent to the function highlight as shown above:

<pre class="brush: js;" id="js-sample1">
...
</pre>

When writing HTML or XML samples, an on-line HTML encoder may help you to escape special characters. (The brush for HTML is the shBrushXml.js, btw.) You can consider minifying the JavaScript code above and the external scripts you include by the Google Closure Compiler or a similar tool. There are tools for minifying CSS files too, even on-line. For example, this is the partially minified code I've used for my articles:

(function() {
function loadCss(d){for(var b=document.getElementsByTagName("head")[0],c=b.getElementsByTagName("link"),e=0;e<c.length;++e)if(0<=c[e].href.indexOf(d))return;c=document.createElement("link");c.rel="stylesheet";c.type="text/css";c.href=d;b.appendChild(c)}
function loadJs(d,b){for(var c=document.getElementsByTagName("head")[0],e=c.getElementsByTagName("script"),a,f=0;f<e.length;++f){var g=e[f];if(0<=g.src.indexOf(d)){a=g;break}}if(!a)a=document.createElement("script"),a.type="text/javascript";if(b)if(a.readyState)if("loaded"==a.readyState||"complete"==a.readyState)b();else{var h=a.onreadystatechange;a.onreadystatechange=function(){("loaded"==a.readyState||"complete"==a.readyState)&&b();h&&h()}}else if(a.getAttribute("data-loaded"))b();else{a.async=
!1;var i=a.onload;a.onload=function(){a.setAttribute("data-loaded","true");b();i&&i()}}if(!a.src)a.src=d,c.appendChild(a)}function highlight(d){SyntaxHighlighter.config.bloggerMode=!0;for(var b=0;b<d.length;++b){var c=document.getElementById(d[b]);SyntaxHighlighter.highlight(void 0,c)}};

loadCss("https://sites.google.com/site/your_name/styles/shCore.min.css")
loadCss("https://sites.google.com/site/your_name/styles/shThemeVisualStudioLike.min.css")
loadJs("https://sites.google.com/site/your_name/scripts/shCore.min.js", function() {
  loadJs("https://sites.google.com/site/your_name/scripts/shBrushJScript.min.js", function() {
    loadJs("https://sites.google.com/site/your_name/scripts/shBrushXml.min.js", function() {
      highlight(["js-sh-loader", "js-sh-chain", "html-sh-sample", "js-sh-real"])
    })
  })
})

}());

Loading the syntax highlighting support only for articles that actually need it takes a little more care than having it loaded on all pages automatically, indeed. But you'll have your readers download less data and make fewer HTTP requests when browsing your regular pages. And when you replace you blog template with another one you won't need bother editing it to include your modifications; your blog articles will be encapsulated with all extras they need to be rendered.

Enjoy!

No comments: