CURRENT PROJECTS
loading
CATEGORIES AND POSTS
loading
overset
DEVELOPMENT LOG FOR JIM PALMER
Posted 01/18/2009 in javascript


Jsyn is a super fast and super lightweight generic syntax highlighter written in javascript. It does not rely on any javascript library and uses a single regular expression for its lexical analysis.

The current development release, comments and all, weighs in at 4.4KB.

Jsyn hijacks the <B> HTML tag for applying styles to tokenized strings and removes the font-weight style. Using this tag allows for minimal markup to be applied to the DOM on the fly and actually tested to be the fastest instead of span, div, p, etc or any element that could be made inline. The innerHTML is being used here not because I love deviating from the standard, but because it is up to 10 times faster than using on the fly DOM element creation. As soon as innerHTML does become deprecated switching to creating DOM elements will be a sinch with jsyn.

Currently there are no specific language definitions and a generic set of keywords and datatypes. I'll be working towards including minimal language definitions for popular languages.

ASSETS As they reside at Google Code
BASIC USAGE
Include the jsyn assets:
<head>
	<script type="text/javascript" src="jsyn.js"></script>
	<link type="text/css" rel="stylesheet" href="jsyn.css"></link>
</head>
Then put the code you want highlighted in a PRE tag with the code class assigned to it:
<pre class="code">/* hello world test function */
var hw = function (delimiter) {
	return 'Hello' + delimiter + ['W','o','r','l','d'].join('');
}</pre>
Then the output of above after jsyn runs:
/* hello world test function */
var hw = function (delimiter) {
	return 'Hello' + delimiter + ['W','o','r','l','d'].join('');
}

Try it yourself by filling out a comment below and previewing it with your own code inside the <pre class="code"></pre> declaration in the message textarea.

NOTE - Ensure that you at least substitute the < and > charactes with the appropriate HTML Entities (i.e. &lt; and &gt;) in your code inside the pre block.

DEMO
Here is a demonstration of the jsyn.js core itself being highlighted:
/* jsyn - ultralight syntax highlighter; Jim Palmer - jimpalmer@gmail.com; released under MIT License */
var jsyn = function () {
	this.nodes = document.getElementsByTagName('pre');
	/* language definitions */
	var types = {common:{
			k:'break,case,catch,class,continue,delete,do,else,for,function,if,in,instanceof,new,private,protected,public,return,switch,throw,throw,try,typeof,var,watch,while',
			d:'array,bool,boolean,date,datetime,decimal,false,float,hashtable,int,int32,nan,null,string,true',
			c:/(?:\/\*(.|[\n\r])*?\*\/)|(?:\/\/[^\n\r]+[\n\r])|(?:<![-]{2,3}([\s\S](?!>))+[-]{2,3}>)/
		},sql:{
			k:'alter,begin,by,commit,create,delete,drop,exec,from,group,having,insert,join,like,on,order,rollback,select,set,table,transaction,trigger,truncate,union,update,values,where',
			d:'and,asc,between,desc,distinct,exists,inner,left,or,outer,right,top',
			c:/(?:\/\*(.|[\n\r])*?\*\/)|(?:--[^\n\r]+[\n\r])/
		},python:{
			k:'and,as,assert,break,class,continue,def,del,elif,else,except,exec,finally,for,from,global,if,import,in,is,lambda,not,or,pass,print,raise,return,try,while,with,yield',
			d:'int,str,types',
			c:/(?:#[^\n\r]+[\n\r])/
		}},dtab = 8;
	for (var node in this.nodes) {
		var n = this.nodes[node],cnc = 0,cnl = ( n.childNodes != null ? n.childNodes.length : 0 ),is = n.nodeValue || '';
		// limit to 'pre.code' selector and non-jsyn()'d blocks
		if ((n.className || '' ).indexOf('code') < 0 || (n.className || '' ).indexOf('jsyn') >= 0) continue; 
		for (;cnc < cnl; cnc++) is += n.childNodes.item(cnc).nodeValue; // capture all the content in the 'pre.code'
		var ext = new Date(),os = [],rec = 0,re = [],pi = 0,a = true,sl = is.length,c = 0,ct = types.common,
			tabs = parseInt((n.className.match(/tab([0-9]+)/) || [])[1]) || dtab;
		// find first language definition in className - otherwise default to 'common'
		for (var ty in types) if (n.className.indexOf(ty) >= 0) { ct = types[ty]; break; } // console.log(ty);
		// build the language specific tokenizers
		var r = [{c:'c',r:ct.c}, // comments
				{c:'s',r:/(?:\/\S+\/)|(?:'(?:\\'|[^'])*')|(?:"(?:\\"|[^"])*")/}, // regexp,strings
				{c:'n',r:/(?:\d+\.?\d*[%f]?)/}, // numbers
				{c:'k',r:(new RegExp('(?:'+ ct.k.split(',').join('\\s)|(?:') +')'))}, // keywords
				{c:'d',r:(new RegExp('(?:'+ ct.d.split(',').join('\\s)|(?:') +')'))}, // datatypes
				{c:'f',r:/(?:[\\\[\]\(\)\{\}:\#\@\+\-\=\*\%\&\|\.]+)|(?:==|>=|<=|!=|<<|>>)/}, // flow operators
				{c:'w',r:/(?:[A-Za-z_-]\w*)/}, // word (variables)
				{c:'t',r:(new RegExp(( tabs == dtab ? '' : '(?:\t)' )))} // reformatted tabs
				],rel = r.length;
		// build the tokenizing regexp
		for (;rec < rel;rec++) re.push( ( ( rec && r[rec].r.source ) ? '|' : '' ) + r[rec].r.source );
		for (var t = new RegExp(re.join(''),'gmi'); c < sl && (a = t.exec(is)); c++) // iterate rexexp.exec tokens
			for (rec = 0; rec < rel; rec++) // loop through each regexp type to match on token
				if (r[rec].r.source && r[rec].r.test(a[0])) { // modify token if matched on any regexp 
					// tab manipulation if not default
					if (tabs != dtab && r[rec].c == 't')
						var mp = t.lastIndex - a[0].length - 1,
							rn = Math.max(is.lastIndexOf('\r', mp), is.lastIndexOf('\n', mp))
							to = tabs - ( ( mp - Math.max(is.lastIndexOf('\t', mp), rn) ) % tabs );
					// innerHTML method - wrap found token in appropriately matched regexp type
					os.push( is.substring(pi,(t.lastIndex - a[0].length)).replace(/</g,'<').replace(/>/g,'>') +
						'<b class="'+ r[rec].c + ( r[rec].c == 't' && parseInt(to) != tabs ? to : '') +'">'+
						a[0].replace(/</g,'<').replace(/>/g,'>') +'</b>' );
					pi = t.lastIndex;
					break;
				}
		// update the newly build innerhtml (pre needed to render in IE6 properly)
		n.innerHTML = (/msie/i.test(navigator.userAgent) ? '<pre>' : '') + os.join('') + is.substring(pi,sl) + ((new Date()).getTime() - ext.getTime()) +'ms'+ (/msie/i.test(navigator.userAgent) ? '</pre>' : '');
		n.className += ( n.className ? ' ' : '' ) + 'jsyn';
	}
	return true;
};
document.attachEvent && document.attachEvent("onreadystatechange", /* IE DOM ready*/
	function(e){ document.readyState === "complete" && jsyn() && document.detachEvent(e.type, arguments.callee); }) || 
document.addEventListener && document.addEventListener("DOMContentLoaded", /* !IE DOM ready */
	function(e){ jsyn() && document.removeEventListener(e.type, arguments.callee, false); }, false);

TODO
comments
loading
new comment
NAME
EMAIL ME ON UPDATES
EMAIL (hidden)
URL
MESSAGE TAGS ALLOWED: <code> <a> <pre class="code [tab4|tabX|inline|bash]"> <br>
PREVIEW COMMENT
TURING TEST
gravatar