JavaScript

индекс
246,38

Подсветка синтаксиса несколькими строками javascript

Да, я знаю, что такое синтаксический анализ. И знаю много разных библиотек для подсветки чего угодно. Только это всё не то, когда надо подсветить простенький примерчик, не содержащий всяких кодоизвращений. И уж совсем негоже тянуть для этого много-много байт _правильно_ разбирающих _любой_ код.

Для случаев без кодоизврата (а их большинство) можно использовать такой код:
code = code
// ключевые слова (список неполон, написал, что в голову пришло)
.replace(/(var|function|typeof|new|return|if|for|in|while|break|do|continue|switch|case)([^a-z0-9\$_])/gi,
'<span class="kwrd">$1</span>$2')
// всякие скобочки
.replace(/(\{|\}|\]|\[|\|)/gi,'<span class="kwrd">$1</span>')
// однострочные комментарии
.replace(/(\/\/[^\n\r]*(\n|\r\n))/g,'<span class="comm">$1</span>')
// строки
.replace(/('.*?')/g,'<span class="str">$1</span>')
// функции (когда после идентификатора идет скобка)
.replace(/([a-z\_\$][a-z0-9_]*)\(/gi,'<span class="func">$1</span>(')
// не люблю восьмизначные табы, пусть лучше будет 4 пробела
.replace(/\t/g,'    ');




CSS


.str{color:red}/* Строки красные */
.func{color:blue}/* Юзер-функции синие */
.comm{color:orange}/* Комменты оранжевые */
.kwrd{font-weight:bold}/* Ключевые слова полужирные */
.str span{color:red;font-weight:normal}/* Всё внутри строки — строка */
.comm span{color:orange;font-weight:normal}/* Всё внутри комментария — комментарий */


Пример


Подсветка синтаксиса - upload images with Picamatic
Обратите внимание на баг в последней строке. Об этом разговор далее

Область применения, баги


Данный код заточен на javascript, о чём можно судить по ключевым словам. Сделать код понимающим любой Си-подобный язык не представляет труда.

Возиться с многострочными комментариями я не стал, как и с многострочными строками, ибо не так уж частно многострочные комменты используются в примерах, а к многострочным строкам в javascript я как-то не привык.

Еще чем плох данный подход — текст «плоский», то есть этот, с позволения сказать, анализатор, считает слово for внутри строки или комментария таким же ключевым словом, как и все их. Нас это не устраивает, поэтому используются css правила .str span и .comm span, дабы возложить на плечи CSS распознавание блочной структуры кода. Одна проблема возникает — когда строка содержит комментарий, который содержит ключевое слово или пользовательскую функцию. В этом случае мы не взирая на окончание строки всё считаем строкой (до символа перевода строки).

И ещё, двойных кавычек не существует. Можно добавить еще один replace:
.replace(/(".*?")/g,'<span class="str">$1</span>')

Но меня от этого что-то удерживает.

Бонус: плагин для jQuery



chainable, multiple

(function($){

	$.fn.syntax = function(){
		return this.each(function(){
			var jqCode = $(this);
			var code = jqCode.text();
			code = code
			.replace(/(var|function|typeof|new|return|if|for|in|while|break|do|continue|case|switch)([^a-z0-9\$_])/gi,'<span class="kwrd">$1</span>$2')
			.replace(/(\{|\}|\]|\[|\|)/gi,'<span class="kwrd">$1</span>')
			.replace(/(\/\/[^\n\r]*(\n|\r\n))/g,'<span class="comm">$1</span>')
			.replace(/('.*?')/g,'<span class="str">$1</span>')
			.replace(/([a-z\_\$][a-z0-9_]*)\(/gi,'<span class="func">$1</span>(')
			.replace(/\t/g,'    ');
			jqCode.html(code);
		});
	}

	})(jQuery);

	// пример вызова
	$('pre#code').syntax(); // подсветка конкретного блока pre с id=code
	$('pre').syntax(); // подсветка всех pre


Усовершенствованный вариант от etcdema


function Syntax(code){
	var comments	= [];	// Тут собираем все каменты
	var strings		= [];	// Тут собираем все строки
	var res			= [];	// Тут собираем все RegExp
	var all			= { 'C': comments, 'S': strings, 'R': res };
	var safe		= { '<': '<', '>': '>', '&': '&' };

	return code
	// Маскируем HTML
		.replace(/[<>&]/g, function (m)
			{ return safe[m]; })
	// Убираем каменты
		.replace(/\/\*[\s\S]*\*\//g, function(m)
			{ var l=comments.length; comments.push(m); return '~~~C'+l+'~~~';   })
		.replace(/([^\\])\/\/[^\n]*\n/g, function(m, f)
			{ var l=comments.length; comments.push(m); return f+'~~~C'+l+'~~~'; })
	// Убираем regexp
		.replace(/\/(\\\/|[^\/\n])*\/[gim]{0,3}/g, function(m)
			{ var l=res.length; res.push(m); return '~~~R'+l+'~~~';   })
	// Убираем строки
		.replace(/([^\\])((?:'(?:\\'|[^'])*')|(?:"(?:\\"|[^"])*"))/g, function(m, f, s)
			{ var l=strings.length; strings.push(s); return f+'~~~S'+l+'~~~'; })
	// Выделяем ключевые слова
		.replace(/(var|function|typeof|new|return|if|for|in|while|break|do|continue|switch|case)([^a-z0-9\$_])/gi,
			'<span class="kwrd">$1</span>$2')
	// Выделяем скобки
		.replace(/(\{|\}|\]|\[|\|)/gi,
			'<span class="gly">$1</span>')
	// Выделяем имена функций
		.replace(/([a-z\_\$][a-z0-9_]*)[\s]*\(/gi,
			'<span class="func">$1</span>(')
	// Возвращаем на место каменты, строки, RegExp
		.replace(/~~~([CSR])(\d+)~~~/g, function(m, t, i)
			{ return '<span class="'+t+'">'+all[t][i]+'</span>'; })
	// Выставляем переводы строк
		.replace(/\n/g,
			'<br/>')
	// Табуляцию заменяем неразрывными пробелами
		.replace(/\t/g,
			'&nbsp;&nbsp;&nbsp;&nbsp;');
}
Тут понадобятся другие стили:
.S{color:red}/* Строки красные */
.func{color:blue}/* Юзер-функции синие */
.C{color:orange}/* Комменты оранжевые */
.kwrd{font-weight:bold}/* Ключевые слова полужирные */
.R{color:gray} /*Серые регвыражения */


upd: учтя исправления и дополнения, получил скрипт сам себя подсвечивающий, а вот версия от etcdema : dema.ru/syntax/
+56
23 октября 2008, 15:26
78

комментарии (30)

0
logman #
Идея интересная, думаю вполне подойдет для личных блогов и не сильно специализированных на программировании сайтов. Большой плюс, что обработка производится на клиенте и не загружает сервер, а хороший синтаксический анализ все таки вещь не сильно быстрая. К сожалению закончились разряды, как будут обязательно плюсану.
0
freehome #
Спасибо.
0
oboyshikov #
В JavaScript не силен, а подобная, не тяжелая подсветка как раз понадобилась, так что большое Вам спасибо.
+3
ainu #
Здорово. Коротко и быстро. И красиво.
P.S. Сам пользуюсь hightlight.js, это пример «многих_многих_байт». Он крут, но и размерами гораздо больше, и грузится соответственно дольше.
+1
1602 #
Да, хорошая библиотека, тож её использовал использовал в нескольких проектах. Но вот захотелось чего-то небольшого и понятного.
НЛО прилетело и опубликовало эту надпись здесь
+1
no_smoking #
Вот наткнулся на не большую библиотечку codepress.org/ только там куча подсветок, автозакрытие скобок, автоподстоновки и все это чюдо весит не так уж и много 4к библиотека плюс по выбору правила с css для определённого языка примерно 2к
0
1602 #
Сайт лежит. (хабраэффект?) За ссылку спасибо, глянем как сайт встанет.
0
no_smoking #
да заметил решил зайти посмотреть свежую версию и не смог :(, а библиотка хорошая можно в одминке использовать для редактирования исходников :)
0
bolk #
LISP подстветите мне так? :)
+3
1602 #
Насколько я знаю LISP, там подсвечивать особо нечего :)

code = code
.replace(/(defun|let|lambda|if|null|list|cons|append|loop)([^a-z0-9\$_])/gi,'$1$2')
.replace(/(\(|\)|\]|\[|\|)/gi,'$1')
.replace(/(\;[^\n\r]*(\n|\r\n))/g,'$1')
.replace(/(".*?")/g,'$1')
.replace(/\(([a-z\_\$][a-z0-9_]*)/gi,'($1')
.replace(/\t/g,' ');

как-то так :)
0
1602 #
не проверял, но вроде должно быть правильно
0
Nc_Soft #
Может кто подскажет хорошую библу для подсветки css/sql/xml/js?
Желательно на php
+3
ETCDema #
Немного исправленный вариант:
function Syntax(code)
{
	var comments	= [];	// Тут собираем все каменты
	var strings		= [];	// Тут собираем все строки
	var res			= [];	// Тут собираем все RegExp
	var all			= { 'C': comments, 'S': strings, 'R': res };
	var safe		= { '<': '<', '>': '>', '&': '&' };

	return code
	// Маскируем HTML
						.replace(/[<>&]/g, function (m)
							{ return safe[m]; })
	// Убираем каменты
						.replace(/\/\*[\s\S]*\*\//g, function(m)
							{ var l=comments.length; comments.push(m); return '~~~C'+l+'~~~';   })
						.replace(/([^\\])\/\/[^\n]*\n/g, function(m, f)
							{ var l=comments.length; comments.push(m); return f+'~~~C'+l+'~~~'; })
	// Убираем regexp
						.replace(/\/(\\\/|[^\/\n])*\/[gim]{0,3}/g, function(m)
							{ var l=res.length; res.push(m); return '~~~R'+l+'~~~';   })
	// Убираем строки
						.replace(/([^\\])((?:'(?:\\'|[^'])*')|(?:"(?:\\"|[^"])*"))/g, function(m, f, s)
							{ var l=strings.length; strings.push(s); return f+'~~~S'+l+'~~~'; })
	// Выделяем ключевые слова
						.replace(/(var|function|typeof|new|return|if|for|in|while|break|do|continue|switch|case)([^a-z0-9\$_])/gi,
							'<span class="kwrd">$1</span>$2')
	// Выделяем скобки
						.replace(/(\{|\}|\]|\[|\|)/gi,
							'<span class="gly">$1</span>')
	// Выделяем имена функций
						.replace(/([a-z\_\$][a-z0-9_]*)[\s]*\(/gi,
							'<span class="func">$1</span>(')
	// Возвращаем на место каменты, строки, RegExp
						.replace(/~~~([CSR])(\d+)~~~/g, function(m, t, i)
							{ return '<span class="'+t+'">'+all[t][i]+'</span>'; })
	// Выставляем переводы строк
						.replace(/\n/g,
							'
')
	// Табуляцию заменяем неразрывными пробелами
						.replace(/\t/g,
							'    ');
}

+1
1602 #
Спасибо, добавил в пост.
+1
ETCDema #
Еще чуть-чуть поправлю: Стили
.S span{color:red;font-weight:normal}/* Всё внутри строки — строка */
.C span{color:orange;font-weight:normal}/* Всё внутри комментария — комментарий */

ненужны, т. к. в моем варианте в каменты и строки ничего вложено не будет.
0
1602 #
ахда, точно (рефлекс копипасты сработал)
+1
ETCDema #
Немного неправильные 2 последние замены (сразу не обратил внимание), должно быть так:
// Выставляем переводы строк
	.replace(/\n/g,	'<'+'br>')
// Табуляцию заменяем неразрывными пробелами
	.replace(/\t/g, '&'+'nbsp;&'+'nbsp;&'+'nbsp;&'+'nbsp;');


+1
1602 #
ага. хабр съел пару тэгов.
всё поправлено
0
dpritula #
Спасибо.
Хоть и повторюсь, но здорово. Коротко и быстро. И красиво.
0
dpritula #
Позволю себе добавить следующее:

Код, скопированный из редактора с принудительной заменой табуляции пробелами (что есть правильно на мой взгляд) будет выглядеть абсолютно некошерно.

Это можно исправить, объявив локальную переменную:
var spacesInTab = 4;


И дописать простенький регэксп:
.replace(new RegExp('\\s{' + spacesInTab + '}', 'g'),
    new Array(spacesInTab + 1).join("&nbsp;"));


Вуаля.
+1
ETCDema #
Насчет
Если кто может разместить сей файлик на постоянной основе — будьте так любезны, а ссылку я выложу.

Немного посидел, довел до ума и положил тут: http://dema.ru/syntax/ неминимизированный код с комментариями занимает 192 строки или 7 202 байт, если выкинуть все лишнее — примерно 2Кб.
0
1602 #
Пару раз заметил глюк: вначале и в конце строки комментарий считается пустым регулярным выражением. И ещё у вас на сайте (не сочтите за дерзость) «плагны» написано, вместо «Плагины».
0
ETCDema #
Спасибо, поправил
0
afi #
Неплохо было бы еще избавиться от необходимости прописывать стили в документе, а тем более подключать внешний файл. Например, так:

$.fn.syntax = function(){

// add style
var style = $('<style>.S{color:red;}.func{color:blue;}.C{color:orange;}.kwrd{font-weight:bold;}.R{color:gray;}</style> ');
$("body").append(style);
return this.each(function(){
....
....


Только нужно будет дать стилям уникальные имена, напр. syntaxjs_func, syntaxjs_C и т. д., чтобы не пересекались с другими.
Также, цвета можно вынести в настройки.
0
Hush #
Всё гениальное — просто. Лаконичная и красивая реализация!
0
rubyrabbit #
Спасибо.

Могу я считать, что этот код выпущен под свободной лицензией? ))
Сделаем расширение для PunBB 1.3.
0
1602 #
Пожалуйста.

Код, изначально написанный мной, может считаться выпущенным под свободной лицензией. По поводу же кода хабраюзера ETCDema ничего сказать не могу. Строго говоря, нельзя считать его код модификацией моего, поэтому лучше у него спросить :)
0
Aleko #
Сильно развил вашу идею и написал достаточно хорошую подсветку под систему ucoz. Можно конечно использовать и на сторонних ресурсах. Прошу оценить подсветку прямо на этой странице. Подсвечиваются js, css, html. Весь код укладывается в 9 килобайт.
–1
Oreolek #
Это получается не javascript, а jQuery.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.