0,0 → 1,706 |
/** |
* Code Syntax Highlighter. |
* Version 1.5.1 |
* Copyright (C) 2004-2007 Alex Gorbatchev. |
* http://www.dreamprojections.com/syntaxhighlighter/ |
* |
* This library 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 2.1 of the License, or (at your option) |
* any later version. |
* |
* This library 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 Lesser General Public License for more |
* details. |
* |
* You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to |
* the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
*/ |
|
// |
// create namespaces |
// |
var dp = { |
sh : |
{ |
Toolbar : {}, |
Utils : {}, |
RegexLib: {}, |
Brushes : {}, |
Strings : { |
AboutDialog : '<html><head><title>About...</title></head><body class="dp-about"><table cellspacing="0"><tr><td class="copy"><p class="title">dp.SyntaxHighlighter</div><div class="para">Version: {V}</p><p><a href="http://www.dreamprojections.com/syntaxhighlighter/?ref=about" target="_blank">http://www.dreamprojections.com/syntaxhighlighter</a></p>©2004-2007 Alex Gorbatchev.</td></tr><tr><td class="footer"><input type="button" class="close" value="OK" onClick="window.close()"/></td></tr></table></body></html>' |
}, |
ClipboardSwf : null, |
Version : '1.5.1' |
} |
}; |
|
// make an alias |
dp.SyntaxHighlighter = dp.sh; |
|
// |
// Toolbar functions |
// |
|
dp.sh.Toolbar.Commands = { |
ExpandSource: { |
label: '+ expand source', |
check: function(highlighter) { return highlighter.collapse; }, |
func: function(sender, highlighter) |
{ |
sender.parentNode.removeChild(sender); |
highlighter.div.className = highlighter.div.className.replace('collapsed', ''); |
} |
}, |
|
// opens a new windows and puts the original unformatted source code inside. |
ViewSource: { |
label: 'view plain', |
func: function(sender, highlighter) |
{ |
var code = dp.sh.Utils.FixForBlogger(highlighter.originalCode).replace(/</g, '<'); |
var wnd = window.open('', '_blank', 'width=750, height=400, location=0, resizable=1, menubar=0, scrollbars=0'); |
wnd.document.write('<textarea style="width:99%;height:99%">' + code + '</textarea>'); |
wnd.document.close(); |
} |
}, |
|
// Copies the original source code in to the clipboard. Uses either IE only method or Flash object if ClipboardSwf is set |
CopyToClipboard: { |
label: 'copy to clipboard', |
check: function() { return window.clipboardData != null || dp.sh.ClipboardSwf != null; }, |
func: function(sender, highlighter) |
{ |
var code = dp.sh.Utils.FixForBlogger(highlighter.originalCode) |
.replace(/</g,'<') |
.replace(/>/g,'>') |
.replace(/&/g,'&') |
; |
|
if(window.clipboardData) |
{ |
window.clipboardData.setData('text', code); |
} |
else if(dp.sh.ClipboardSwf != null) |
{ |
var flashcopier = highlighter.flashCopier; |
|
if(flashcopier == null) |
{ |
flashcopier = document.createElement('div'); |
highlighter.flashCopier = flashcopier; |
highlighter.div.appendChild(flashcopier); |
} |
|
flashcopier.innerHTML = '<embed src="' + dp.sh.ClipboardSwf + '" FlashVars="clipboard='+encodeURIComponent(code)+'" width="0" height="0" type="application/x-shockwave-flash"></embed>'; |
} |
|
alert('The code is in your clipboard now'); |
} |
}, |
|
// creates an invisible iframe, puts the original source code inside and prints it |
PrintSource: { |
label: 'print', |
func: function(sender, highlighter) |
{ |
var iframe = document.createElement('IFRAME'); |
var doc = null; |
|
// 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; |
|
dp.sh.Utils.CopyStyles(doc, window.document); |
doc.write('<div class="' + highlighter.div.className.replace('collapsed', '') + ' printing">' + highlighter.div.innerHTML + '</div>'); |
doc.close(); |
|
iframe.contentWindow.focus(); |
iframe.contentWindow.print(); |
|
alert('Printing...'); |
|
document.body.removeChild(iframe); |
} |
}, |
|
About: { |
label: '?', |
func: function(highlighter) |
{ |
var wnd = window.open('', '_blank', 'dialog,width=300,height=150,scrollbars=0'); |
var doc = wnd.document; |
|
dp.sh.Utils.CopyStyles(doc, window.document); |
|
doc.write(dp.sh.Strings.AboutDialog.replace('{V}', dp.sh.Version)); |
doc.close(); |
wnd.focus(); |
} |
} |
}; |
|
// creates a <div /> with all toolbar links |
dp.sh.Toolbar.Create = function(highlighter) |
{ |
var div = document.createElement('DIV'); |
|
div.className = 'tools'; |
|
for(var name in dp.sh.Toolbar.Commands) |
{ |
var cmd = dp.sh.Toolbar.Commands[name]; |
|
if(cmd.check != null && !cmd.check(highlighter)) |
continue; |
|
div.innerHTML += '<a href="#" onclick="dp.sh.Toolbar.Command(\'' + name + '\',this);return false;">' + cmd.label + '</a>'; |
} |
|
return div; |
} |
|
// executes toolbar command by name |
dp.sh.Toolbar.Command = function(name, sender) |
{ |
var n = sender; |
|
while(n != null && n.className.indexOf('dp-highlighter') == -1) |
n = n.parentNode; |
|
if(n != null) |
dp.sh.Toolbar.Commands[name].func(sender, n.highlighter); |
} |
|
// copies all <link rel="stylesheet" /> from 'target' window to 'dest' |
dp.sh.Utils.CopyStyles = function(destDoc, sourceDoc) |
{ |
var links = sourceDoc.getElementsByTagName('link'); |
|
for(var i = 0; i < links.length; i++) |
if(links[i].rel.toLowerCase() == 'stylesheet') |
destDoc.write('<link type="text/css" rel="stylesheet" href="' + links[i].href + '"></link>'); |
} |
|
dp.sh.Utils.FixForBlogger = function(str) |
{ |
return (dp.sh.isBloggerMode == true) ? str.replace(/<br\s*\/?>|<br\s*\/?>/gi, '\n') : str; |
} |
|
// |
// Common reusable regular expressions |
// |
dp.sh.RegexLib = { |
MultiLineCComments : new RegExp('/\\*[\\s\\S]*?\\*/', 'gm'), |
SingleLineCComments : new RegExp('//.*$', 'gm'), |
SingleLinePerlComments : new RegExp('#.*$', 'gm'), |
DoubleQuotedString : new RegExp('"(?:\\.|(\\\\\\")|[^\\""\\n])*"','g'), |
SingleQuotedString : new RegExp("'(?:\\.|(\\\\\\')|[^\\''\\n])*'", 'g') |
}; |
|
// |
// Match object |
// |
dp.sh.Match = function(value, index, css) |
{ |
this.value = value; |
this.index = index; |
this.length = value.length; |
this.css = css; |
} |
|
// |
// Highlighter object |
// |
dp.sh.Highlighter = function() |
{ |
this.noGutter = false; |
this.addControls = true; |
this.collapse = false; |
this.tabsToSpaces = true; |
this.wrapColumn = 80; |
this.showColumns = true; |
} |
|
// static callback for the match sorting |
dp.sh.Highlighter.SortCallback = 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; |
} |
|
dp.sh.Highlighter.prototype.CreateElement = function(name) |
{ |
var result = document.createElement(name); |
result.highlighter = this; |
return result; |
} |
|
// gets a list of all matches for a given regular expression |
dp.sh.Highlighter.prototype.GetMatches = function(regex, css) |
{ |
var index = 0; |
var match = null; |
|
while((match = regex.exec(this.code)) != null) |
this.matches[this.matches.length] = new dp.sh.Match(match[0], match.index, css); |
} |
|
dp.sh.Highlighter.prototype.AddBit = function(str, css) |
{ |
if(str == null || str.length == 0) |
return; |
|
var span = this.CreateElement('SPAN'); |
|
// str = str.replace(/&/g, '&'); |
str = str.replace(/ /g, ' '); |
str = str.replace(/</g, '<'); |
// str = str.replace(/</g, '<'); |
// str = str.replace(/>/g, '>'); |
str = str.replace(/\n/gm, ' <br>'); |
|
// when adding a piece of code, check to see if it has line breaks in it |
// and if it does, wrap individual line breaks with span tags |
if(css != null) |
{ |
if((/br/gi).test(str)) |
{ |
var lines = str.split(' <br>'); |
|
for(var i = 0; i < lines.length; i++) |
{ |
span = this.CreateElement('SPAN'); |
span.className = css; |
span.innerHTML = lines[i]; |
|
this.div.appendChild(span); |
|
// don't add a <BR> for the last line |
if(i + 1 < lines.length) |
this.div.appendChild(this.CreateElement('BR')); |
} |
} |
else |
{ |
span.className = css; |
span.innerHTML = str; |
this.div.appendChild(span); |
} |
} |
else |
{ |
span.innerHTML = str; |
this.div.appendChild(span); |
} |
} |
|
// checks if one match is inside any other match |
dp.sh.Highlighter.prototype.IsInside = function(match) |
{ |
if(match == null || match.length == 0) |
return false; |
|
for(var i = 0; i < this.matches.length; i++) |
{ |
var c = this.matches[i]; |
|
if(c == null) |
continue; |
|
if((match.index > c.index) && (match.index < c.index + c.length)) |
return true; |
} |
|
return false; |
} |
|
dp.sh.Highlighter.prototype.ProcessRegexList = function() |
{ |
for(var i = 0; i < this.regexList.length; i++) |
this.GetMatches(this.regexList[i].regex, this.regexList[i].css); |
} |
|
dp.sh.Highlighter.prototype.ProcessSmartTabs = function(code) |
{ |
var lines = code.split('\n'); |
var result = ''; |
var tabSize = 4; |
var tab = '\t'; |
|
// This function inserts specified amount of spaces in the string |
// where a tab is while removing that given tab. |
function InsertSpaces(line, pos, count) |
{ |
var left = line.substr(0, pos); |
var right = line.substr(pos + 1, line.length); // pos + 1 will get rid of the tab |
var spaces = ''; |
|
for(var i = 0; i < count; i++) |
spaces += ' '; |
|
return left + spaces + right; |
} |
|
// This function process one line for 'smart tabs' |
function ProcessLine(line, tabSize) |
{ |
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; |
} |
|
// Go through all the lines and do the 'smart tabs' magic. |
for(var i = 0; i < lines.length; i++) |
result += ProcessLine(lines[i], tabSize) + '\n'; |
|
return result; |
} |
|
dp.sh.Highlighter.prototype.SwitchToList = function() |
{ |
// thanks to Lachlan Donald from SitePoint.com for this <br/> tag fix. |
var html = this.div.innerHTML.replace(/<(br)\/?>/gi, '\n'); |
var lines = html.split('\n'); |
|
if(this.addControls == true) |
this.bar.appendChild(dp.sh.Toolbar.Create(this)); |
|
// add columns ruler |
if(this.showColumns) |
{ |
var div = this.CreateElement('div'); |
var columns = this.CreateElement('div'); |
var showEvery = 10; |
var i = 1; |
|
while(i <= 150) |
{ |
if(i % showEvery == 0) |
{ |
div.innerHTML += i; |
i += (i + '').length; |
} |
else |
{ |
div.innerHTML += '·'; |
i++; |
} |
} |
|
columns.className = 'columns'; |
columns.appendChild(div); |
this.bar.appendChild(columns); |
} |
|
for(var i = 0, lineIndex = this.firstLine; i < lines.length - 1; i++, lineIndex++) |
{ |
var li = this.CreateElement('LI'); |
var span = this.CreateElement('SPAN'); |
|
// uses .line1 and .line2 css styles for alternating lines |
li.className = (i % 2 == 0) ? 'alt' : ''; |
span.innerHTML = lines[i] + ' '; |
|
li.appendChild(span); |
this.ol.appendChild(li); |
} |
|
this.div.innerHTML = ''; |
} |
|
dp.sh.Highlighter.prototype.Highlight = function(code) |
{ |
function Trim(str) |
{ |
return str.replace(/^\s*(.*?)[\s\n]*$/g, '$1'); |
} |
|
function Chop(str) |
{ |
return str.replace(/\n*$/, '').replace(/^\n*/, ''); |
} |
|
function Unindent(str) |
{ |
var lines = dp.sh.Utils.FixForBlogger(str).split('\n'); |
var indents = new Array(); |
var regex = new RegExp('^\\s*', 'g'); |
var min = 1000; |
|
// go through every line and check for common number of indents |
for(var i = 0; i < lines.length && min > 0; i++) |
{ |
if(Trim(lines[i]).length == 0) |
continue; |
|
var matches = regex.exec(lines[i]); |
|
if(matches != null && matches.length > 0) |
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'); |
} |
|
// This function returns a portions of the string from pos1 to pos2 inclusive |
function Copy(string, pos1, pos2) |
{ |
return string.substr(pos1, pos2 - pos1); |
} |
|
var pos = 0; |
|
if(code == null) |
code = ''; |
|
this.originalCode = code; |
this.code = Chop(Unindent(code)); |
this.div = this.CreateElement('DIV'); |
this.bar = this.CreateElement('DIV'); |
this.ol = this.CreateElement('OL'); |
this.matches = new Array(); |
|
this.div.className = 'dp-highlighter'; |
this.div.highlighter = this; |
|
this.bar.className = 'bar'; |
|
// set the first line |
this.ol.start = this.firstLine; |
|
if(this.CssClass != null) |
this.ol.className = this.CssClass; |
|
if(this.collapse) |
this.div.className += ' collapsed'; |
|
if(this.noGutter) |
this.div.className += ' nogutter'; |
|
// replace tabs with spaces |
if(this.tabsToSpaces == true) |
this.code = this.ProcessSmartTabs(this.code); |
|
this.ProcessRegexList(); |
|
// if no matches found, add entire code as plain text |
if(this.matches.length == 0) |
{ |
this.AddBit(this.code, null); |
this.SwitchToList(); |
this.div.appendChild(this.bar); |
this.div.appendChild(this.ol); |
return; |
} |
|
// sort the matches |
this.matches = this.matches.sort(dp.sh.Highlighter.SortCallback); |
|
// The following loop 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. |
for(var i = 0; i < this.matches.length; i++) |
if(this.IsInside(this.matches[i])) |
this.matches[i] = null; |
|
// 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 < this.matches.length; i++) |
{ |
var match = this.matches[i]; |
|
if(match == null || match.length == 0) |
continue; |
|
this.AddBit(Copy(this.code, pos, match.index), null); |
this.AddBit(match.value, match.css); |
|
pos = match.index + match.length; |
} |
|
this.AddBit(this.code.substr(pos), null); |
|
this.SwitchToList(); |
this.div.appendChild(this.bar); |
this.div.appendChild(this.ol); |
} |
|
dp.sh.Highlighter.prototype.GetKeywords = function(str) |
{ |
return '\\b' + str.replace(/ /g, '\\b|\\b') + '\\b'; |
} |
|
dp.sh.BloggerMode = function() |
{ |
dp.sh.isBloggerMode = true; |
} |
|
// highlightes all elements identified by name and gets source code from specified property |
dp.sh.HighlightAll = function(name, showGutter /* optional */, showControls /* optional */, collapseAll /* optional */, firstLine /* optional */, showColumns /* optional */) |
{ |
function FindValue() |
{ |
var a = arguments; |
|
for(var i = 0; i < a.length; i++) |
{ |
if(a[i] == null) |
continue; |
|
if(typeof(a[i]) == 'string' && a[i] != '') |
return a[i] + ''; |
|
if(typeof(a[i]) == 'object' && a[i].value != '') |
return a[i].value + ''; |
} |
|
return null; |
} |
|
function IsOptionSet(value, list) |
{ |
for(var i = 0; i < list.length; i++) |
if(list[i] == value) |
return true; |
|
return false; |
} |
|
function GetOptionValue(name, list, defaultValue) |
{ |
var regex = new RegExp('^' + name + '\\[(\\w+)\\]$', 'gi'); |
var matches = null; |
|
for(var i = 0; i < list.length; i++) |
if((matches = regex.exec(list[i])) != null) |
return matches[1]; |
|
return defaultValue; |
} |
|
function FindTagsByName(list, name, tagName) |
{ |
var tags = document.getElementsByTagName(tagName); |
|
for(var i = 0; i < tags.length; i++) |
//if( (new RegExp('\s*'+name+'\s*')).test(tags[i].getAttribute('class')) ) |
if(tags[i].getAttribute('name') == name) |
list.push(tags[i]); |
} |
|
var elements = []; |
var highlighter = null; |
var registered = {}; |
var propertyName = 'innerHTML'; |
|
// for some reason IE doesn't find <pre/> by name, however it does see them just fine by tag name... |
FindTagsByName(elements, name, 'pre'); |
FindTagsByName(elements, name, 'textarea'); |
|
if(elements.length == 0) |
return; |
|
// register all brushes |
for(var brush in dp.sh.Brushes) |
{ |
var aliases = dp.sh.Brushes[brush].Aliases; |
|
if(aliases == null) |
continue; |
|
for(var i = 0; i < aliases.length; i++) |
registered[aliases[i]] = brush; |
} |
|
for(var i = 0; i < elements.length; i++) |
{ |
var element = elements[i]; |
var options = FindValue( |
element.attributes['class'], element.className, |
element.attributes['language'], element.language |
); |
var language = ''; |
|
if(options == null) |
continue; |
|
options = options.split(':'); |
|
language = options[0].toLowerCase(); |
|
if(registered[language] == null) |
continue; |
|
// instantiate a brush |
highlighter = new dp.sh.Brushes[registered[language]](); |
|
// hide the original element |
element.style.display = 'none'; |
|
highlighter.noGutter = (showGutter == null) ? IsOptionSet('nogutter', options) : !showGutter; |
highlighter.addControls = (showControls == null) ? !IsOptionSet('nocontrols', options) : showControls; |
highlighter.collapse = (collapseAll == null) ? IsOptionSet('collapse', options) : collapseAll; |
highlighter.showColumns = (showColumns == null) ? IsOptionSet('showcolumns', options) : showColumns; |
|
// write out custom brush style |
var headNode = document.getElementsByTagName('head')[0]; |
if(highlighter.Style && headNode) |
{ |
var styleNode = document.createElement('style'); |
styleNode.setAttribute('type', 'text/css'); |
|
if(styleNode.styleSheet) // for IE |
{ |
styleNode.styleSheet.cssText = highlighter.Style; |
} |
else // for everyone else |
{ |
var textNode = document.createTextNode(highlighter.Style); |
styleNode.appendChild(textNode); |
} |
|
headNode.appendChild(styleNode); |
} |
|
// first line idea comes from Andrew Collington, thanks! |
highlighter.firstLine = (firstLine == null) ? parseInt(GetOptionValue('firstline', options, 1)) : firstLine; |
|
highlighter.Highlight(element[propertyName]); |
|
highlighter.source = element; |
|
element.parentNode.insertBefore(highlighter.div, element); |
} |
} |