/**
 * Code Formatter
 * http://kellin.cnblogs.com/
 *
 * This library was lastly modified at Oct 6, 2007. You can get the latest version
 * from the url: http://www.cnblogs.com/Files/Kellin/CodeFormatter.js.
 *
 * This library is a free software. You can redistribute it and/or modify it without
 * the publisher's approval. But I hope you can include the author's name and his blog's
 * address in your copy. I truely appreciate that.
 *
 * _______________________________________________________
 * Please note that the library has not been fully tested. It may result in your
 * application's crash or even cause damage to your computers. You must be fully
 * reponsible for that as you choose this library and therefore understand the possible
 * security risk. The author cannot provide any warranties.
 */


// ---------------------------------------------------------
// CHAR declaration
//
var CHAR = {
	a : 97,
	A : 65,
	z : 122,
	Z : 90,
	n0 : 48,
	n9 : 57,
	doubleQuote : 34, // "
	singleQuote : 39, // '
	at :          64, // @
	star :        42, // *
	slash :       47, // /
	backslash :   92, // \
	newline :     10, // \n
	underscore :  95  // _
};

// ---------------------------------------------------------
// Function prototype methods ( from ASP.NET Ajax Library )
// - $registerClass
// - $resolveInheritance
// - $initializeBase
//
Function.prototype.$registerClass = function(typeName, baseType) {
	var parsedName;
	try{
		parsedName = eval(typeName);
	}catch(e){
		throw new Error("Invalid type name: " + typeName);
	}
	if( parsedName != this ){
		throw new Error("Invalid type name: " + typeName);
	}

	this.prototype.ctor = this;
	this.__typeName = typeName;
	this.__class = true;
	if( baseType ){
		this.__baseType = baseType;
		this.__basePrototypePending = true;
	}
}
Function.prototype.$resolveInheritance = function() {
	if( this.__basePrototypePending ){
		var baseType = this.__baseType;
		baseType.$resolveInheritance();

		for(var memberName in baseType.prototype){
			var memberValue = baseType.prototype[memberName];
			if(!this.prototype[memberName]){
				this.prototype[memberName] = memberValue;
			}
		}

		delete this.__basePrototypePending;
	}
}
Function.prototype.$initializeBase = function(instance,baseArguments) {
	this.$resolveInheritance();
	if( this.__baseType ){
		if( !baseArguments ){
			this.__baseType.apply(instance);
		}else{
			this.__baseType.apply(instance, arguments);
		}
	}
	return instance;
}

// ---------------------------------------------------------
// Command methods
//
String.prototype.equals = function(s,i) {
	if(!s || typeof(s)!='string' || s.length!=this.length){
		return false;
	}
	if(i == true){
		return this.toLowerCase() == s.toLowerCase();
	}else{
		return this == s;
	}
}

String.prototype.indexOfAny = function(arr, startIndex) {
	if(!startIndex){
		startIndex = 0;
	}
	if(startIndex<0){
		throw new Error("'startIndex' must be greater than 0.");
	}
	var minPos = -1;
	for(var i=0; i<arr.length; i++){
		var pos = this.indexOf(arr[i], startIndex);
		if( pos == startIndex ){
			return pos;
		}
		if( pos >= 0 && (pos < minPos || minPos == -1) ){
			minPos = pos;
		}
	}
	return minPos;
}


String.prototype.endsWith = function(suffix) {
	return (this.substr(this.length - suffix.length) == suffix);
}

String.prototype.startsWith = function(prefix) {
	return (this.substr(0, prefix.length) == prefix);
}

String.prototype.trim = function() {
	if (arguments.length != 0){
		throw new Error('No argument is expected.');
	}
	return this.replace(/^\s+|\s+$/g, '');
}


String.isNullOrEmpty = function(str) {
	return (str==null || typeof(str)!='string' || str.length==0);
}


// ---------------------------------------------------------
// Kellin declaration
// - $findParent
// - $addEvent
//
if(typeof(window.Kellin)=='undefined'){
	window.Kellin = {}
}
Kellin.$findParent = function(src,tag) {
	while(src && src.parentNode){
		src = src.parentNode;
		if(src.nodeType == 1 && src.tagName.toLowerCase() == tag.toLowerCase()){
			return src;
		}
	}
	return null;
}
Kellin.$addEvent = function(obj, type ,fn) {
	if(typeof(obj.attachEvent)!='undefined'){
		obj.attachEvent('on'+type, fn);
	}else{
		obj.addEventListener(type, fn, false);
	}
}

// --------------------------------------
// StringBuilder class ( from ASP.NET Ajax Library )
//
Kellin.StringBuilder = function(initialText) {
	this._parts = !String.isNullOrEmpty(initialText) ? [initialText] : [];
	this._value = {};
	this._len = 0;
}
Kellin.StringBuilder.prototype = {
	append : function( text ) {
		if(!String.isNullOrEmpty(text)){
			this._parts[this._parts.length] = text;
		}
	},

	appendLine : function( text ) {
		this._parts[this._parts.length] = String.isNullOrEmpty(text) ? '\r\n' : text + '\r\n';
	},

	clear : function() {
		if (arguments.length != 0){
			throw new Error('No argument is expected.');
		}
		this._parts = [];
		this._value = {};
		this._len = 0;
	},

	isEmpty : function() {
		if (arguments.length != 0){
			throw new Error('No argument is expected.');
		}
		if (this._parts.length == 0) {
			return true;
		}
		return this.toString() == '';
	},

	toString : function( separator ) {
		separator = separator || '';
		var parts = this._parts;
		if (this._len != parts.length) {
			this._value = {};
			this._len = parts.length;
		}
		var val = this._value;
		if (typeof(val[separator]) == 'undefined') {
			if (separator != '') {
				for (var i = 0; i < parts.length;) {
					if (String.isNullOrEmpty(parts[i])) {
						parts.splice(i, 1);
					}
					else {
						i++;
					}
				}
			}
			val[separator] = this._parts.join(separator);
		}
		return val[separator];
	}
}


// --------------------------------------
// Kellin.CodeFormatter class
//
Kellin.CodeFormatter = function(src, srcFormatter) {
	this._source = src;
	this._srcFormatter = srcFormatter;
}

Kellin.CodeFormatter.prototype.update = function(){
	var p = this._source;
	p.className += " source_code";
	var m = p.innerHTML.trim();
	if(!m || m.length==0){
		return;
	}

	if( this._srcFormatter && !this._srcFormatter._lineNumbers ){
		// Processes the content to highlight keywords, comments and string literals
		m = this._srcFormatter.formatCode(m);
		p.innerHTML = "<pre>" + m + "</pre>";
	}
	else{
		// Adds line numbers
		m = m.replace(/\r\n/g, '\n');
		var lines = m.split('\n').length;
		var ln = '';
		for(var i=1;i<=lines;i++){
			ln += '<div>' + i + '</div>';
		}

		// Processes the content to highlight keywords, comments and string literals
		if(this._srcFormatter){
			m = this._srcFormatter.formatCode(m);
		}

		p.innerHTML = '<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse;"><tr><td class="code_lineNumbers">&nbsp;</td><td class="code_codeZone"><pre>&nbsp;</pre></td></tr></table>';
		var cells = p.childNodes[0].rows[0].cells;
		cells[0].innerHTML = ln;
		cells[1].innerHTML = "<pre>" + m + "</pre>";
	}
}


// ---------------------------------------------------------
// Kellin.CodeFormatter.performFormatting
//
Kellin.CodeFormatter.performFormatting = function() {
	var nodes = document.getElementsByTagName("pre");
	for(var i=0;i<nodes.length;i++){
		var node = nodes[i];

		// The attribute "sourceCode" is required.
		var code = node.getAttribute("sourceCode");
		if(String.isNullOrEmpty(code) || code.trim().length==0){
			continue;
		}

		// Has the element already been parsed?
		if ( typeof(node.__formatted) != 'undefined' ){
			continue;
		} else {
			node.__formatted = true;
		}

		var formatter;
		switch( code.toUpperCase() ){
			case "C#":
				formatter = new Kellin.CSharpFormat();
				break;

			case "JS":
			case "JSCRIPT":
			case "JAVASCRIPT":
				formatter = new Kellin.JavaScriptFormat();
				break;

			// Other supported source types

			case "C":
			case "C++":
				formatter = new Kellin.CPlusPlusFormat();
				break;

			case "JAVA":
				formatter = new Kellin.CodeFormat();
				break;

			default:
				formatter = new Kellin.SourceFormat();
				break;
		}

		var cf = new Kellin.CodeFormatter(node, formatter);
		cf.update();
	}
}

// ---------------------------------------------------------
// Method registerStyleSheet
//
Kellin.CodeFormatter.registerStyleSheet = function() {
	var css = '.source_code, .source_code pre, .source_code td { background-color: #e6e6e6; color: black; font-family: \u65b0\u5b8b\u4f53, Verdana, Tahoma; border: 0; padding: 0; margin: 0; vertical-align: top; }'
		+ '.source_code { overflow: auto; overflow-x: auto; overflow-y: visible; padding: 8px; margin: 10px 0; }'
		+ '.source_code .k { color: blue; }'
		+ '.source_code .c { color: green }'
		+ '.source_code .s { color: #800000 }'
		+ '.source_code * { font-size: 12px !important; line-height: normal !important; }'
		+ '.source_code td.code_codeZone { padding: 0 0 0 12px; border-color: #6ce26c; border-style: solid; border-width: 0 0 0 1px; }'
		+ '.source_code td.code_lineNumbers { text-align: right; color: #2b91af; padding: 0 2px 0 0; }';

	if( typeof(document.createStyleSheet) != 'undefined' ){
		// IE
		var s = document.createStyleSheet();
		s.cssText = css;
	}else{
		var s = document.createElement("style");
		s.type = "text/css";
		s.media = "all";
		var objText = document.createTextNode(css);
		s.appendChild(objText);
		document.getElementsByTagName('HEAD')[0].appendChild(s);
	}
}

// ---------------------------------------------------------
// Kellin.SourceFormat class
//
Kellin.SourceFormat = function() {
	this._tabSpaces = 4;
	this._lineNumbers = true;
}
Kellin.SourceFormat.$registerClass('Kellin.SourceFormat',null);

Kellin.SourceFormat.prototype = {
	get_tab : function() {
		if( this._tabSpaces < 0 ){
			throw new Error( "_tabSpaces should be greater than zero." );
		}
		var tab = '';
		for(var i=0; i<this._tabSpaces; i++){
			tab += '\u0020';
		}
		return tab;
	},

	// The main method that the inherited classes should implement.
	//
	formatCode : function(s) {
		if(String.isNullOrEmpty(s)){
			return s;
		}

		return s.replace(/\t/gm, this.get_tab());
	}
}

// ---------------------------------------------------------
// Kellin.CodeFormat class ( base: Kellin.SourceFormat )
//
Kellin.CodeFormat = function() {
	Kellin.CodeFormat.$initializeBase(this);

	// Initializes an instance of RegExp class
	var words = this.get_keywords();

	if( !String.isNullOrEmpty(words) ){

		var a = words.replace(/\s{1,}/gm, '\u0020');
		var r = '\\b(' + a.split('\u0020').join('|') + ')\\b';
		if( this.get_caseSensitive ){
			this._keywordsRegex = new RegExp(r, "gm");
		}else{
			this._keywordsRegex = new RegExp(r, "igm");
		}

	}
	else{

		this._keywordsRegex = null;

	}
}
Kellin.CodeFormat.$registerClass('Kellin.CodeFormat', Kellin.SourceFormat);

Kellin.CodeFormat.prototype = {
	get_keywords : function() {
		// Returns a list of keywords defined in each language.
		// Must be separated with spaces.
		return null;
	},

	get_caseSensitive : function() {
		return true;
	},

	// Formats the specified code segment.
	//
	formatCode : function(s) {
		if(String.isNullOrEmpty(s)){
			return s;
		}

		// Replaces the tabs as white spaces.
		s = s.replace(/\t/gm, this.get_tab());

		var sb = new Kellin.StringBuilder();
		var start = 0;
		var end = s.length;

		while( start < end ){
			var pos = s.indexOfAny(['//', '/*', '"', '\''], start);
			if( pos < 0 || pos > end ){
				this.f_common(sb, s, start, end);
				break;
			}

			var c = s.charCodeAt(pos);
			var nextPos;
			var className;
			if( c == CHAR.doubleQuote ){

				// Processes string literals
				className = 's';
				nextPos = this.get_string(s, start, pos, end);

			}else if(c == CHAR.slash){

				// Process comments
				className = 'c';
				if( s.charCodeAt(pos+1)==CHAR.star ){
					nextPos = this.get_mcomment(s, pos, end);
				}else{
					nextPos = this.get_scomment(s, pos, end);
				}

			}else{

				// Processes character literals, such as '\u0020', 'a'.
				className = 's';
				nextPos = this.get_quotedChar(s, pos, end);

			}

			this.f_common(sb, s, start, pos);

			sb.append( '<span class="' + className + '">' );
			sb.append( s.substring(pos, nextPos) );
			sb.append( '</span>' );
			start = nextPos;
		}

		return sb.toString();
	},

	// Processes the ordinal code which does not contain string/character
	// literals and comments to highlight keywords.
	//
	f_common : function(sb, s, start, end){
		s = s.substring(start, end);
		if ( this._keywordsRegex != null ){
			sb.append( s.replace( this._keywordsRegex, '<span class="k">$1</span>' ) );
		}else{
			sb.append( s );
		}
	},

	// Finds the end position of a single line string.
	get_string : function(s, start, pos, end) {
		return this.get_quoted(s, pos, end, '"');
	},

	get_quoted : function(s, start, end, quoteMark) {
		start ++;
		var pos = start;

		while( pos < end ){
			pos = s.indexOfAny([quoteMark, '\n'], pos);
			if(pos < 0 || pos >= end){
				return end;
			}else{
				var c = s.charCodeAt(pos);
				if( c == CHAR.newline ){
					return pos + 1;
				}

				if( pos == start || s.charCodeAt(pos-1) != CHAR.backslash ){
					return pos + 1;
				}
				pos ++;
			}
		}

		return end;
	},

	// Finds the end position of a quoted charactor.
	// e.g. 'a', '\u0020', ...
	get_quotedChar : function(s, start, end) {
		return this.get_quoted(s, start, end, '\'');
	},

	// Finds the end position of a multiline comment block: "/* */"
	get_mcomment : function(s, start, end) {
		start += 2;
		// Finds the end of a multiline comment
		pos = s.indexOf("*/", start);
		if(pos >= 0 && pos < end){
			return pos + 2;
		}else{
			return end;
		}
	},

	// Finds the end position of a single line comment: "//"
	get_scomment : function(s, start, end) {
		start += 2;
		// Finds the end of a multiline comment
		pos = s.indexOfAny(['//', '\n'], start);
		if( pos < 0 || pos >= end ){
			return end;
		}

		if( s.charCodeAt(pos) == CHAR.newline ){
			return pos + 1;
		}
		return pos + 2;
	}
};

// ---------------------------------------------------------
// Kellin.CLikeFormat class ( base: Kellin.CodeFormat )
// We should process the preprocess tags for C-like languages.
//
Kellin.CLikeFormat = function() {
	Kellin.CLikeFormat.$initializeBase(this);

	this._preprocessorRegex = this.get_preprocessorRegex();
};
Kellin.CLikeFormat.$registerClass('Kellin.CLikeFormat', Kellin.CodeFormat);

Kellin.CLikeFormat.prototype = {
	// Processes the ordinal code, which does not contain string/character
	// literals and comments, to highlight preprocess tags and/or keywords
	//
	f_common : function(sb, s, start, end){
		s = s.substring(start, end);
		var lines = s.split('\n');

		for(var i=0; i<lines.length; i++){
			if( i > 0 ){
				sb.appendLine();
			}

			var line = lines[i];
			var arr = this._preprocessorRegex.exec( line );
			if ( arr == null ){
				// Finds all the keywords
				if( this._keywordsRegex != null ){
					sb.append( line.replace( this._keywordsRegex, '<span class="k">$1</span>' ) );
				}
				else{
					sb.append( line );
				}
			}
			else{
				// A preprocessor tag was found
				sb.append( arr[1] );
				sb.append( '<span class="k">' );
				sb.append( arr[2] );
				sb.append( '</span>' );
				sb.append( arr[3] );
			}
		}
	},

	// Returns a RegExp object to process the preprocessor tags like #region and #if
	get_preprocessorRegex : function() {
		return /^(\s*)(#\w+)(.*)$/;
	}
}

// ---------------------------------------------------------
// Kellin.CSharpFormat class
// In C#, we have to process the special case that multiline string literals
// have "@" prefixes.
//
Kellin.CSharpFormat = function() {
	Kellin.CSharpFormat.$initializeBase(this);
}
Kellin.CSharpFormat.$registerClass('Kellin.CSharpFormat', Kellin.CLikeFormat);

Kellin.CSharpFormat.prototype = {
	get_keywords : function() {
		return "abstract as base bool break byte case catch char"
			+ " checked class const continue decimal default delegate do double else"
			+ " enum event explicit extern false finally fixed float for foreach goto"
			+ " if implicit in int interface internal is lock long namespace new null"
			+ " object operator out override partial params private protected public readonly"
			+ " ref return sbyte sealed short sizeof stackalloc static string struct"
			+ " switch this throw true try typeof uint ulong unchecked unsafe ushort"
			+ " using value virtual void volatile where while yield"
			+ " get set";
	},

	// Finds the end position of a string.
	get_string : function(s, segStart, pos, end) {
		if( pos > segStart && s.charCodeAt(pos-1)==CHAR.at ){
			pos --;
			return this.get_mstring(s, pos, end);
		}else{
			return this.get_sstring(s, pos, end);
		}
	},

	// Finds the end position of a multiline string which has a prefix as @"
	get_mstring : function(s, start, end) {
		var pos = start + 2;

		while(pos < end){
			pos = s.indexOf('"', pos);
			if(pos < 0 || pos >= end){
				return end;
			}else{
				if(s.charCodeAt(pos+1) == CHAR.star){
					pos += 2;
				}else{
					// The end of the string.
					return pos + 1;
				}
			}
		}

		return end;
	},

	// Finds the end position of a single line string.
	get_sstring : function(s, start, end) {
		return this.get_quoted(s, start, end, '"');
	}
};


// ---------------------------------------------------------
// Kellin.JavaScriptFormat class
//
Kellin.JavaScriptFormat = function() {
	Kellin.JavaScriptFormat.$initializeBase(this);
}
Kellin.JavaScriptFormat.$registerClass('Kellin.JavaScriptFormat', Kellin.CodeFormat);

Kellin.JavaScriptFormat.prototype = {
	get_keywords : function() {
		return "break delete function return typeof case do if switch var catch else in"
			+ " this void continue false instanceof throw while debugger finally new"
			+ " true with default for null try abstract double goto native static"
			+ " boolean enum implements package super byte export import private"
			+ " synchronized char extends int protected throws class final interface"
			+ " public transient const float long short volatile prototype window"
	}
}


// ---------------------------------------------------------
// Kellin.CPlusPlusFormat class
//
Kellin.CPlusPlusFormat = function() {
	Kellin.CPlusPlusFormat.$initializeBase(this);
}
Kellin.CPlusPlusFormat.$registerClass('Kellin.CPlusPlusFormat', Kellin.CLikeFormat);

Kellin.CPlusPlusFormat.prototype = {
	get_keywords : function() {
		return "__asm __based __cdecl __declspec __except __far __fastcall __finally __fortran __huge __inline __int16"
			+ " __int32 __int64 __int8 __interrupt __leave __loadds __near __pascal __saveregs __segment __segname __self"
			+ " __stdcall __try __uuidof"
			+ " auto bool break case char const continue default defined do double"
			+ " else enum extern float for goto if int long register return"
			+ " short signed sizeof static struct switch"
			+ " typedef union unsigned void volatile while"
			+ " __multiple_inheritance __single_inheritance __virtual_inheritance"
			+ " catch class const_cast delete dynamic_cast explicit export false friend"
			+ " inline mutable namespace new operator private protected public"
			+ " reinterpret_cast static_cast template this throw true try typeid typename"
			+ " using virtual wchar_t"
			+ " dllexport dllimport naked thread uuid";
	}
}


// ---------------------------------------------------------
// Starts to format the entire content
//
Kellin.$addEvent(window, 'load', Kellin.CodeFormatter.performFormatting);
Kellin.CodeFormatter.registerStyleSheet();
Kellin.CodeFormatter.performFormatting();

