Egyszerű Syntax Highlighter Drupal 5 alatt
Most már egy ideje működik egy új kód színező a poetro.hu alatt, ez pedig a SyntaxHighlighter JavaScript kódra épül, amit az oldalba integráltam. Ez a kód színező elég sok mindent tud, ugyanakkor pár dologgal nem értek egyet abban, ami az eredeti kódban van, ezért pár dolog máshogy működik. Sajnos a kód Drupal 5-höz készült de mivel nem tartalmaz sok kódot könnyen portolható újabb változatokra.
Az alap, ahogy régen is, a Code Filter modulra épül, azonban ebben kellett végeznem pár módosítást. Amit szerettem volna megvalósítani, az az, hogy tetszőleges CSS osztályt lehessen társítani a kód blokkhoz például <code class="brush-JScript">var x;</code>
formában.
Ami viszont teljesen új, az egy modul, ami ezeket a <code>
blokkokat kezeli. Maga a PHP kód igazából nem csinál sokat, csak a beállításokat teszi elérhetővé JavaScriptben és betölti a fő szkriptet.
$path = drupal_get_path('module', 'syntax_highlighter');
drupal_add_js(array(
'syntax_highlighter' => array(
'selector' => variable_get('syntax_highlighter_selector', 'code'),
'path' => base_path() . $path .'/syntaxhighlighter',
)
), 'setting');
drupal_add_js($path .'/syntax_highlighter.js');
Amint látszik a SyntaxHighlighter JavaScript kódjából nem töltődik be semmi, csak a modul saját JavaScriptje, ami majd betölti a tényleges JavaScript kódot. Ez azért hasznos, mert nem töltődik be feleslegesen több kilobyte-nyi kód. Amennyiben mégis szükség van rá, akkor ráadásul aszinkron módon töltjük be a kódot, ami mivel párhuzamosan több kód töltődik, ezért még gyorsabban is fog tudni betöltődni. Sajnos ezt csak a DOM betöltődése után tudjuk megtenni.
// Global Killswitch
if (Drupal.jsEnabled) {
(function ($, window, document, undefined) {
// All available brushes for highlighting.
var brushes = [
"AppleScript",
"AS3",
"Bash",
"ColdFusion",
"Cpp",
"CSharp",
"Css",
"Delphi",
"Diff",
"Erlang",
"Groovy",
"Java",
"JavaFX",
"JScript",
"Perl",
"Php",
"Plain",
"PowerShell",
"Python",
"Ruby",
"Sass",
"Scala",
"Sql",
"Vb",
"Xml"
],
init = function () {
var settings = Drupal.settings.syntax_highlighter,
bl = brushes.length,
classesRx,
i = 0,
codes = $(settings.selector),
toLoad = {},
head = $('head:first'),
cssAdded = false;
if (codes.length) {
// Create a RegExp for matching the class of the code with any brush.
// The code should have a class of `brush-TYPE` ex. `brush-JScript`.
// Matching is case sensitive.
classesRx = new RegExp('(?:^|\\s)brush-(' + brushes.join('|') + ')(?:$|\\s)');
// Filter the codes if there are any of them, that should be syntax highlighted.
codes = codes.filter(function () {
var matches = this.className.match(classesRx),
lineMatches,
brush;
// Check if there are any classes we have as a brush
if (matches) {
// Store the brush in the items to load, and as data in the element.
brush = matches[1];
toLoad[brush] = brush;
$.data(this, 'brush', brush);
lineMatches = this.className.match(/(?:^|\s)brushline-(\d+)(?:$|\s)/);
if (lineMatches) {
$.data(this, 'brushFirstLine', lineMatches[1]);
}
return true;
}
else {
return false;
}
});
// Check if we still have elements...
if (codes.length) {
// Load the CSS for the highlighter.
$('<link>').attr({
'rel' : 'stylesheet',
'type' : 'text/css',
'href' : settings.path + '/styles/shCore.css',
'media': 'all'
}).appendTo(head);
$('<link>').attr({
'rel' : 'stylesheet',
'type' : 'text/css',
'href' : settings.path + '/styles/shThemeDefault.css',
'media': 'all'
}).appendTo(head);
// Load the core for the highlighter.
$.ajax({
'url': settings.path + '/scripts/shCore.js',
'cache': true,
'dataType': 'script',
'success': function () {
var m,
itemsLeft, // # of scripts that are left to be loaded
sl, // # of scripts
scripts = [], // Array of script URLs.
i = 0;
// List all script to load.
for (m in toLoad) {
if (toLoad.hasOwnProperty(m)) {
scripts.push(settings.path + '/scripts/shBrush'+ m +'.js');
}
}
sl = itemsLeft = scripts.length;
// Load the scripts.
for (; i < sl; i += 1) {
$.ajax({
'url': scripts[i],
'cache': true,
'dataType': 'script',
'complete': function () {
itemsLeft -= 1;
// Check if all the scripts are loaded.
if (!itemsLeft) {
// All loaded, so apply the highlighter.
codes.each(function () {
// Get rid of all the BR tags.
var el = $(this).find('br').replaceWith("\n");
// Finally highlight with some defaults,
// using the brush stored in the elements' data.
SyntaxHighlighter.highlight({
'brush' : $.data(this, 'brush').toLowerCase(),
'tab-size' : 2,
'toolbar' : false,
'first-line' : $.data(this, 'brushFirstLine') || 1
}, this);
});
}
}
});
}
}
});
}
}
};
// Run init function when the DOM is ready.
$(init);
})($, window, document);
}
Hogyan működik
Vizsgáljuk meg kicsit a kódot. Az eleje az Űrlap mező helykitöltő jQuery kiegészítő-ben említett mintára épül, így az ott olvasottak itt is állnak.
// Global Killswitch
if (Drupal.jsEnabled) {
(function ($, window, document, undefined) {
//…
})($, window, document);
}
Azután következik a támogatott “ecsetek” listája, vagyis ezeket a nyelveket tudjuk színezni, és ilyen néven kell őket megadni a CSS osztályban, hogy a hozzá kapcsolódó JavaScript kód betöltődjön:
var brushes = [
"AppleScript",
"AS3",
"Bash",
"ColdFusion",
"Cpp",
"CSharp",
"Css",
"Delphi",
"Diff",
"Erlang",
"Groovy",
"Java",
"JavaFX",
"JScript",
"Perl",
"Php",
"Plain",
"PowerShell",
"Python",
"Ruby",
"Sass",
"Scala",
"Sql",
"Vb",
"Xml"
],
Az init
függvény az általános inicializációt végzi. Pár változóra szükségünk lesz a kódban, ezért ezeket gyorsan előkészítjük, ugyan a bonyolultabbakkal kicsit várunk, hogy feleslegesen ne terheljük a gépet, amennyiben nincs is mit csinálni.
init = function () {
var settings = Drupal.settings.syntax_highlighter,
bl = brushes.length,
classesRx,
i = 0,
codes = $(settings.selector),
toLoad = {},
head = $('head:first'),
cssAdded = false;
Osztály vizsgálat
Most már tudjuk mik a támogatott osztályok, és hogy milyen elemeket kell vizsgálni, akkor nézzük meg, rendelkeznek-e ezek a DOM elemek a kívánt osztállyal. Az elem osztálya a következően kell, hogy kinézzen: brush-Ecset
, ahol az Ecset
egy az előbb említett elemek közül a listában, például Php esetén az elem (egyik) osztálya brush-Php
kell, hogy legyen. Ennek ellenőrzéséhez egy reguláris kifejezést építünk.
if (codes.length) {
// Create a RegExp for matching the class of the code with any brush.
// The code should have a class of `brush-TYPE` ex. `brush-JScript`.
// Matching is case sensitive.
classesRx = new RegExp('(?:^|\\s)brush-(' + brushes.join('|') + ')(?:$|\\s)');
//…
}
Az előbb elkészített reguláris kifejezést pedig alkalmazzuk az elemekre, ezáltal kiszűrjük a nekünk kellőket:
// Filter the codes if there are any of them, that should be syntax highlighted.
codes = codes.filter(function () {
var matches = this.className.match(classesRx),
lineMatches,
brush;
// Check if there are any classes we have as a brush
if (matches) {
// Store the brush in the items to load, and as data in the element.
brush = matches[1];
toLoad[brush] = brush;
$.data(this, 'brush', brush);
lineMatches = this.className.match(/(?:^|\s)brushline-(\d+)(?:$|\s)/);
if (lineMatches) {
$.data(this, 'brushFirstLine', lineMatches[1]);
}
return true;
}
else {
return false;
}
});
Amennyiben a jQuery filter
függvényének egy függvényt adunk át paraméterként, akkor ezt a függvényt fogja meghívni minden egyes kiválasztott elemre, és a this
mindig az aktuális elemre fog mutatni. Amennyiben ez a függvény true
értékkel tér vissza, akkor az elem benne marad a kiválasztásban, ha false
-szal, akkor kikerül belőle.
Ha az elem osztályár illik a reguláris kifejezés, megvizsgáljuk, hogy esetleg meg volt-e adva sorszám is, ezzel tudjuk jelölni, hogy az idézett kódrészlet hányadik sornál kezdődik. Ezt például a brushline-123
osztály a kód blokkhoz való hozzáadásával tudjuk megtenni. Amennyiben meg volt adva sorszám, akkor azt eltároljuk ezt az ecsettel egyetemben a jQuery data függvényével hozzákapcsolva az adatot a DOM elemhez.
Betöltés
Ha maradt színezni való elemünk, akkor betöltjük az alapvető CSS fájlokat, és a fő JavaScript fájlt, amitől a színező ecsetei függenek.
// Load the CSS for the highlighter.
$('<link>').attr({
'rel' : 'stylesheet',
'type' : 'text/css',
'href' : settings.path + '/styles/shCore.css',
'media': 'all'
}).appendTo(head);
$('<link>').attr({
'rel' : 'stylesheet',
'type' : 'text/css',
'href' : settings.path + '/styles/shThemeDefault.css',
'media': 'all'
}).appendTo(head);
// Load the core for the highlighter.
$.ajax({
'url': settings.path + '/scripts/shCore.js',
'cache': true,
'dataType': 'script',
'success': function () {
var m,
itemsLeft, // # of scripts that are left to be loaded
sl, // # of scripts
scripts = [], // Array of script URLs.
i = 0;
// List all script to load.
for (m in toLoad) {
if (toLoad.hasOwnProperty(m)) {
scripts.push(settings.path + '/scripts/shBrush'+ m +'.js');
}
}
sl = itemsLeft = scripts.length;
// Load the scripts.
for (; i < sl; i += 1) {
//…
}
}
});
A CSS fájlokat egyszerű betölteni, egyszerűen a <link>
elemeket hozzá kell adni a <head>
-hez. A JavaScript kód betöltése kicsit trükkösebb, ugyanis, mint írtam az ecsetek függenek a fő modultól, azaz előbb azt kell betölteni, utána lehet csak az ecseteket. Erre használjuk a $.ajax
success
eseménykezelőjét. Ez a függvény akkor hívódik meg, mikor a $.ajax
-nek megadott JavaScript fájl már be van töltve, és már le is futott. Az eseménykezelőben összegyűjtjük a betöltendő JavaScripteket, majd betöltjük őket a második for
ciklusban.
$.ajax({
'url': scripts[i],
'cache': true,
'dataType': 'script',
'complete': function () {
itemsLeft -= 1;
// Check if all the scripts are loaded.
if (!itemsLeft) {
// All loaded, so apply the highlighter.
codes.each(function () {
// Get rid of all the BR tags.
var el = $(this).find('br').replaceWith("\n");
// Finally highlight with some defaults,
// using the brush stored in the elements` data.
SyntaxHighlighter.highlight({
'brush' : $.data(this, 'brush').toLowerCase(),
'tab-size' : 2,
'toolbar' : false,
'first-line' : $.data(this, 'brushFirstLine') || 1
}, this);
});
}
}
});
Ezeknek az ecseteknek a betöltődése esetén vizsgáljuk hogy mindegyik be lett-e már töltve, és ha igen, akkor alkalmazzuk a már szűrt kód blokkokra a kód színezőt. Mivel a Code Filter modul <br>
elemeket rak a sortörések helyére, és a SyntaxHighlighter ezt nem támogatja ezért ezeket vissza kell alakítani sortöréssé. A kódszínezés beállításai a Drupal szabályait követik, egyedül az eszközsort kapcsoltam ki, amíg nem tudom szépen bekonfigurálni.