8
\$\begingroup\$

Yesterday and today I've made a very basic syntax highlight.

It creates a function in the window object that handles part of the job:

(function(window){

    var f=window.highlight = function(lang, element){

        var lang_defs = f[lang];

        if(!lang_defs)
        {
            throw new TypeError( 'The language "' + lang + '" was not yet defined' );
        }

        if(!(element instanceof Element))
        {
            throw new TypeError( 'The 2nd parameter must be an Element' );
        }

        element.className += ' highlight ' + lang;

        for(var i = 0, l = lang_defs.length; i<l; i++)
        {
            var html = '';

            for(var j = 0, k = element.childNodes.length; j<k; j++)
            {
                if(element.childNodes[j].nodeType === 3)
                {
                    html += element.childNodes[j].nodeValue
                        .replace(
                            lang_defs[i].match,
                            /*shortcut to decide if the lang_defs[i].replace is one of those types
                             *if so, passes it directly
                             *otherwise, makes a string matching based on the object
                             */
                            {'string':1, 'function':1}[ typeof lang_defs[i].replace ]
                                ? lang_defs[i].replace
                                : '<' + lang_defs[i].replace.tag +
                                    ' class="' + lang_defs[i]['class'] + '">' +
                                        lang_defs[i].replace.text +
                                    '</' + lang_defs[i].replace.tag + '>'
                        );
                }
                else
                {
                    html += element.childNodes[j].outerHTML;
                }
            }
            element.innerHTML = html;

            if('function' === typeof lang_defs[i].patch)
            {
                var returned = lang_defs[i].patch.call( element );
                if('string' === typeof returned)
                {
                    element.innerHTML = returned;
                }
            }
        }
    };

    //default replace object
    f.default_replace = {'tag': 'span', 'text': '$1'};

})(Function('return this')());//just be sure that we have the real window

Each language is a property in the function, which is then read (sql example):

(function(window){

    if('function' === typeof window.highlight)
    {
        window.highlight.sql=[
            {
                'class':'string',
                'match':/([bn]?"(?:[^"]|[\\"]")*"|[bn]?'(?:[^']|[\\']')*')(?=[\b\s\(\),;\$#\+\-\*\/]|$)/g,
                'replace':window.highlight.default_replace
            },
            {
                'class':'comment',
                'match':/((?:\/\/|\-\-\s|#)[^\r\n]*|\/\*(?:[^*]|\*[^\/])*(?:\*\/|$))/g,
                'replace':window.highlight.default_replace,
                'patch':function(){
                    return this.innerHTML.replace(
                            /((?:\/\/|\-\-\s|#)[^\r\n]*|\/\*(?:[^*]|\*[^\/])*(?:\*\/|$))/g,
                            '$1</span>'
                        ).replace(//matches single-line comments
                            /<span class="comment">((?:#|-- |\/\/)(?:.|<\/span><span class="[^"]+">([^<])<\/span>)*)([\r\n]|$)/g,
                            function(_,part1,part2,part3){
                                return '<span class="comment">'+
                                    //cleans up all spans
                                    ((part1||'')+(part2||'')).replace(/<\/?span(?: class="[^"]+")?>/g,'')+
                                    '</span>'+
                                    (part3||'');
                            }
                        ).replace(//matches multi-line comments
                            /<span class="comment">(\/\*(?:[^*]|\*[^\/])+(?:\*\/(?:<\/span>)?|$))/g,
                            function(_,part1){
                                return '<span class="comment">'+
                                    //cleans up all spans
                                    ((part1||'')).replace(/<\/?span(?: class="[^"]+")?>/g,'')+
                                    '</span>';
                            }
                        );
                }
            },
            {
                /*    
                 * numbers aren't that 'regular' and many edge-cases were left behind    
                 * with the help of @MLM (http://stackoverflow.com/users/796832/mlm),    
                 * we were able to make this work.    
                 * he took over the regex and patched it all up, I did the replace string    
                 */
                'match':/((?:^|\b|\(|\s|,))(?![a-z_]+)([+\-]?\d+(?:\.\d+)?(?:[eE]-?\d+)?)((?=$|\b|\s|\(|\)|,|;))/g,
                'replace':'$1<span class="number">$2</span>$3'
            },
            {
                'class':'name',
                'match':/(`[^`]+`)/g,
                'replace':window.highlight.default_replace
            },
            {
                'class':'var',
                'match':/(@@?[a-z_][a-z_\d]*)/g,
                'replace':window.highlight.default_replace
            },
            {
                'class':'keyword',
                //the keyword replace must have an aditional check (`(?!\()` after the name), due to the function replace()
                'match':/\b(accessible|add|all|alter|analyze|and|as|asc|asensitive|before|between|bigint|binary|blob|both|by|call|cascade|case|change|char|character|check|collate|column|condition|constraint|continue|convert|create|cross|current_date|current_time|current_timestamp|current_user|cursor|database|databases|day_hour|day_microsecond|day_minute|day_second|dec|decimal|declare|default|delayed|delete|desc|describe|deterministic|distinct|distinctrow|div|double|drop|dual|each|else|elseif|enclosed|escaped|exists|exit|explain|false|fetch|float|float4|float8|for|force|foreign|from|fulltext|generated|get|grant|group|having|high_priority|hour_microsecond|hour_minute|hour_second|if|ignore|in|index|infile|inner|inout|insensitive|insert|int|int1|int2|int3|int4|int8|integer|interval|into|io_after_gtids|io_before_gtids|is|iterate|join|key|keys|kill|leading|leave|left|like|limit|linear|lines|load|localtime|localtimestamp|lock|long|longblob|longtext|loop|low_priority|master_bind|master_ssl_verify_server_cert|match|maxvalue|mediumblob|mediumint|mediumtext|middleint|minute_microsecond|minute_second|mod|modifies|natural|nonblocking|not|no_write_to_binlog|null|numeric|on|optimize|optimizer_costs|option|optionally|or|order|out|outer|outfile|parse_gcol_expr|partition|precision|primary|procedure|purge|range|read|reads|read_write|real|references|regexp|release|rename|repeat|replace(?!\()|require|resignal|restrict|return|revoke|right|rlike|schema|schemas|second_microsecond|select|sensitive|separator|set|show|signal|smallint|spatial|specific|sql|sqlexception|sqlstate|sqlwarning|sql_big_result|sql_calc_found_rows|sql_small_result|ssl|starting|stored|straight_join|table|terminated|then|tinyblob|tinyint|tinytext|to|trailing|trigger|true|undo|union|unique|unlock|unsigned|update|usage|use|using|utc_date|utc_time|utc_timestamp|values|varbinary|varchar|varcharacter|varying|virtual|when|where|while|with|write|xor|year_month|zerofill)\b/gi,
                'replace':window.highlight.default_replace
            },
            {
                'class':'func',
                'match':/\b([a-z_][a-z_\d]*)\b(?=\()/gi,
                'replace':window.highlight.default_replace
            },
            {
                'class':'name',
                'match':/\b([a-z\_][a-z_\d]*)\b/gi,
                'replace':window.highlight.default_replace
            }
        ];
    }

})(Function('return this')());

There are 2 themes: the default one and then a console-like one.

Example of the execution:

// highlight function, separated file

(function(window){
	
	var f=window.highlight = function(lang, element){

		var lang_defs = f[lang];
		
		if(!lang_defs)
		{
			throw new TypeError( 'The language "' + lang + '" was not yet defined' );
		}
		
		if(!(element instanceof Element))
		{
			throw new TypeError( 'The 2nd parameter must be an Element' );
		}
		
		element.className += ' highlight ' + lang;
		
		for(var i = 0, l = lang_defs.length; i<l; i++)
		{
			var html = '';
			
			for(var j = 0, k = element.childNodes.length; j<k; j++)
			{
				if(element.childNodes[j].nodeType === 3)
				{
					html += element.childNodes[j].nodeValue
						.replace(
							lang_defs[i].match,
							/*shortcut to decide if the lang_defs[i].replace is one of those types
							 *if so, passes it directly
							 *otherwise, makes a string matching based on the object
							 */
							{'string':1, 'function':1}[ typeof lang_defs[i].replace ]
								? lang_defs[i].replace
								: '<' + lang_defs[i].replace.tag +
									' class="' + lang_defs[i]['class'] + '">' +
										lang_defs[i].replace.text +
									'</' + lang_defs[i].replace.tag + '>'
						);
				}
				else
				{
					html += element.childNodes[j].outerHTML;
				}
			}
			element.innerHTML = html;
			
			if('function' === typeof lang_defs[i].patch)
			{
				var returned = lang_defs[i].patch.call( element );
				if('string' === typeof returned)
				{
					element.innerHTML = returned;
				}
			}
		}
	};

	//default replace object
	f.default_replace = {'tag': 'span', 'text': '$1'};

})(Function('return this')());//just be sure that we have the real window

//==========================================================

// sql syntax highlight, anothed different file

(function(window){

	if('function' === typeof window.highlight)
	{
		window.highlight.sql=[
			{
				'class':'string',
				'match':/([bn]?"(?:[^"]|[\\"]")*"|[bn]?'(?:[^']|[\\']')*')(?=[\b\s\(\),;\$#\+\-\*\/]|$)/g,
				'replace':window.highlight.default_replace
			},
			{
				'class':'comment',
				'match':/((?:\/\/|\-\-\s|#)[^\r\n]*|\/\*(?:[^*]|\*[^\/])*(?:\*\/|$))/g,
				'replace':window.highlight.default_replace,
				'patch':function(){
					return this.innerHTML.replace(
							/((?:\/\/|\-\-\s|#)[^\r\n]*|\/\*(?:[^*]|\*[^\/])*(?:\*\/|$))/g,
							'$1</span>'
						).replace(//matches single-line comments
							/<span class="comment">((?:#|-- |\/\/)(?:.|<\/span><span class="[^"]+">([^<])<\/span>)*)([\r\n]|$)/g,
							function(_,part1,part2,part3){
								return '<span class="comment">'+
									//cleans up all spans
									((part1||'')+(part2||'')).replace(/<\/?span(?: class="[^"]+")?>/g,'')+
									'</span>'+
									(part3||'');
							}
						).replace(//matches multi-line comments
							/<span class="comment">(\/\*(?:[^*]|\*[^\/])+(?:\*\/(?:<\/span>)?|$))/g,
							function(_,part1){
								return '<span class="comment">'+
									//cleans up all spans
									((part1||'')).replace(/<\/?span(?: class="[^"]+")?>/g,'')+
									'</span>';
							}
						);
				}
			},
			{
				/*    
				 * numbers aren't that 'regular' and many edge-cases were left behind    
				 * with the help of @MLM (http://stackoverflow.com/users/796832/mlm),    
				 * we were able to make this work.    
				 * he took over the regex and patched it all up, I did the replace string    
				 */
				'match':/((?:^|\b|\(|\s|,))(?![a-z_]+)([+\-]?\d+(?:\.\d+)?(?:[eE]-?\d+)?)((?=$|\b|\s|\(|\)|,|;))/g,
				'replace':'$1<span class="number">$2</span>$3'
			},
			{
				'class':'name',
				'match':/(`[^`]+`)/g,
				'replace':window.highlight.default_replace
			},
			{
				'class':'var',
				'match':/(@@?[a-z_][a-z_\d]*)/g,
				'replace':window.highlight.default_replace
			},
			{
				'class':'keyword',
				//the keyword replace must have an aditional check (`(?!\()` after the name), due to the function replace()
				'match':/\b(accessible|add|all|alter|analyze|and|as|asc|asensitive|before|between|bigint|binary|blob|both|by|call|cascade|case|change|char|character|check|collate|column|condition|constraint|continue|convert|create|cross|current_date|current_time|current_timestamp|current_user|cursor|database|databases|day_hour|day_microsecond|day_minute|day_second|dec|decimal|declare|default|delayed|delete|desc|describe|deterministic|distinct|distinctrow|div|double|drop|dual|each|else|elseif|enclosed|escaped|exists|exit|explain|false|fetch|float|float4|float8|for|force|foreign|from|fulltext|generated|get|grant|group|having|high_priority|hour_microsecond|hour_minute|hour_second|if|ignore|in|index|infile|inner|inout|insensitive|insert|int|int1|int2|int3|int4|int8|integer|interval|into|io_after_gtids|io_before_gtids|is|iterate|join|key|keys|kill|leading|leave|left|like|limit|linear|lines|load|localtime|localtimestamp|lock|long|longblob|longtext|loop|low_priority|master_bind|master_ssl_verify_server_cert|match|maxvalue|mediumblob|mediumint|mediumtext|middleint|minute_microsecond|minute_second|mod|modifies|natural|nonblocking|not|no_write_to_binlog|null|numeric|on|optimize|optimizer_costs|option|optionally|or|order|out|outer|outfile|parse_gcol_expr|partition|precision|primary|procedure|purge|range|read|reads|read_write|real|references|regexp|release|rename|repeat|replace(?!\()|require|resignal|restrict|return|revoke|right|rlike|schema|schemas|second_microsecond|select|sensitive|separator|set|show|signal|smallint|spatial|specific|sql|sqlexception|sqlstate|sqlwarning|sql_big_result|sql_calc_found_rows|sql_small_result|ssl|starting|stored|straight_join|table|terminated|then|tinyblob|tinyint|tinytext|to|trailing|trigger|true|undo|union|unique|unlock|unsigned|update|usage|use|using|utc_date|utc_time|utc_timestamp|values|varbinary|varchar|varcharacter|varying|virtual|when|where|while|with|write|xor|year_month|zerofill)\b/gi,
				'replace':window.highlight.default_replace
			},
			{
				'class':'func',
				'match':/\b([a-z_][a-z_\d]*)\b(?=\()/gi,
				'replace':window.highlight.default_replace
			},
			{
				'class':'name',
				'match':/\b([a-z\_][a-z_\d]*)\b/gi,
				'replace':window.highlight.default_replace
			}
		];
	}

})(Function('return this')());

//=========================================================

// execution example:

window.onload = function(){
    
    highlight('sql', document.getElementsByTagName('pre')[0]);
    
}
/*console theme, main file*/

.highlight, .highlight *{
	background:black;
	color:white;
	font-family:'Consolas',monospace;
	font-size:16px;
	word-wrap: break-word;
	white-space: pre;
}

/*sql style, different file*/

.highlight.sql .keyword{color:teal;}
.highlight.sql .string{color:red;}
.highlight.sql .func{color:purple;}
.highlight.sql .number{color:#0F0;}
.highlight.sql .name{color:olive;}
.highlight.sql .var{color:green;}
.highlight.sql .comment{color:gray;}
<!-- example code, found somewhere on google, with some edge cases -->

<pre>
    
CREATE TABLE `shop` (
    article INT(4) UNSIGNED ZEROFILL DEFAULT '0000' NOT NULL,
    dealer  CHAR(20)                 DEFAULT ''     NOT NULL,
    price   DOUBLE(16,2)             DEFAULT '0.00' NOT NULL,
    PRIMARY KEY(article, dealer));
INSERT INTO shop VALUES
    (1,'A',3.45),(1,'B',3.99),(2,'A',10.99),(3,'B',1.45),
    (3,'C',1.69),(3,'D',-1.25),(4,'D',19.95);
    
#This is an example of sql a = 'b', 'c'
    
SELECT MAX(article) AS article FROM shop;

SELECT s1.article, s1.dealer, s1.price
FROM shop s1
LEFT JOIN shop s2 ON s1.price &lt; s2.price
WHERE s2.article IS NULL;
    
SELECT article, MAX(price) AS price
FROM   shop
GROUP BY article;
    
SELECT article, dealer, price
FROM   shop s1
WHERE  price=(SELECT MAX(s2.price)
              FROM shop s2
              WHERE s1.article = s2.article);

SELECT @min_price:=MIN(price),@max_price:=MAX(price) FROM shop;
SELECT * FROM shop WHERE price=@min_price OR price=@max_price;
    
    /*
    
    a string =' ',
    
    */

select '1'#comment
    /*''*/ or 2;

</pre>

I'm actually happy with the results, but the comment highlighting is concerning me.

Is there anything I can do to improve the highlight syntax?
What can I do to improve the performance?
Am I handling comments correctly or should I try a different way?

\$\endgroup\$
4
  • 1
    \$\begingroup\$ Recent, related question \$\endgroup\$ – Flambino May 17 '15 at 1:23
  • 1
    \$\begingroup\$ @Flambino I know. I commented there. And that was my main inspiration. \$\endgroup\$ – Ismael Miguel May 17 '15 at 1:25
  • 2
    \$\begingroup\$ Ah, yes, I see that now. Well, now the link's there in case anyone else is interested. \$\endgroup\$ – Flambino May 17 '15 at 1:26
  • \$\begingroup\$ @Flambino Thanks a lot for the link. I should have refered that in the question. \$\endgroup\$ – Ismael Miguel May 17 '15 at 1:29
7
\$\begingroup\$

After closely watching the code, I realized that I've made a few mistakes:

Mistake 1:

The languages are being added directly as a property in the function. That's just begging for trouble!

I've added an object where all the languages will be added.

Mistake 2:

To generate the new HTML to search for the text nodes (the un-highlighted text), I was refreshing the element itself.

That means that all the styles related with that element were being refreshed over and over and over again.

Now, with a document fragment, the number of reflows was reduced to 2!

Mistake 3 (or 2.5?):

Since I was setting the class at the beggining, before any code, which was helping in the number of reflows.

This is a total waste of time for the CPU. This reflow was moved before doing any direct changes in the code, but before adding the new HTML.

Mistake 4:

The lack of 'stricness' disturbed some people.

And it is a good point!

Now, 'use strict'; is present in the code.


Now, with all the mistakes sorted out, I've also made some changes:

Change 1:

The language now can be set directly in the element, or as an optional parameter in the function.

Change 2:

If you forget to set a language, both on the element and in the parameter, it throws a friendly exception.

Also, the exception about the language being missing was adjusted.

Change 3:

You can now pass a set of elements (NodeList or HTMLCollection) and the elements will be highlighted.

The exceptions that are thrown will be handled differently, giving the other elements a chance.

The individual results for each element are returned in the form of an array.

Change 4:

@Mast pointed out something very simple and small.

The initialization had the following line:

var f=window.highlight = function(element, lang){//...

At first, it is completely unclear what f is doing.

I've changed it's name to fn, and added a description of what that variable is doing there.


After all the changes, this is the final result:

(function(window){

    'use strict';

    //fn keeps an internal reference to avoid problems with rewritting the window.highlight
    var fn = window.highlight = function(element, lang){

        'use strict';

        if(element instanceof NodeList || element instanceof HTMLCollection)
        {
            for(var i = 0, l = element.length, results = []; i<l; i++)
            {
                try
                {
                    results[i] = fn( element[i], lang );
                }
                catch(e)
                {
                    //logs the message, to give a chance to all the other elements
                    ( console.error || console.log ).call( console, e.message );

                    results[i] = false;
                }
            }
            return results;
        }

        if(!(element instanceof Element))
        {
            throw new TypeError( 'The 1st parameter must be an Element or NodeList' );
        }

        lang = lang || element.getAttribute('data-lang');

        if(!lang)
        {
            throw new TypeError( 'Missing language definition. Set the 2nd parameter or the attribute data-lang' );
        }

        var lang_defs = fn.langs[lang];

        if(!lang_defs)
        {
            throw new TypeError( 'The language "' + lang + '" was not yet defined' );
        }

        //create a document fragment, to avoid reflow and increase performance
        var fragment = document.createDocumentFragment(),
            div = document.createElement('div');

        div.innerHTML = element.innerHTML;
        fragment.appendChild(div);

        for(var i = 0, l = lang_defs.length; i<l; i++)
        {
            var html = '';

            for(var j = 0, k = div.childNodes.length; j<k; j++)
            {
                if(div.childNodes[j].nodeType === 3)
                {
                    html += div.childNodes[j].nodeValue
                        .replace(
                            lang_defs[i].match,
                            /*shortcut to decide if the lang_defs[i].replace is one of those types
                             *if so, passes it directly
                             *otherwise, makes a string matching based on the object
                             */
                            {'string':1, 'function':1}[ typeof lang_defs[i].replace ]
                                ? lang_defs[i].replace
                                : '<' + lang_defs[i].replace.tag +
                                    ' class="' + lang_defs[i]['class'] + '">' +
                                        lang_defs[i].replace.text +
                                    '</' + lang_defs[i].replace.tag + '>'
                        );
                }
                else
                {
                    html += div.childNodes[j].outerHTML;
                }
            }

            //refreshes the HTML, before doing anything else
            div.innerHTML = html;

            if('function' === typeof lang_defs[i].patch)
            {
                var returned = lang_defs[i].patch.call( div );
                if('string' === typeof returned)
                {
                    div.innerHTML = returned;
                }
            }
        }


        //only change at the end, to avoid unnecessary reflows
        element.className += ' highlight ' + lang;
        element.innerHTML = div.innerHTML;

        return true;
    };

    //default replace object
    fn.default_replace = {'tag': 'span', 'text': '$1'};

    //all the languages will be added here
    fn.langs = {};

})(Function('return this')());//just be sure that we have the real window

Example of an execution (same HTML and CSS):

//main file, containing the main function

(function(window){
	
	'use strict';
	
	//fn keeps an internal reference to avoid problems with rewritting the window.highlight
	var fn = window.highlight = function(element, lang){
		
		'use strict';
		  
		if(element instanceof NodeList || element instanceof HTMLCollection)
		{
			for(var i = 0, l = element.length, results = []; i<l; i++)
			{
				try
				{
					results[i] = fn( element[i], lang );
				}
				catch(e)
				{
					//logs the message, to give a chance to all the other elements
					( console.error || console.log ).call( console, e.message );

					results[i] = false;
				}
			}
			return results;
		}
		
		if(!(element instanceof Element))
		{
			throw new TypeError( 'The 1st parameter must be an Element or NodeList' );
		}
		
		lang = lang || element.getAttribute('data-lang');
		
		if(!lang)
		{
			throw new TypeError( 'Missing language definition. Set the 2nd parameter or the attribute data-lang' );
		}
		
		var lang_defs = fn.langs[lang];
		
		if(!lang_defs)
		{
			throw new TypeError( 'The language "' + lang + '" was not yet defined' );
		}
		
		//create a document fragment, to avoid reflow and increase performance
		var fragment = document.createDocumentFragment(),
			div = document.createElement('div');
		
		div.innerHTML = element.innerHTML;
		fragment.appendChild(div);
		
		for(var i = 0, l = lang_defs.length; i<l; i++)
		{
			var html = '';
			
			for(var j = 0, k = div.childNodes.length; j<k; j++)
			{
				if(div.childNodes[j].nodeType === 3)
				{
					html += div.childNodes[j].nodeValue
						.replace(
							lang_defs[i].match,
							/*shortcut to decide if the lang_defs[i].replace is one of those types
							 *if so, passes it directly
							 *otherwise, makes a string matching based on the object
							 */
							{'string':1, 'function':1}[ typeof lang_defs[i].replace ]
								? lang_defs[i].replace
								: '<' + lang_defs[i].replace.tag +
									' class="' + lang_defs[i]['class'] + '">' +
										lang_defs[i].replace.text +
									'</' + lang_defs[i].replace.tag + '>'
						);
				}
				else
				{
					html += div.childNodes[j].outerHTML;
				}
			}
			
			//refreshes the HTML, before doing anything else
			div.innerHTML = html;
			
			if('function' === typeof lang_defs[i].patch)
			{
				var returned = lang_defs[i].patch.call( div );
				if('string' === typeof returned)
				{
					div.innerHTML = returned;
				}
			}
		}
		
		
		//only change at the end, to avoid unnecessary reflows
		element.className += ' highlight ' + lang;
		element.innerHTML = div.innerHTML;
		
		return true;
	};

	//default replace object
	fn.default_replace = {'tag': 'span', 'text': '$1'};
	
	//all the languages will be added here
	fn.langs = {};

})(Function('return this')());//just be sure that we have the real window


//==========================================================

// sql syntax highlight, anothed different file

(function(window){
	'use strict';
	if('function' === typeof window.highlight)
	{
		window.highlight.langs.sql=[
			{
				'class':'string',
				'match':/([bn]?"(?:[^"]|[\\"]")*"|[bn]?'(?:[^']|[\\']')*')(?=[\b\s\(\),;\$#\+\-\*\/]|$)/g,
				'replace':window.highlight.default_replace
			},
			{
				'class':'comment',
				'match':/((?:\/\/|\-\-\s|#)[^\r\n]*|\/\*(?:[^*]|\*[^\/])*(?:\*\/|$))/g,
				'replace':window.highlight.default_replace,
				'patch':function(){
					'use strict';
					return this.innerHTML.replace(
							/((?:\/\/|\-\-\s|#)[^\r\n]*|\/\*(?:[^*]|\*[^\/])*(?:\*\/|$))/g,
							'$1</span>'
						).replace(//matches single-line comments
							/<span class="comment">((?:#|-- |\/\/)(?:.|<\/span><span class="[^"]+">([^<])<\/span>)*)([\r\n]|$)/g,
							function(_,part1,part2,part3){
								return '<span class="comment">'+
									//cleans up all spans
									((part1||'')+(part2||'')).replace(/<\/?span(?: class="[^"]+")?>/g,'')+
									'</span>'+
									(part3||'');
							}
						).replace(//matches multi-line comments
							/<span class="comment">(\/\*(?:[^*]|\*[^\/])+(?:\*\/(?:<\/span>)?|$))/g,
							function(_,part1){
								return '<span class="comment">'+
									//cleans up all spans
									((part1||'')).replace(/<\/?span(?: class="[^"]+")?>/g,'')+
									'</span>';
							}
						);
				}
			},
			{
				/*    
				 * numbers aren't that 'regular' and many edge-cases were left behind    
				 * with the help of @MLM (http://stackoverflow.com/users/796832/mlm),    
				 * we were able to make this work.    
				 * he took over the regex and patched it all up, I did the replace string    
				 */
				'match':/((?:^|\b|\(|\s|,))(?![a-z_]+)([+\-]?\d+(?:\.\d+)?(?:[eE]-?\d+)?)((?=$|\b|\s|\(|\)|,|;))/g,
				'replace':'$1<span class="number">$2</span>$3'
			},
			{
				'class':'name',
				'match':/(`[^`]+`)/g,
				'replace':window.highlight.default_replace
			},
			{
				'class':'var',
				'match':/(@@?[a-z_][a-z_\d]*)/g,
				'replace':window.highlight.default_replace
			},
			{
				'class':'keyword',
				//the keyword replace must have an aditional check (`(?!\()` after the name), due to the function replace()
				'match':/\b(accessible|add|all|alter|analyze|and|as|asc|asensitive|before|between|bigint|binary|blob|both|by|call|cascade|case|change|char|character|check|collate|column|condition|constraint|continue|convert|create|cross|current_date|current_time|current_timestamp|current_user|cursor|database|databases|day_hour|day_microsecond|day_minute|day_second|dec|decimal|declare|default|delayed|delete|desc|describe|deterministic|distinct|distinctrow|div|double|drop|dual|each|else|elseif|enclosed|escaped|exists|exit|explain|false|fetch|float|float4|float8|for|force|foreign|from|fulltext|generated|get|grant|group|having|high_priority|hour_microsecond|hour_minute|hour_second|if|ignore|in|index|infile|inner|inout|insensitive|insert|int|int1|int2|int3|int4|int8|integer|interval|into|io_after_gtids|io_before_gtids|is|iterate|join|key|keys|kill|leading|leave|left|like|limit|linear|lines|load|localtime|localtimestamp|lock|long|longblob|longtext|loop|low_priority|master_bind|master_ssl_verify_server_cert|match|maxvalue|mediumblob|mediumint|mediumtext|middleint|minute_microsecond|minute_second|mod|modifies|natural|nonblocking|not|no_write_to_binlog|null|numeric|on|optimize|optimizer_costs|option|optionally|or|order|out|outer|outfile|parse_gcol_expr|partition|precision|primary|procedure|purge|range|read|reads|read_write|real|references|regexp|release|rename|repeat|replace(?!\()|require|resignal|restrict|return|revoke|right|rlike|schema|schemas|second_microsecond|select|sensitive|separator|set|show|signal|smallint|spatial|specific|sql|sqlexception|sqlstate|sqlwarning|sql_big_result|sql_calc_found_rows|sql_small_result|ssl|starting|stored|straight_join|table|terminated|then|tinyblob|tinyint|tinytext|to|trailing|trigger|true|undo|union|unique|unlock|unsigned|update|usage|use|using|utc_date|utc_time|utc_timestamp|values|varbinary|varchar|varcharacter|varying|virtual|when|where|while|with|write|xor|year_month|zerofill)\b/gi,
				'replace':window.highlight.default_replace
			},
			{
				'class':'func',
				'match':/\b([a-z_][a-z_\d]*)\b(?=\()/gi,
				'replace':window.highlight.default_replace
			},
			{
				'class':'name',
				'match':/\b([a-z\_][a-z_\d]*)\b/gi,
				'replace':window.highlight.default_replace
			}
		];
	}

})(Function('return this')());

//=========================================================

// execution example:

window.onload = function(){
	
	highlight(document.getElementsByTagName('pre'));
	
}
/*styling for this example only*/


p {
  font-family:sans-serif;
  margin-bottom: 0px;
}
pre {margin-top:5px;}

/*console theme, main file*/

.highlight, .highlight *{
	background:black;
	color:white;
	font-family:'Consolas',monospace;
	font-size:16px;
	word-wrap: break-word;
	white-space: pre;
}

/*sql style, different file*/

.highlight.sql .keyword{color:teal;}
.highlight.sql .string{color:red;}
.highlight.sql .func{color:purple;}
.highlight.sql .number{color:#0F0;}
.highlight.sql .name{color:olive;}
.highlight.sql .var{color:green;}
.highlight.sql .comment{color:gray;}
<p>Simple example:</p>

<pre data-lang="sql">

insert into `table` select * from `_copy` where id &lt; 55;

</pre>

<p>Simple example, without language defined:</p>

<pre>

select "This will be 'unhighlighted'";

</pre>

<p>Complex example:</p>

<pre data-lang="sql">
    
CREATE TABLE `shop` (
    article INT(4) UNSIGNED ZEROFILL DEFAULT '0000' NOT NULL,
    dealer  CHAR(20)                 DEFAULT ''     NOT NULL,
    price   DOUBLE(16,2)             DEFAULT '0.00' NOT NULL,
    PRIMARY KEY(article, dealer));
INSERT INTO shop VALUES
    (1,'A',3.45),(1,'B',3.99),(2,'A',10.99),(3,'B',1.45),
    (3,'C',1.69),(3,'D',-1.25),(4,'D',19.95);
    
#This is an example of sql a = 'b', 'c'
    
SELECT MAX(article) AS article FROM shop;

SELECT s1.article, s1.dealer, s1.price
FROM shop s1
LEFT JOIN shop s2 ON s1.price &lt; s2.price
WHERE s2.article IS NULL;
    
SELECT article, MAX(price) AS price
FROM   shop
GROUP BY article;
    
SELECT article, dealer, price
FROM   shop s1
WHERE  price=(SELECT MAX(s2.price)
              FROM shop s2
              WHERE s1.article = s2.article);

SELECT @min_price:=MIN(price),@max_price:=MAX(price) FROM shop;
SELECT * FROM shop WHERE price=@min_price OR price=@max_price;
    
    /*
    
    a string =' ',
    
    */

select '1'#comment
    /*''*/ or 2;

select "Also has support for "" &lt;-- that style of quotes",
       '''Like this as well!''';

</pre>

\$\endgroup\$
6
  • 1
    \$\begingroup\$ var f=window.highlight In your opinion, is f a descriptive name for such a var? \$\endgroup\$ – Mast May 20 '15 at 6:49
  • \$\begingroup\$ @Mast Not really. f is a local variable, used to refer internally to the function window.highlight. This is just to avoid repeating window.highlight.<property> every single time, and also to avoid that, when someone does something similar to this: var x=window.highlight;window.highlight=null;, it will still work correctly (the language replace rules are bound to the window.highlight, but it is easily changeable). Probably naming fn or something would be better. What do you think? \$\endgroup\$ – Ismael Miguel May 20 '15 at 8:08
  • 1
    \$\begingroup\$ I'm not up to date with naming conventions in JS, but tWindowVar would be more descriptive than f. Still not great, but better. If you're concerned about safety, you might be interested in strict. \$\endgroup\$ – Mast May 20 '15 at 8:24
  • \$\begingroup\$ @Mast It's not safety. It's just so that people don't screw up. Relying as little as possible in the outside. Imagine the chaos that would be if someone just ran window.highlight=null; while I was accessing directly to window.highlight within the function. tWindowVar would be fine in VB, but in Javascript, it wouldn't be that good. Also, what for is the t there? And why WindowVar? It is a local variable, so, it won't be exposed to the window object. But yes, I should be using 'use strict';. I will add it there. I will change f to fn (fn is short for function). \$\endgroup\$ – Ismael Miguel May 20 '15 at 8:29
  • 1
    \$\begingroup\$ @Mast Sorry the delay to say something. I've made some changes in the answer and changed the name to fn, like I said I would do. Thanks for reminding me of the strict mode, I totally forgot about it. \$\endgroup\$ – Ismael Miguel May 20 '15 at 10:11

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.