/** * SyntaxHighlighter * http://alexgorbatchev.com/ * * SyntaxHighlighter is donationware. If you are using it, please donate. * http://alexgorbatchev.com/wiki/SyntaxHighlighter:Donate * * @version * 2.1.364 (October 15 2009) * * @copyright * Copyright (C) 2004-2009 Alex Gorbatchev. * * @license * This file is part of SyntaxHighlighter. * * SyntaxHighlighter is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * SyntaxHighlighter is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with SyntaxHighlighter. If not, see . */ // // Begin anonymous function. This is used to contain local scope variables without polutting global scope. // if (!window.SyntaxHighlighter) var SyntaxHighlighter = function() { // 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' : true, /** Lines to highlight. */ 'highlight' : 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, /** 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, /** Enables or disables automatic line wrapping. */ 'wrap-lines' : true, 'html-script' : false }, config : { /** Enables use of tags. */ scriptScriptTags : { left: /(<|<)\s*script.*?(>|>)/gi, right: /(<|<)\/\s*script\s*(>|>)/gi } }, toolbar : { /** * Creates new toolbar for a highlighter. * @param {Highlighter} highlighter Target highlighter. */ create : function(highlighter) { var div = document.createElement('DIV'), items = sh.toolbar.items ; div.className = 'toolbar'; for (var name in items) { var constructor = items[name], command = new constructor(highlighter), element = command.create() ; highlighter.toolbarCommands[name] = command; if (element == null) continue; if (typeof(element) == 'string') element = sh.toolbar.createButton(element, highlighter.id, name); element.className += 'item ' + name; div.appendChild(element); } return div; }, /** * Create a standard anchor button for the toolbar. * @param {String} label Label text to display. * @param {String} highlighterId Highlighter ID that this button would belong to. * @param {String} commandName Command name that would be executed. * @return {Element} Returns an 'A' element. */ createButton : function(label, highlighterId, commandName) { var a = document.createElement('a'), style = a.style, config = sh.config, width = config.toolbarItemWidth, height = config.toolbarItemHeight ; a.href = '#' + commandName; a.title = label; a.highlighterId = highlighterId; a.commandName = commandName; a.innerHTML = label; if (isNaN(width) == false) style.width = width + 'px'; if (isNaN(height) == false) style.height = height + 'px'; a.onclick = function(e) { try { sh.toolbar.executeCommand( this, e || window.event, this.highlighterId, this.commandName ); } catch(e) { sh.utils.alert(e.message); } return false; }; return a; }, /** * Executes a toolbar command. * @param {Element} sender Sender element. * @param {MouseEvent} event Original mouse event object. * @param {String} highlighterId Highlighter DIV element ID. * @param {String} commandName Name of the command to execute. * @return {Object} Passes out return value from command execution. */ executeCommand : function(sender, event, highlighterId, commandName, args) { var highlighter = sh.vars.highlighters[highlighterId], command ; if (highlighter == null || (command = highlighter.toolbarCommands[commandName]) == null) return null; return command.execute(sender, event, args); }, /** Collection of toolbar items. */ items : { expandSource : function(highlighter) { this.create = function() { if (highlighter.getParam('collapse') != true) return; return sh.config.strings.expandSource; }; this.execute = function(sender, event, args) { var div = highlighter.div; sender.parentNode.removeChild(sender); div.className = div.className.replace('collapsed', ''); }; }, /** * Command to open a new window and display the original unformatted source code inside. */ viewSource : function(highlighter) { this.create = function() { return sh.config.strings.viewSource; }; this.execute = function(sender, event, args) { var code = sh.utils.fixInputString(highlighter.originalCode).replace(/' + code + ''); wnd.document.close(); }; }, /** * Command to copy the original source code in to the clipboard. * Uses Flash method if clipboardSwf is configured. */ copyToClipboard : function(highlighter) { var flashDiv, flashSwf, highlighterId = highlighter.id ; this.create = function() { var config = sh.config; // disable functionality if running locally if (config.clipboardSwf == null) return null; function params(list) { var result = ''; for (var name in list) result += ""; return result; }; function attributes(list) { var result = ''; for (var name in list) result += " " + name + "='" + list[name] + "'"; return result; }; var args1 = { width : config.toolbarItemWidth, height : config.toolbarItemHeight, id : highlighterId + '_clipboard', type : 'application/x-shockwave-flash', title : sh.config.strings.copyToClipboard }, // these arguments are used in IE's collection args2 = { allowScriptAccess : 'always', wmode : 'transparent', flashVars : 'highlighterId=' + highlighterId, menu : 'false' }, swf = config.clipboardSwf, html ; if (/msie/i.test(navigator.userAgent)) { html = '' + params(args2) + params({ movie : swf }) + '' ; } else { html = '' ; } flashDiv = document.createElement('div'); flashDiv.innerHTML = html; return flashDiv; }; this.execute = function(sender, event, args) { var command = args.command; switch (command) { case 'get': var code = sh.utils.unindent( sh.utils.fixInputString(highlighter.originalCode) .replace(/</g, '<') .replace(/>/g, '>') .replace(/&/g, '&') ); if(window.clipboardData) // will fall through to the confirmation because there isn't a break window.clipboardData.setData('text', code); else return sh.utils.unindent(code); case 'ok': sh.utils.alert(sh.config.strings.copyToClipboardConfirmation); break; case 'error': sh.utils.alert(args.message); break; } }; }, /** Command to print the colored source code. */ printSource : function(highlighter) { this.create = function() { return sh.config.strings.print; }; this.execute = function(sender, event, args) { var iframe = document.createElement('IFRAME'), doc = null ; // make sure there is never more than one hidden iframe created by SH if (sh.vars.printFrame != null) document.body.removeChild(sh.vars.printFrame); sh.vars.printFrame = iframe; // this hides the iframe iframe.style.cssText = 'position:absolute;width:0px;height:0px;left:-500px;top:-500px;'; document.body.appendChild(iframe); doc = iframe.contentWindow.document; copyStyles(doc, window.document); doc.write('
' + highlighter.div.innerHTML + '
'); doc.close(); iframe.contentWindow.focus(); iframe.contentWindow.print(); function copyStyles(destDoc, sourceDoc) { var links = sourceDoc.getElementsByTagName('link'); for(var i = 0; i < links.length; i++) if(links[i].rel.toLowerCase() == 'stylesheet' && /shCore\.css$/.test(links[i].href)) destDoc.write(''); }; }; }, /** Command to display the about dialog window. */ about : function(highlighter) { this.create = function() { return sh.config.strings.help; }; this.execute = function(sender, event) { var wnd = sh.utils.popup('', '_blank', 500, 250, 'scrollbars=0'), doc = wnd.document ; doc.write(sh.config.strings.aboutDialog); doc.close(); wnd.focus(); }; } } }, utils : { /** * 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. */ indexOf : function(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. */ guid : function(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. */ merge: function(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. */ toBoolean: function(value) { switch (value) { case "true": return true; case "false": return false; } return value; }, /** * 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. */ popup: function(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. */ addEvent: function(obj, type, func) { if (obj.attachEvent) { obj['e' + type + func] = func; obj[type + func] = function() { obj['e' + type + func](window.event); } obj.attachEvent('on' + type, obj[type + func]); } else { obj.addEventListener(type, func, false); } }, /** * Displays an alert. * @param {String} str String to display. */ alert: function(str) { alert(sh.config.strings.alert + str) }, /** * Finds a brush by its alias. * * @param {String} alias Brush alias. * @param {Boolean} alert Suppresses the alert if false. * @return {Brush} Returns bursh constructor if found, null otherwise. */ findBrush: function(alias, alert) { var brushes = sh.vars.discoveredBrushes, result = null ; if (brushes == null) { brushes = {}; // Find all brushes for (var brush in sh.brushes) { var aliases = sh.brushes[brush].aliases; if (aliases == null) continue; // keep the brush name sh.brushes[brush].name = 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 && alert != false) sh.utils.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. */ eachLine: function(str, callback) { var lines = str.split('\n'); for (var i = 0; i < lines.length; i++) lines[i] = callback(lines[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. */ trimFirstAndLastLines: function(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. */ parseParams: function(str) { var match, result = {}, arrayRegex = new XRegExp("^\\[(?(.*?))\\]$"), regex = new XRegExp( "(?[\\w-]+)" + "\\s*:\\s*" + "(?" + "[\\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 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 tag. */ decorate: function(str, css) { if (str == null || str.length == 0 || str == '\n') return str; str = str.replace(/... to them so that // leading spaces aren't included. if (css != null) str = sh.utils.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 + '' + line + ''; }); 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'. */ padNumber : function(number, length) { var result = number.toString(); while (result.length < length) result = '0' + result; return result; }, /** * Measures width of a single space character. * @return {Number} Returns width of a single space character. */ measureSpace : function() { var container = document.createElement('div'), span, result = 0, body = document.body, id = sh.utils.guid('measureSpace'), // variable names will be compressed, so it's better than a plain string divOpen = '
' + divOpen + 'lines">' + divOpen + 'line">' + divOpen + 'content' + '"> ' + closeSpan + closeSpan + closeDiv + closeDiv + closeDiv + closeDiv ; body.appendChild(container); span = document.getElementById(id); if (/opera/i.test(navigator.userAgent)) { var style = window.getComputedStyle(span, null); result = parseInt(style.getPropertyValue("width")); } else { result = span.offsetWidth; } body.removeChild(container); 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. */ processTabs : function(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. */ processSmartTabs : function(code, tabSize) { var lines = code.split('\n'), 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 = sh.utils.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. */ fixInputString : function(str) { var br = /|<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. */ trim: function(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. */ unindent: function(str) { var lines = sh.utils.fixInputString(str).split('\n'), 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 (sh.utils.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. */ matchesSortCallback: function(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 regexList collection. * @return {Array} Returns a list of Match objects. */ getMatches: function(code, regexInfo) { function defaultAdd(match, regexInfo) { return [new sh.Match(match[0], match.index, regexInfo.css)]; }; var index = 0, match = null, result = [], func = regexInfo.func ? regexInfo.func : defaultAdd ; while((match = regexInfo.regex.exec(code)) != null) result = result.concat(func(match, regexInfo)); return result; }, processUrls: function(code) { var lt = '<', gt = '>' ; return code.replace(sh.regexLib.url, function(m) { var suffix = '', prefix = ''; // We include < and > in the URL for the common cases like // The problem is that they get transformed into <http://google.com> // Where as > easily looks like part of the URL string. if (m.indexOf(lt) == 0) { prefix = lt; m = m.substring(lt.length); } if (m.indexOf(gt) == m.length - gt.length) { m = m.substring(0, m.length - gt.length); suffix = gt; } return prefix + '' + m + '' + suffix; }); }, /** * Finds all