Jump to main navigation


New (modified) JavaScript syntax highlighter: SHJS

080817

I was having a few problems with the syntax highlighter I was using for the code blocks in this blog, so I decided to look for an alternative. There are a few out there, but my choice went to SHJS. I liked it straight away, although I wanted a couple of features that are not there. So I got my hands dirty and wrote some code to implement them. Here's a description of what I've done, in case someone has similar needs.

Using CODE instead of PRE

The first problem I found is that I use this HTML for code blocks (read the details in How to display code fragments in web pages):

<pre><code class='language here'>
// Code here
...
...
</code></pre>

Since SHJS only allows the use of PRE with the class of the language used, and the classes are prefixed with "sh_", i decided to let JavaScript fix things for me instead of going through all my posts changing the HTML (which I didn't want to do anyway). This simple JavaScript fragment did the trick:

var codes = document.getElementsByTagName("code");
for (var i=0, l=codes.length ; i<l ; i++) {
  elPRE = codes[i].parentNode;

  // skip any CODE element that is not inside a PRE element
  if (elPRE.tagName != "PRE") continue;

  // Prepare for highlighting
  switch (codes[i].className) {
    case 'css':
    case 'html':
    case 'php':
    // add other cases as needed
      elPRE.className += " sh_" + codes[i].className;
      break;
    case 'js':
      elPRE.className += " sh_javascript";
      break;
  }
}

This code simply checks all CODE elements that are children of a PRE element, and transfers the class from CODE to PRE, with the "sh_" prefix (and in the case of JavaScript fragments, changes my class "js" to "sh_javascript").

Note that this doesn't require any modification of SHJS, it's simply some code that I execute once the DOM is loaded, before calling the SHJS highlighter.

Allowing STRONG inside the code fragments

The following technique doesn't work in IE6 nor IE7. The problem is that IE performs text normalization when setting innerHTML, which breaks the PRE formatting (see The Internet Explorer innerHTML Quirk by Sebastian Redl).

I don't know of any workarounds for this. Maybe this technique could be rewritten to use DOM methods instead of innerHTML (I might give that a try, and will post the results here if I do). In the meantime, I've just filtered the code that implements this from IE (I use IE conditional compilation, but you can choose your favorite filtering method). Note that I haven't included the filtering code to keep things simple, don't forget to do it yourself.

If you're an IE user, you won't even see what the rest of this post is about, sorry.

This one was a bit trickier. Sometimes I want to emphasize certain bits of the code fragments, which I do by adding a STRONG element around those bits, like in the following example:

<pre><code class='css'>
// Some CSS code, with an emphasized line
p {
  margin-left: 10px;
  <strong>background: #e45ff8;</strong>
  text-align: center;
}
</code></pre>

After highlighting, I want the above code to look like this (remember that this doesn't work with IE):

// Some CSS code, with an emphasized line
p {
  margin-left: 10px;
  background: #e45ff8;
  text-align: center;
}

The problem is that SHJS (as well as all other JavaScript highlighters I've tried) removes that STRONG element, so I either had to chose to highlight the fragment or have the emphasized bit. After a bit of messing around with the source code, my solution has been to modify the SHJS main file to first 'hide' the STRONG tags and then bring them back after the syntax highlighting has been performed. This might not be the most efficient way to do it, but it works. Here's the code I've added to SHJS:

function sh_highlightElement(htmlDocument, element, language) {

  // Hide STRONG tags
  // IE-HIDE FROM HERE...
  element.innerHTML = element.innerHTML.replace("</strong>", "/*STRONGTAG-END*/", "g");
  element.innerHTML = element.innerHTML.replace("<strong>", "/*STRONGTAG*/", "g");
  // ...TO HERE (USING YOUR FAVORITE METHOD)

  sh_addClass(element, "sh_sourceCode");
  var inputString;
  if (element.childNodes.length === 0) {
    return;
  }
  else {
    inputString = sh_getText(element);
  }

  sh_builder.init(htmlDocument, element);
  sh_highlightString(inputString, language, sh_builder);
  sh_builder.close();

  // Restore STRONG tags
  // IE-HIDE FROM HERE...
  element.innerHTML = element.innerHTML.replace("<span class=\"sh_comment\">/*STRONGTAG*/</span>", "<strong>", "g");
  element.innerHTML = element.innerHTML.replace("<span class=\"sh_comment\">/*STRONGTAG-END*/</span>", "</strong>", "g");
  // Also need to search for STRONGTAG and STRONGTAG-END inside a comment (i.e.: no span)
  element.innerHTML = element.innerHTML.replace("/*STRONGTAG*/", "<strong>", "g");
  element.innerHTML = element.innerHTML.replace("/*STRONGTAG-END*/", "</strong>", "g");
  // ...TO HERE (USING YOUR FAVORITE METHOD)
}

Voilà! Highlighted code with emphasized bits.

There is one important limitation, in regards to where you can use the STRONG element in the code. You should not use it in comments, or at least you should be careful about it. For example, if you open the STRONG element before a comment and close it after some code, the highlighting will break. Personally I don't care too much, but of course it would be nice to fix that and make it more solid, I might try that some time.

This modification to SHJS might create problems depending on the code being displayed, for example if you use the literal /*STRONGTAG*/ in the code fragment (which I imagine is highly unlikely) or with the comments issue described above. I've tested it a fair bit and haven't found other problematic situations, but there probably are. :)

You can follow any responses to this entry through the RSS 2.0 feed. Both comments and pings are currently closed.

Comments to “New (modified) JavaScript syntax highlighter: SHJS”

Comments are closed.

Additional content and navigation

Categories

Main navigation menu