| // |
| // Begin anonymous function. This is used to contain local scope variables without polutting global scope. |
| // |
| if (typeof(SyntaxHighlighter) == 'undefined') 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 : '@ABOUT@' |
| } |
| }, |
| |
| /** 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 |
| |
| /** |
| * 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; |