0,0 → 1,1721 |
/** |
* SyntaxHighlighter |
* http://alexgorbatchev.com/SyntaxHighlighter |
* |
* SyntaxHighlighter is donationware. If you are using it, please donate. |
* http://alexgorbatchev.com/SyntaxHighlighter/donate.html |
* |
* @version |
* 3.0.83 (July 02 2010) |
* |
* @copyright |
* Copyright (C) 2004-2010 Alex Gorbatchev. |
* |
* @license |
* Dual licensed under the MIT and GPL licenses. |
*/ |
// |
// Begin anonymous function. This is used to contain local scope variables without polutting global scope. |
// |
var SyntaxHighlighter = function() { |
|
// CommonJS |
if (typeof(require) != 'undefined' && typeof(XRegExp) == 'undefined') |
{ |
XRegExp = require('XRegExp').XRegExp; |
} |
|
// Shortcut object which will be assigned to the SyntaxHighlighter variable. |
// This is a shorthand for local reference in order to avoid long namespace |
// references to SyntaxHighlighter.whatever... |
var sh = { |
defaults : { |
/** Additional CSS class names to be added to highlighter elements. */ |
'class-name' : '', |
|
/** First line number. */ |
'first-line' : 1, |
|
/** |
* Pads line numbers. Possible values are: |
* |
* false - don't pad line numbers. |
* true - automaticaly pad numbers with minimum required number of leading zeroes. |
* [int] - length up to which pad line numbers. |
*/ |
'pad-line-numbers' : false, |
|
/** Lines to highlight. */ |
'highlight' : null, |
|
/** Title to be displayed above the code block. */ |
'title' : null, |
|
/** Enables or disables smart tabs. */ |
'smart-tabs' : true, |
|
/** Gets or sets tab size. */ |
'tab-size' : 4, |
|
/** Enables or disables gutter. */ |
'gutter' : true, |
|
/** Enables or disables toolbar. */ |
'toolbar' : true, |
|
/** Enables quick code copy and paste from double click. */ |
'quick-code' : true, |
|
/** Forces code view to be collapsed. */ |
'collapse' : false, |
|
/** Enables or disables automatic links. */ |
'auto-links' : true, |
|
/** Gets or sets light mode. Equavalent to turning off gutter and toolbar. */ |
'light' : false, |
|
'html-script' : false |
}, |
|
config : { |
space : ' ', |
|
/** Enables use of <SCRIPT type="syntaxhighlighter" /> tags. */ |
useScriptTags : true, |
|
/** Blogger mode flag. */ |
bloggerMode : false, |
|
stripBrs : false, |
|
/** Name of the tag that SyntaxHighlighter will automatically look for. */ |
tagName : 'pre', |
|
strings : { |
expandSource : 'expand source', |
help : '?', |
alert: 'SyntaxHighlighter\n\n', |
noBrush : 'Can\'t find brush for: ', |
brushNotHtmlScript : 'Brush wasn\'t configured for html-script option: ', |
|
// this is populated by the build script |
aboutDialog : '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml"><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /><title>About SyntaxHighlighter</title></head><body style="font-family:Geneva,Arial,Helvetica,sans-serif;background-color:#fff;color:#000;font-size:1em;text-align:center;"><div style="text-align:center;margin-top:1.5em;"><div style="font-size:xx-large;">SyntaxHighlighter</div><div style="font-size:.75em;margin-bottom:3em;"><div>version 3.0.83 (July 02 2010)</div><div><a href="http://alexgorbatchev.com/SyntaxHighlighter" target="_blank" style="color:#005896">http://alexgorbatchev.com/SyntaxHighlighter</a></div><div>JavaScript code syntax highlighter.</div><div>Copyright 2004-2010 Alex Gorbatchev.</div></div><div>If you like this script, please <a href="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=2930402" style="color:#005896">donate</a> to <br/>keep development active!</div></div></body></html>' |
} |
}, |
|
/** Internal 'global' variables. */ |
vars : { |
discoveredBrushes : null, |
highlighters : {} |
}, |
|
/** This object is populated by user included external brush files. */ |
brushes : {}, |
|
/** Common regular expressions. */ |
regexLib : { |
multiLineCComments : /\/\*[\s\S]*?\*\//gm, |
singleLineCComments : /\/\/.*$/gm, |
singleLinePerlComments : /#.*$/gm, |
doubleQuotedString : /"([^\\"\n]|\\.)*"/g, |
singleQuotedString : /'([^\\'\n]|\\.)*'/g, |
multiLineDoubleQuotedString : new XRegExp('"([^\\\\"]|\\\\.)*"', 'gs'), |
multiLineSingleQuotedString : new XRegExp("'([^\\\\']|\\\\.)*'", 'gs'), |
xmlComments : /(<|<)!--[\s\S]*?--(>|>)/gm, |
url : /\w+:\/\/[\w-.\/?%&=:@;]*/g, |
|
/** <?= ?> tags. */ |
phpScriptTags : { left: /(<|<)\?=?/g, right: /\?(>|>)/g }, |
|
/** <%= %> tags. */ |
aspScriptTags : { left: /(<|<)%=?/g, right: /%(>|>)/g }, |
|
/** <script></script> tags. */ |
scriptScriptTags : { left: /(<|<)\s*script.*?(>|>)/gi, right: /(<|<)\/\s*script\s*(>|>)/gi } |
}, |
|
toolbar: { |
/** |
* Generates HTML markup for the toolbar. |
* @param {Highlighter} highlighter Highlighter instance. |
* @return {String} Returns HTML markup. |
*/ |
getHtml: function(highlighter) |
{ |
var html = '<div class="toolbar">', |
items = sh.toolbar.items, |
list = items.list |
; |
|
function defaultGetHtml(highlighter, name) |
{ |
return sh.toolbar.getButtonHtml(highlighter, name, sh.config.strings[name]); |
}; |
|
for (var i = 0; i < list.length; i++) |
html += (items[list[i]].getHtml || defaultGetHtml)(highlighter, list[i]); |
|
html += '</div>'; |
|
return html; |
}, |
|
/** |
* Generates HTML markup for a regular button in the toolbar. |
* @param {Highlighter} highlighter Highlighter instance. |
* @param {String} commandName Command name that would be executed. |
* @param {String} label Label text to display. |
* @return {String} Returns HTML markup. |
*/ |
getButtonHtml: function(highlighter, commandName, label) |
{ |
return '<span><a href="#" class="toolbar_item' |
+ ' command_' + commandName |
+ ' ' + commandName |
+ '">' + label + '</a></span>' |
; |
}, |
|
/** |
* Event handler for a toolbar anchor. |
*/ |
handler: function(e) |
{ |
var target = e.target, |
className = target.className || '' |
; |
|
function getValue(name) |
{ |
var r = new RegExp(name + '_(\\w+)'), |
match = r.exec(className) |
; |
|
return match ? match[1] : null; |
}; |
|
var highlighter = getHighlighterById(findParentElement(target, '.syntaxhighlighter').id), |
commandName = getValue('command') |
; |
|
// execute the toolbar command |
if (highlighter && commandName) |
sh.toolbar.items[commandName].execute(highlighter); |
|
// disable default A click behaviour |
e.preventDefault(); |
}, |
|
/** Collection of toolbar items. */ |
items : { |
// Ordered lis of items in the toolbar. Can't expect `for (var n in items)` to be consistent. |
list: ['expandSource', 'help'], |
|
expandSource: { |
getHtml: function(highlighter) |
{ |
if (highlighter.getParam('collapse') != true) |
return ''; |
|
var title = highlighter.getParam('title'); |
return sh.toolbar.getButtonHtml(highlighter, 'expandSource', title ? title : sh.config.strings.expandSource); |
}, |
|
execute: function(highlighter) |
{ |
var div = getHighlighterDivById(highlighter.id); |
removeClass(div, 'collapsed'); |
} |
}, |
|
/** Command to display the about dialog window. */ |
help: { |
execute: function(highlighter) |
{ |
var wnd = popup('', '_blank', 500, 250, 'scrollbars=0'), |
doc = wnd.document |
; |
|
doc.write(sh.config.strings.aboutDialog); |
doc.close(); |
wnd.focus(); |
} |
} |
} |
}, |
|
/** |
* Finds all elements on the page which should be processes by SyntaxHighlighter. |
* |
* @param {Object} globalParams Optional parameters which override element's |
* parameters. Only used if element is specified. |
* |
* @param {Object} element Optional element to highlight. If none is |
* provided, all elements in the current document |
* are returned which qualify. |
* |
* @return {Array} Returns list of <code>{ target: DOMElement, params: Object }</code> objects. |
*/ |
findElements: function(globalParams, element) |
{ |
var elements = element ? [element] : toArray(document.getElementsByTagName(sh.config.tagName)), |
conf = sh.config, |
result = [] |
; |
|
// support for <SCRIPT TYPE="syntaxhighlighter" /> feature |
if (conf.useScriptTags) |
elements = elements.concat(getSyntaxHighlighterScriptTags()); |
|
if (elements.length === 0) |
return result; |
|
for (var i = 0; i < elements.length; i++) |
{ |
var item = { |
target: elements[i], |
// local params take precedence over globals |
params: merge(globalParams, parseParams(elements[i].className)) |
}; |
|
if (item.params['brush'] == null) |
continue; |
|
result.push(item); |
} |
|
return result; |
}, |
|
/** |
* Shorthand to highlight all elements on the page that are marked as |
* SyntaxHighlighter source code. |
* |
* @param {Object} globalParams Optional parameters which override element's |
* parameters. Only used if element is specified. |
* |
* @param {Object} element Optional element to highlight. If none is |
* provided, all elements in the current document |
* are highlighted. |
*/ |
highlight: function(globalParams, element) |
{ |
var elements = this.findElements(globalParams, element), |
propertyName = 'innerHTML', |
highlighter = null, |
conf = sh.config |
; |
|
if (elements.length === 0) |
return; |
|
for (var i = 0; i < elements.length; i++) |
{ |
var element = elements[i], |
target = element.target, |
params = element.params, |
brushName = params.brush, |
code |
; |
|
if (brushName == null) |
continue; |
|
// Instantiate a brush |
if (params['html-script'] == 'true' || sh.defaults['html-script'] == true) |
{ |
highlighter = new sh.HtmlScript(brushName); |
brushName = 'htmlscript'; |
} |
else |
{ |
var brush = findBrush(brushName); |
|
if (brush) |
highlighter = new brush(); |
else |
continue; |
} |
|
code = target[propertyName]; |
|
// remove CDATA from <SCRIPT/> tags if it's present |
if (conf.useScriptTags) |
code = stripCData(code); |
|
// Inject title if the attribute is present |
if ((target.title || '') != '') |
params.title = target.title; |
|
params['brush'] = brushName; |
highlighter.init(params); |
element = highlighter.getDiv(code); |
|
// carry over ID |
if ((target.id || '') != '') |
element.id = target.id; |
|
target.parentNode.replaceChild(element, target); |
} |
}, |
|
/** |
* Main entry point for the SyntaxHighlighter. |
* @param {Object} params Optional params to apply to all highlighted elements. |
*/ |
all: function(params) |
{ |
attachEvent( |
window, |
'load', |
function() { sh.highlight(params); } |
); |
} |
}; // end of sh |
|
sh['all'] = sh.all; |
sh['highlight'] = sh.highlight; |
|
/** |
* Checks if target DOM elements has specified CSS class. |
* @param {DOMElement} target Target DOM element to check. |
* @param {String} className Name of the CSS class to check for. |
* @return {Boolean} Returns true if class name is present, false otherwise. |
*/ |
function hasClass(target, className) |
{ |
return target.className.indexOf(className) != -1; |
}; |
|
/** |
* Adds CSS class name to the target DOM element. |
* @param {DOMElement} target Target DOM element. |
* @param {String} className New CSS class to add. |
*/ |
function addClass(target, className) |
{ |
if (!hasClass(target, className)) |
target.className += ' ' + className; |
}; |
|
/** |
* Removes CSS class name from the target DOM element. |
* @param {DOMElement} target Target DOM element. |
* @param {String} className CSS class to remove. |
*/ |
function removeClass(target, className) |
{ |
target.className = target.className.replace(className, ''); |
}; |
|
/** |
* Converts the source to array object. Mostly used for function arguments and |
* lists returned by getElementsByTagName() which aren't Array objects. |
* @param {List} source Source list. |
* @return {Array} Returns array. |
*/ |
function toArray(source) |
{ |
var result = []; |
|
for (var i = 0; i < source.length; i++) |
result.push(source[i]); |
|
return result; |
}; |
|
/** |
* Splits block of text into lines. |
* @param {String} block Block of text. |
* @return {Array} Returns array of lines. |
*/ |
function splitLines(block) |
{ |
return block.split('\n'); |
} |
|
/** |
* Generates HTML ID for the highlighter. |
* @param {String} highlighterId Highlighter ID. |
* @return {String} Returns HTML ID. |
*/ |
function getHighlighterId(id) |
{ |
var prefix = 'highlighter_'; |
return id.indexOf(prefix) == 0 ? id : prefix + id; |
}; |
|
/** |
* Finds Highlighter instance by ID. |
* @param {String} highlighterId Highlighter ID. |
* @return {Highlighter} Returns instance of the highlighter. |
*/ |
function getHighlighterById(id) |
{ |
return sh.vars.highlighters[getHighlighterId(id)]; |
}; |
|
/** |
* Finds highlighter's DIV container. |
* @param {String} highlighterId Highlighter ID. |
* @return {Element} Returns highlighter's DIV element. |
*/ |
function getHighlighterDivById(id) |
{ |
return document.getElementById(getHighlighterId(id)); |
}; |
|
/** |
* Stores highlighter so that getHighlighterById() can do its thing. Each |
* highlighter must call this method to preserve itself. |
* @param {Highilghter} highlighter Highlighter instance. |
*/ |
function storeHighlighter(highlighter) |
{ |
sh.vars.highlighters[getHighlighterId(highlighter.id)] = highlighter; |
}; |
|
/** |
* Looks for a child or parent node which has specified classname. |
* Equivalent to jQuery's $(container).find(".className") |
* @param {Element} target Target element. |
* @param {String} search Class name or node name to look for. |
* @param {Boolean} reverse If set to true, will go up the node tree instead of down. |
* @return {Element} Returns found child or parent element on null. |
*/ |
function findElement(target, search, reverse /* optional */) |
{ |
if (target == null) |
return null; |
|
var nodes = reverse != true ? target.childNodes : [ target.parentNode ], |
propertyToFind = { '#' : 'id', '.' : 'className' }[search.substr(0, 1)] || 'nodeName', |
expectedValue, |
found |
; |
|
expectedValue = propertyToFind != 'nodeName' |
? search.substr(1) |
: search.toUpperCase() |
; |
|
// main return of the found node |
if ((target[propertyToFind] || '').indexOf(expectedValue) != -1) |
return target; |
|
for (var i = 0; nodes && i < nodes.length && found == null; i++) |
found = findElement(nodes[i], search, reverse); |
|
return found; |
}; |
|
/** |
* Looks for a parent node which has specified classname. |
* This is an alias to <code>findElement(container, className, true)</code>. |
* @param {Element} target Target element. |
* @param {String} className Class name to look for. |
* @return {Element} Returns found parent element on null. |
*/ |
function findParentElement(target, className) |
{ |
return findElement(target, className, true); |
}; |
|
/** |
* Finds an index of element in the array. |
* @ignore |
* @param {Object} searchElement |
* @param {Number} fromIndex |
* @return {Number} Returns index of element if found; -1 otherwise. |
*/ |
function indexOf(array, searchElement, fromIndex) |
{ |
fromIndex = Math.max(fromIndex || 0, 0); |
|
for (var i = fromIndex; i < array.length; i++) |
if(array[i] == searchElement) |
return i; |
|
return -1; |
}; |
|
/** |
* Generates a unique element ID. |
*/ |
function guid(prefix) |
{ |
return (prefix || '') + Math.round(Math.random() * 1000000).toString(); |
}; |
|
/** |
* Merges two objects. Values from obj2 override values in obj1. |
* Function is NOT recursive and works only for one dimensional objects. |
* @param {Object} obj1 First object. |
* @param {Object} obj2 Second object. |
* @return {Object} Returns combination of both objects. |
*/ |
function merge(obj1, obj2) |
{ |
var result = {}, name; |
|
for (name in obj1) |
result[name] = obj1[name]; |
|
for (name in obj2) |
result[name] = obj2[name]; |
|
return result; |
}; |
|
/** |
* Attempts to convert string to boolean. |
* @param {String} value Input string. |
* @return {Boolean} Returns true if input was "true", false if input was "false" and value otherwise. |
*/ |
function toBoolean(value) |
{ |
var result = { "true" : true, "false" : false }[value]; |
return result == null ? value : result; |
}; |
|
/** |
* Opens up a centered popup window. |
* @param {String} url URL to open in the window. |
* @param {String} name Popup name. |
* @param {int} width Popup width. |
* @param {int} height Popup height. |
* @param {String} options window.open() options. |
* @return {Window} Returns window instance. |
*/ |
function popup(url, name, width, height, options) |
{ |
var x = (screen.width - width) / 2, |
y = (screen.height - height) / 2 |
; |
|
options += ', left=' + x + |
', top=' + y + |
', width=' + width + |
', height=' + height |
; |
options = options.replace(/^,/, ''); |
|
var win = window.open(url, name, options); |
win.focus(); |
return win; |
}; |
|
/** |
* Adds event handler to the target object. |
* @param {Object} obj Target object. |
* @param {String} type Name of the event. |
* @param {Function} func Handling function. |
*/ |
function attachEvent(obj, type, func, scope) |
{ |
function handler(e) |
{ |
e = e || window.event; |
|
if (!e.target) |
{ |
e.target = e.srcElement; |
e.preventDefault = function() |
{ |
this.returnValue = false; |
}; |
} |
|
func.call(scope || window, e); |
}; |
|
if (obj.attachEvent) |
{ |
obj.attachEvent('on' + type, handler); |
} |
else |
{ |
obj.addEventListener(type, handler, false); |
} |
}; |
|
/** |
* Displays an alert. |
* @param {String} str String to display. |
*/ |
function alert(str) |
{ |
window.alert(sh.config.strings.alert + str); |
}; |
|
/** |
* Finds a brush by its alias. |
* |
* @param {String} alias Brush alias. |
* @param {Boolean} showAlert Suppresses the alert if false. |
* @return {Brush} Returns bursh constructor if found, null otherwise. |
*/ |
function findBrush(alias, showAlert) |
{ |
var brushes = sh.vars.discoveredBrushes, |
result = null |
; |
|
if (brushes == null) |
{ |
brushes = {}; |
|
// Find all brushes |
for (var brush in sh.brushes) |
{ |
var info = sh.brushes[brush], |
aliases = info.aliases |
; |
|
if (aliases == null) |
continue; |
|
// keep the brush name |
info.brushName = brush.toLowerCase(); |
|
for (var i = 0; i < aliases.length; i++) |
brushes[aliases[i]] = brush; |
} |
|
sh.vars.discoveredBrushes = brushes; |
} |
|
result = sh.brushes[brushes[alias]]; |
|
if (result == null && showAlert != false) |
alert(sh.config.strings.noBrush + alias); |
|
return result; |
}; |
|
/** |
* Executes a callback on each line and replaces each line with result from the callback. |
* @param {Object} str Input string. |
* @param {Object} callback Callback function taking one string argument and returning a string. |
*/ |
function eachLine(str, callback) |
{ |
var lines = splitLines(str); |
|
for (var i = 0; i < lines.length; i++) |
lines[i] = callback(lines[i], i); |
|
return lines.join('\n'); |
}; |
|
/** |
* This is a special trim which only removes first and last empty lines |
* and doesn't affect valid leading space on the first line. |
* |
* @param {String} str Input string |
* @return {String} Returns string without empty first and last lines. |
*/ |
function trimFirstAndLastLines(str) |
{ |
return str.replace(/^[ ]*[\n]+|[\n]*[ ]*$/g, ''); |
}; |
|
/** |
* Parses key/value pairs into hash object. |
* |
* Understands the following formats: |
* - name: word; |
* - name: [word, word]; |
* - name: "string"; |
* - name: 'string'; |
* |
* For example: |
* name1: value; name2: [value, value]; name3: 'value' |
* |
* @param {String} str Input string. |
* @return {Object} Returns deserialized object. |
*/ |
function parseParams(str) |
{ |
var match, |
result = {}, |
arrayRegex = new XRegExp("^\\[(?<values>(.*?))\\]$"), |
regex = new XRegExp( |
"(?<name>[\\w-]+)" + |
"\\s*:\\s*" + |
"(?<value>" + |
"[\\w-%#]+|" + // word |
"\\[.*?\\]|" + // [] array |
'".*?"|' + // "" string |
"'.*?'" + // '' string |
")\\s*;?", |
"g" |
) |
; |
|
while ((match = regex.exec(str)) != null) |
{ |
var value = match.value |
.replace(/^['"]|['"]$/g, '') // strip quotes from end of strings |
; |
|
// try to parse array value |
if (value != null && arrayRegex.test(value)) |
{ |
var m = arrayRegex.exec(value); |
value = m.values.length > 0 ? m.values.split(/\s*,\s*/) : []; |
} |
|
result[match.name] = value; |
} |
|
return result; |
}; |
|
/** |
* Wraps each line of the string into <code/> tag with given style applied to it. |
* |
* @param {String} str Input string. |
* @param {String} css Style name to apply to the string. |
* @return {String} Returns input string with each line surrounded by <span/> tag. |
*/ |
function wrapLinesWithCode(str, css) |
{ |
if (str == null || str.length == 0 || str == '\n') |
return str; |
|
str = str.replace(/</g, '<'); |
|
// Replace two or more sequential spaces with leaving last space untouched. |
str = str.replace(/ {2,}/g, function(m) |
{ |
var spaces = ''; |
|
for (var i = 0; i < m.length - 1; i++) |
spaces += sh.config.space; |
|
return spaces + ' '; |
}); |
|
// Split each line and apply <span class="...">...</span> to them so that |
// leading spaces aren't included. |
if (css != null) |
str = eachLine(str, function(line) |
{ |
if (line.length == 0) |
return ''; |
|
var spaces = ''; |
|
line = line.replace(/^( | )+/, function(s) |
{ |
spaces = s; |
return ''; |
}); |
|
if (line.length == 0) |
return spaces; |
|
return spaces + '<code class="' + css + '">' + line + '</code>'; |
}); |
|
return str; |
}; |
|
/** |
* Pads number with zeros until it's length is the same as given length. |
* |
* @param {Number} number Number to pad. |
* @param {Number} length Max string length with. |
* @return {String} Returns a string padded with proper amount of '0'. |
*/ |
function padNumber(number, length) |
{ |
var result = number.toString(); |
|
while (result.length < length) |
result = '0' + result; |
|
return result; |
}; |
|
/** |
* Replaces tabs with spaces. |
* |
* @param {String} code Source code. |
* @param {Number} tabSize Size of the tab. |
* @return {String} Returns code with all tabs replaces by spaces. |
*/ |
function processTabs(code, tabSize) |
{ |
var tab = ''; |
|
for (var i = 0; i < tabSize; i++) |
tab += ' '; |
|
return code.replace(/\t/g, tab); |
}; |
|
/** |
* Replaces tabs with smart spaces. |
* |
* @param {String} code Code to fix the tabs in. |
* @param {Number} tabSize Number of spaces in a column. |
* @return {String} Returns code with all tabs replaces with roper amount of spaces. |
*/ |
function processSmartTabs(code, tabSize) |
{ |
var lines = splitLines(code), |
tab = '\t', |
spaces = '' |
; |
|
// Create a string with 1000 spaces to copy spaces from... |
// It's assumed that there would be no indentation longer than that. |
for (var i = 0; i < 50; i++) |
spaces += ' '; // 20 spaces * 50 |
|
// This function inserts specified amount of spaces in the string |
// where a tab is while removing that given tab. |
function insertSpaces(line, pos, count) |
{ |
return line.substr(0, pos) |
+ spaces.substr(0, count) |
+ line.substr(pos + 1, line.length) // pos + 1 will get rid of the tab |
; |
}; |
|
// Go through all the lines and do the 'smart tabs' magic. |
code = eachLine(code, function(line) |
{ |
if (line.indexOf(tab) == -1) |
return line; |
|
var pos = 0; |
|
while ((pos = line.indexOf(tab)) != -1) |
{ |
// This is pretty much all there is to the 'smart tabs' logic. |
// Based on the position within the line and size of a tab, |
// calculate the amount of spaces we need to insert. |
var spaces = tabSize - pos % tabSize; |
line = insertSpaces(line, pos, spaces); |
} |
|
return line; |
}); |
|
return code; |
}; |
|
/** |
* Performs various string fixes based on configuration. |
*/ |
function fixInputString(str) |
{ |
var br = /<br\s*\/?>|<br\s*\/?>/gi; |
|
if (sh.config.bloggerMode == true) |
str = str.replace(br, '\n'); |
|
if (sh.config.stripBrs == true) |
str = str.replace(br, ''); |
|
return str; |
}; |
|
/** |
* Removes all white space at the begining and end of a string. |
* |
* @param {String} str String to trim. |
* @return {String} Returns string without leading and following white space characters. |
*/ |
function trim(str) |
{ |
return str.replace(/^\s+|\s+$/g, ''); |
}; |
|
/** |
* Unindents a block of text by the lowest common indent amount. |
* @param {String} str Text to unindent. |
* @return {String} Returns unindented text block. |
*/ |
function unindent(str) |
{ |
var lines = splitLines(fixInputString(str)), |
indents = new Array(), |
regex = /^\s*/, |
min = 1000 |
; |
|
// go through every line and check for common number of indents |
for (var i = 0; i < lines.length && min > 0; i++) |
{ |
var line = lines[i]; |
|
if (trim(line).length == 0) |
continue; |
|
var matches = regex.exec(line); |
|
// In the event that just one line doesn't have leading white space |
// we can't unindent anything, so bail completely. |
if (matches == null) |
return str; |
|
min = Math.min(matches[0].length, min); |
} |
|
// trim minimum common number of white space from the begining of every line |
if (min > 0) |
for (var i = 0; i < lines.length; i++) |
lines[i] = lines[i].substr(min); |
|
return lines.join('\n'); |
}; |
|
/** |
* Callback method for Array.sort() which sorts matches by |
* index position and then by length. |
* |
* @param {Match} m1 Left object. |
* @param {Match} m2 Right object. |
* @return {Number} Returns -1, 0 or -1 as a comparison result. |
*/ |
function matchesSortCallback(m1, m2) |
{ |
// sort matches by index first |
if(m1.index < m2.index) |
return -1; |
else if(m1.index > m2.index) |
return 1; |
else |
{ |
// if index is the same, sort by length |
if(m1.length < m2.length) |
return -1; |
else if(m1.length > m2.length) |
return 1; |
} |
|
return 0; |
}; |
|
/** |
* Executes given regular expression on provided code and returns all |
* matches that are found. |
* |
* @param {String} code Code to execute regular expression on. |
* @param {Object} regex Regular expression item info from <code>regexList</code> collection. |
* @return {Array} Returns a list of Match objects. |
*/ |
function getMatches(code, regexInfo) |
{ |
function defaultAdd(match, regexInfo) |
{ |
return match[0]; |
}; |
|
var index = 0, |
match = null, |
matches = [], |
func = regexInfo.func ? regexInfo.func : defaultAdd |
; |
|
while((match = regexInfo.regex.exec(code)) != null) |
{ |
var resultMatch = func(match, regexInfo); |
|
if (typeof(resultMatch) == 'string') |
resultMatch = [new sh.Match(resultMatch, match.index, regexInfo.css)]; |
|
matches = matches.concat(resultMatch); |
} |
|
return matches; |
}; |
|
/** |
* Turns all URLs in the code into <a/> tags. |
* @param {String} code Input code. |
* @return {String} Returns code with </a> tags. |
*/ |
function processUrls(code) |
{ |
var gt = /(.*)((>|<).*)/; |
|
return code.replace(sh.regexLib.url, function(m) |
{ |
var suffix = '', |
match = null |
; |
|
// We include < and > in the URL for the common cases like <http://google.com> |
// The problem is that they get transformed into <http://google.com> |
// Where as > easily looks like part of the URL string. |
|
if (match = gt.exec(m)) |
{ |
m = match[1]; |
suffix = match[2]; |
} |
|
return '<a href="' + m + '">' + m + '</a>' + suffix; |
}); |
}; |
|
/** |
* Finds all <SCRIPT TYPE="syntaxhighlighter" /> elementss. |
* @return {Array} Returns array of all found SyntaxHighlighter tags. |
*/ |
function getSyntaxHighlighterScriptTags() |
{ |
var tags = document.getElementsByTagName('script'), |
result = [] |
; |
|
for (var i = 0; i < tags.length; i++) |
if (tags[i].type == 'syntaxhighlighter') |
result.push(tags[i]); |
|
return result; |
}; |
|
/** |
* Strips <![CDATA[]]> from <SCRIPT /> content because it should be used |
* there in most cases for XHTML compliance. |
* @param {String} original Input code. |
* @return {String} Returns code without leading <![CDATA[]]> tags. |
*/ |
function stripCData(original) |
{ |
var left = '<![CDATA[', |
right = ']]>', |
// for some reason IE inserts some leading blanks here |
copy = trim(original), |
changed = false, |
leftLength = left.length, |
rightLength = right.length |
; |
|
if (copy.indexOf(left) == 0) |
{ |
copy = copy.substring(leftLength); |
changed = true; |
} |
|
var copyLength = copy.length; |
|
if (copy.indexOf(right) == copyLength - rightLength) |
{ |
copy = copy.substring(0, copyLength - rightLength); |
changed = true; |
} |
|
return changed ? copy : original; |
}; |
|
|
/** |
* Quick code mouse double click handler. |
*/ |
function quickCodeHandler(e) |
{ |
var target = e.target, |
highlighterDiv = findParentElement(target, '.syntaxhighlighter'), |
container = findParentElement(target, '.container'), |
textarea = document.createElement('textarea'), |
highlighter |
; |
|
if (!container || !highlighterDiv || findElement(container, 'textarea')) |
return; |
|
highlighter = getHighlighterById(highlighterDiv.id); |
|
// add source class name |
addClass(highlighterDiv, 'source'); |
|
// Have to go over each line and grab it's text, can't just do it on the |
// container because Firefox loses all \n where as Webkit doesn't. |
var lines = container.childNodes, |
code = [] |
; |
|
for (var i = 0; i < lines.length; i++) |
code.push(lines[i].innerText || lines[i].textContent); |
|
// using \r instead of \r or \r\n makes this work equally well on IE, FF and Webkit |
code = code.join('\r'); |
|
// inject <textarea/> tag |
textarea.appendChild(document.createTextNode(code)); |
container.appendChild(textarea); |
|
// preselect all text |
textarea.focus(); |
textarea.select(); |
|
// set up handler for lost focus |
attachEvent(textarea, 'blur', function(e) |
{ |
textarea.parentNode.removeChild(textarea); |
removeClass(highlighterDiv, 'source'); |
}); |
}; |
|
/** |
* Match object. |
*/ |
sh.Match = function(value, index, css) |
{ |
this.value = value; |
this.index = index; |
this.length = value.length; |
this.css = css; |
this.brushName = null; |
}; |
|
sh.Match.prototype.toString = function() |
{ |
return this.value; |
}; |
|
/** |
* Simulates HTML code with a scripting language embedded. |
* |
* @param {String} scriptBrushName Brush name of the scripting language. |
*/ |
sh.HtmlScript = function(scriptBrushName) |
{ |
var brushClass = findBrush(scriptBrushName), |
scriptBrush, |
xmlBrush = new sh.brushes.Xml(), |
bracketsRegex = null, |
ref = this, |
methodsToExpose = 'getDiv getHtml init'.split(' ') |
; |
|
if (brushClass == null) |
return; |
|
scriptBrush = new brushClass(); |
|
for(var i = 0; i < methodsToExpose.length; i++) |
// make a closure so we don't lose the name after i changes |
(function() { |
var name = methodsToExpose[i]; |
|
ref[name] = function() |
{ |
return xmlBrush[name].apply(xmlBrush, arguments); |
}; |
})(); |
|
if (scriptBrush.htmlScript == null) |
{ |
alert(sh.config.strings.brushNotHtmlScript + scriptBrushName); |
return; |
} |
|
xmlBrush.regexList.push( |
{ regex: scriptBrush.htmlScript.code, func: process } |
); |
|
function offsetMatches(matches, offset) |
{ |
for (var j = 0; j < matches.length; j++) |
matches[j].index += offset; |
} |
|
function process(match, info) |
{ |
var code = match.code, |
matches = [], |
regexList = scriptBrush.regexList, |
offset = match.index + match.left.length, |
htmlScript = scriptBrush.htmlScript, |
result |
; |
|
// add all matches from the code |
for (var i = 0; i < regexList.length; i++) |
{ |
result = getMatches(code, regexList[i]); |
offsetMatches(result, offset); |
matches = matches.concat(result); |
} |
|
// add left script bracket |
if (htmlScript.left != null && match.left != null) |
{ |
result = getMatches(match.left, htmlScript.left); |
offsetMatches(result, match.index); |
matches = matches.concat(result); |
} |
|
// add right script bracket |
if (htmlScript.right != null && match.right != null) |
{ |
result = getMatches(match.right, htmlScript.right); |
offsetMatches(result, match.index + match[0].lastIndexOf(match.right)); |
matches = matches.concat(result); |
} |
|
for (var j = 0; j < matches.length; j++) |
matches[j].brushName = brushClass.brushName; |
|
return matches; |
} |
}; |
|
/** |
* Main Highlither class. |
* @constructor |
*/ |
sh.Highlighter = function() |
{ |
// not putting any code in here because of the prototype inheritance |
}; |
|
sh.Highlighter.prototype = { |
/** |
* Returns value of the parameter passed to the highlighter. |
* @param {String} name Name of the parameter. |
* @param {Object} defaultValue Default value. |
* @return {Object} Returns found value or default value otherwise. |
*/ |
getParam: function(name, defaultValue) |
{ |
var result = this.params[name]; |
return toBoolean(result == null ? defaultValue : result); |
}, |
|
/** |
* Shortcut to document.createElement(). |
* @param {String} name Name of the element to create (DIV, A, etc). |
* @return {HTMLElement} Returns new HTML element. |
*/ |
create: function(name) |
{ |
return document.createElement(name); |
}, |
|
/** |
* Applies all regular expression to the code and stores all found |
* matches in the `this.matches` array. |
* @param {Array} regexList List of regular expressions. |
* @param {String} code Source code. |
* @return {Array} Returns list of matches. |
*/ |
findMatches: function(regexList, code) |
{ |
var result = []; |
|
if (regexList != null) |
for (var i = 0; i < regexList.length; i++) |
// BUG: length returns len+1 for array if methods added to prototype chain (oising@gmail.com) |
if (typeof (regexList[i]) == "object") |
result = result.concat(getMatches(code, regexList[i])); |
|
// sort and remove nested the matches |
return this.removeNestedMatches(result.sort(matchesSortCallback)); |
}, |
|
/** |
* Checks to see if any of the matches are inside of other matches. |
* This process would get rid of highligted strings inside comments, |
* keywords inside strings and so on. |
*/ |
removeNestedMatches: function(matches) |
{ |
// Optimized by Jose Prado (http://joseprado.com) |
for (var i = 0; i < matches.length; i++) |
{ |
if (matches[i] === null) |
continue; |
|
var itemI = matches[i], |
itemIEndPos = itemI.index + itemI.length |
; |
|
for (var j = i + 1; j < matches.length && matches[i] !== null; j++) |
{ |
var itemJ = matches[j]; |
|
if (itemJ === null) |
continue; |
else if (itemJ.index > itemIEndPos) |
break; |
else if (itemJ.index == itemI.index && itemJ.length > itemI.length) |
matches[i] = null; |
else if (itemJ.index >= itemI.index && itemJ.index < itemIEndPos) |
matches[j] = null; |
} |
} |
|
return matches; |
}, |
|
/** |
* Creates an array containing integer line numbers starting from the 'first-line' param. |
* @return {Array} Returns array of integers. |
*/ |
figureOutLineNumbers: function(code) |
{ |
var lines = [], |
firstLine = parseInt(this.getParam('first-line')) |
; |
|
eachLine(code, function(line, index) |
{ |
lines.push(index + firstLine); |
}); |
|
return lines; |
}, |
|
/** |
* Determines if specified line number is in the highlighted list. |
*/ |
isLineHighlighted: function(lineNumber) |
{ |
var list = this.getParam('highlight', []); |
|
if (typeof(list) != 'object' && list.push == null) |
list = [ list ]; |
|
return indexOf(list, lineNumber.toString()) != -1; |
}, |
|
/** |
* Generates HTML markup for a single line of code while determining alternating line style. |
* @param {Integer} lineNumber Line number. |
* @param {String} code Line HTML markup. |
* @return {String} Returns HTML markup. |
*/ |
getLineHtml: function(lineIndex, lineNumber, code) |
{ |
var classes = [ |
'line', |
'number' + lineNumber, |
'index' + lineIndex, |
'alt' + (lineNumber % 2 == 0 ? 1 : 2).toString() |
]; |
|
if (this.isLineHighlighted(lineNumber)) |
classes.push('highlighted'); |
|
if (lineNumber == 0) |
classes.push('break'); |
|
return '<div class="' + classes.join(' ') + '">' + code + '</div>'; |
}, |
|
/** |
* Generates HTML markup for line number column. |
* @param {String} code Complete code HTML markup. |
* @param {Array} lineNumbers Calculated line numbers. |
* @return {String} Returns HTML markup. |
*/ |
getLineNumbersHtml: function(code, lineNumbers) |
{ |
var html = '', |
count = splitLines(code).length, |
firstLine = parseInt(this.getParam('first-line')), |
pad = this.getParam('pad-line-numbers') |
; |
|
if (pad == true) |
pad = (firstLine + count - 1).toString().length; |
else if (isNaN(pad) == true) |
pad = 0; |
|
for (var i = 0; i < count; i++) |
{ |
var lineNumber = lineNumbers ? lineNumbers[i] : firstLine + i, |
code = lineNumber == 0 ? sh.config.space : padNumber(lineNumber, pad) |
; |
|
html += this.getLineHtml(i, lineNumber, code); |
} |
|
return html; |
}, |
|
/** |
* Splits block of text into individual DIV lines. |
* @param {String} code Code to highlight. |
* @param {Array} lineNumbers Calculated line numbers. |
* @return {String} Returns highlighted code in HTML form. |
*/ |
getCodeLinesHtml: function(html, lineNumbers) |
{ |
html = trim(html); |
|
var lines = splitLines(html), |
padLength = this.getParam('pad-line-numbers'), |
firstLine = parseInt(this.getParam('first-line')), |
html = '', |
brushName = this.getParam('brush') |
; |
|
for (var i = 0; i < lines.length; i++) |
{ |
var line = lines[i], |
indent = /^( |\s)+/.exec(line), |
spaces = null, |
lineNumber = lineNumbers ? lineNumbers[i] : firstLine + i; |
; |
|
if (indent != null) |
{ |
spaces = indent[0].toString(); |
line = line.substr(spaces.length); |
spaces = spaces.replace(' ', sh.config.space); |
} |
|
line = trim(line); |
|
if (line.length == 0) |
line = sh.config.space; |
|
html += this.getLineHtml( |
i, |
lineNumber, |
(spaces != null ? '<code class="' + brushName + ' spaces">' + spaces + '</code>' : '') + line |
); |
} |
|
return html; |
}, |
|
/** |
* Returns HTML for the table title or empty string if title is null. |
*/ |
getTitleHtml: function(title) |
{ |
return title ? '<caption>' + title + '</caption>' : ''; |
}, |
|
/** |
* Finds all matches in the source code. |
* @param {String} code Source code to process matches in. |
* @param {Array} matches Discovered regex matches. |
* @return {String} Returns formatted HTML with processed mathes. |
*/ |
getMatchesHtml: function(code, matches) |
{ |
var pos = 0, |
result = '', |
brushName = this.getParam('brush', '') |
; |
|
function getBrushNameCss(match) |
{ |
var result = match ? (match.brushName || brushName) : brushName; |
return result ? result + ' ' : ''; |
}; |
|
// Finally, go through the final list of matches and pull the all |
// together adding everything in between that isn't a match. |
for (var i = 0; i < matches.length; i++) |
{ |
var match = matches[i], |
matchBrushName |
; |
|
if (match === null || match.length === 0) |
continue; |
|
matchBrushName = getBrushNameCss(match); |
|
result += wrapLinesWithCode(code.substr(pos, match.index - pos), matchBrushName + 'plain') |
+ wrapLinesWithCode(match.value, matchBrushName + match.css) |
; |
|
pos = match.index + match.length + (match.offset || 0); |
} |
|
// don't forget to add whatever's remaining in the string |
result += wrapLinesWithCode(code.substr(pos), getBrushNameCss() + 'plain'); |
|
return result; |
}, |
|
/** |
* Generates HTML markup for the whole syntax highlighter. |
* @param {String} code Source code. |
* @return {String} Returns HTML markup. |
*/ |
getHtml: function(code) |
{ |
var html = '', |
classes = [ 'syntaxhighlighter' ], |
tabSize, |
matches, |
lineNumbers |
; |
|
// process light mode |
if (this.getParam('light') == true) |
this.params.toolbar = this.params.gutter = false; |
|
className = 'syntaxhighlighter'; |
|
if (this.getParam('collapse') == true) |
classes.push('collapsed'); |
|
if ((gutter = this.getParam('gutter')) == false) |
classes.push('nogutter'); |
|
// add custom user style name |
classes.push(this.getParam('class-name')); |
|
// add brush alias to the class name for custom CSS |
classes.push(this.getParam('brush')); |
|
code = trimFirstAndLastLines(code) |
.replace(/\r/g, ' ') // IE lets these buggers through |
; |
|
tabSize = this.getParam('tab-size'); |
|
// replace tabs with spaces |
code = this.getParam('smart-tabs') == true |
? processSmartTabs(code, tabSize) |
: processTabs(code, tabSize) |
; |
|
// unindent code by the common indentation |
code = unindent(code); |
|
if (gutter) |
lineNumbers = this.figureOutLineNumbers(code); |
|
// find matches in the code using brushes regex list |
matches = this.findMatches(this.regexList, code); |
// processes found matches into the html |
html = this.getMatchesHtml(code, matches); |
// finally, split all lines so that they wrap well |
html = this.getCodeLinesHtml(html, lineNumbers); |
|
// finally, process the links |
if (this.getParam('auto-links')) |
html = processUrls(html); |
|
if (typeof(navigator) != 'undefined' && navigator.userAgent && navigator.userAgent.match(/MSIE/)) |
classes.push('ie'); |
|
html = |
'<div id="' + getHighlighterId(this.id) + '" class="' + classes.join(' ') + '">' |
+ (this.getParam('toolbar') ? sh.toolbar.getHtml(this) : '') |
+ '<table border="0" cellpadding="0" cellspacing="0">' |
+ this.getTitleHtml(this.getParam('title')) |
+ '<tbody>' |
+ '<tr>' |
+ (gutter ? '<td class="gutter">' + this.getLineNumbersHtml(code) + '</td>' : '') |
+ '<td class="code">' |
+ '<div class="container">' |
+ html |
+ '</div>' |
+ '</td>' |
+ '</tr>' |
+ '</tbody>' |
+ '</table>' |
+ '</div>' |
; |
|
return html; |
}, |
|
/** |
* Highlights the code and returns complete HTML. |
* @param {String} code Code to highlight. |
* @return {Element} Returns container DIV element with all markup. |
*/ |
getDiv: function(code) |
{ |
if (code === null) |
code = ''; |
|
this.code = code; |
|
var div = this.create('div'); |
|
// create main HTML |
div.innerHTML = this.getHtml(code); |
|
// set up click handlers |
if (this.getParam('toolbar')) |
attachEvent(findElement(div, '.toolbar'), 'click', sh.toolbar.handler); |
|
if (this.getParam('quick-code')) |
attachEvent(findElement(div, '.code'), 'dblclick', quickCodeHandler); |
|
return div; |
}, |
|
/** |
* Initializes the highlighter/brush. |
* |
* Constructor isn't used for initialization so that nothing executes during necessary |
* `new SyntaxHighlighter.Highlighter()` call when setting up brush inheritence. |
* |
* @param {Hash} params Highlighter parameters. |
*/ |
init: function(params) |
{ |
this.id = guid(); |
|
// register this instance in the highlighters list |
storeHighlighter(this); |
|
// local params take precedence over defaults |
this.params = merge(sh.defaults, params || {}) |
|
// process light mode |
if (this.getParam('light') == true) |
this.params.toolbar = this.params.gutter = false; |
}, |
|
/** |
* Converts space separated list of keywords into a regular expression string. |
* @param {String} str Space separated keywords. |
* @return {String} Returns regular expression string. |
*/ |
getKeywords: function(str) |
{ |
str = str |
.replace(/^\s+|\s+$/g, '') |
.replace(/\s+/g, '|') |
; |
|
return '\\b(?:' + str + ')\\b'; |
}, |
|
/** |
* Makes a brush compatible with the `html-script` functionality. |
* @param {Object} regexGroup Object containing `left` and `right` regular expressions. |
*/ |
forHtmlScript: function(regexGroup) |
{ |
this.htmlScript = { |
left : { regex: regexGroup.left, css: 'script' }, |
right : { regex: regexGroup.right, css: 'script' }, |
code : new XRegExp( |
"(?<left>" + regexGroup.left.source + ")" + |
"(?<code>.*?)" + |
"(?<right>" + regexGroup.right.source + ")", |
"sgi" |
) |
}; |
} |
}; // end of Highlighter |
|
return sh; |
}(); // end of anonymous function |
|
// CommonJS |
typeof(exports) != 'undefined' ? exports['SyntaxHighlighter'] = SyntaxHighlighter : null; |