1/** 2 * Code Syntax Highlighter. 3 * Version 1.5.1 4 * Copyright (C) 2004-2007 Alex Gorbatchev. 5 * http://www.dreamprojections.com/syntaxhighlighter/ 6 * 7 * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General 8 * Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) 9 * any later version. 10 * 11 * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied 12 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 13 * details. 14 * 15 * You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to 16 * the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 17 */ 18 19// 20// create namespaces 21// 22var dp = { 23 sh : 24 { 25 Toolbar : {}, 26 Utils : {}, 27 RegexLib: {}, 28 Brushes : {}, 29 Strings : { 30 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>' 31 }, 32 ClipboardSwf : null, 33 Version : '1.5.1' 34 } 35}; 36 37// make an alias 38dp.SyntaxHighlighter = dp.sh; 39 40// 41// Toolbar functions 42// 43 44dp.sh.Toolbar.Commands = { 45 ExpandSource: { 46 label: '+ expand source', 47 check: function(highlighter) { return highlighter.collapse; }, 48 func: function(sender, highlighter) 49 { 50 sender.parentNode.removeChild(sender); 51 highlighter.div.className = highlighter.div.className.replace('collapsed', ''); 52 } 53 }, 54 55 // opens a new windows and puts the original unformatted source code inside. 56 ViewSource: { 57 label: 'view plain', 58 func: function(sender, highlighter) 59 { 60 var code = dp.sh.Utils.FixForBlogger(highlighter.originalCode).replace(/</g, '<'); 61 var wnd = window.open('', '_blank', 'width=750, height=400, location=0, resizable=1, menubar=0, scrollbars=0'); 62 wnd.document.write('<textarea style="width:99%;height:99%">' + code + '</textarea>'); 63 wnd.document.close(); 64 } 65 }, 66 67 // Copies the original source code in to the clipboard. Uses either IE only method or Flash object if ClipboardSwf is set 68 CopyToClipboard: { 69 label: 'copy to clipboard', 70 check: function() { return window.clipboardData != null || dp.sh.ClipboardSwf != null; }, 71 func: function(sender, highlighter) 72 { 73 var code = dp.sh.Utils.FixForBlogger(highlighter.originalCode) 74 .replace(/</g,'<') 75 .replace(/>/g,'>') 76 .replace(/&/g,'&') 77 ; 78 79 if(window.clipboardData) 80 { 81 window.clipboardData.setData('text', code); 82 } 83 else if(dp.sh.ClipboardSwf != null) 84 { 85 var flashcopier = highlighter.flashCopier; 86 87 if(flashcopier == null) 88 { 89 flashcopier = document.createElement('div'); 90 highlighter.flashCopier = flashcopier; 91 highlighter.div.appendChild(flashcopier); 92 } 93 94 flashcopier.innerHTML = '<embed src="' + dp.sh.ClipboardSwf + '" FlashVars="clipboard='+encodeURIComponent(code)+'" width="0" height="0" type="application/x-shockwave-flash"></embed>'; 95 } 96 97 alert('The code is in your clipboard now'); 98 } 99 }, 100 101 // creates an invisible iframe, puts the original source code inside and prints it 102 PrintSource: { 103 label: 'print', 104 func: function(sender, highlighter) 105 { 106 var iframe = document.createElement('IFRAME'); 107 var doc = null; 108 109 // this hides the iframe 110 iframe.style.cssText = 'position:absolute;width:0px;height:0px;left:-500px;top:-500px;'; 111 112 document.body.appendChild(iframe); 113 doc = iframe.contentWindow.document; 114 115 dp.sh.Utils.CopyStyles(doc, window.document); 116 doc.write('<div class="' + highlighter.div.className.replace('collapsed', '') + ' printing">' + highlighter.div.innerHTML + '</div>'); 117 doc.close(); 118 119 iframe.contentWindow.focus(); 120 iframe.contentWindow.print(); 121 122 alert('Printing...'); 123 124 document.body.removeChild(iframe); 125 } 126 }, 127 128 About: { 129 label: '?', 130 func: function(highlighter) 131 { 132 var wnd = window.open('', '_blank', 'dialog,width=300,height=150,scrollbars=0'); 133 var doc = wnd.document; 134 135 dp.sh.Utils.CopyStyles(doc, window.document); 136 137 doc.write(dp.sh.Strings.AboutDialog.replace('{V}', dp.sh.Version)); 138 doc.close(); 139 wnd.focus(); 140 } 141 } 142}; 143 144// creates a <div /> with all toolbar links 145dp.sh.Toolbar.Create = function(highlighter) 146{ 147 var div = document.createElement('DIV'); 148 149 div.className = 'tools'; 150 151 for(var name in dp.sh.Toolbar.Commands) 152 { 153 var cmd = dp.sh.Toolbar.Commands[name]; 154 155 if(cmd.check != null && !cmd.check(highlighter)) 156 continue; 157 158 div.innerHTML += '<a href="#" onclick="dp.sh.Toolbar.Command(\'' + name + '\',this);return false;">' + cmd.label + '</a>'; 159 } 160 161 return div; 162} 163 164// executes toolbar command by name 165dp.sh.Toolbar.Command = function(name, sender) 166{ 167 var n = sender; 168 169 while(n != null && n.className.indexOf('dp-highlighter') == -1) 170 n = n.parentNode; 171 172 if(n != null) 173 dp.sh.Toolbar.Commands[name].func(sender, n.highlighter); 174} 175 176// copies all <link rel="stylesheet" /> from 'target' window to 'dest' 177dp.sh.Utils.CopyStyles = function(destDoc, sourceDoc) 178{ 179 var links = sourceDoc.getElementsByTagName('link'); 180 181 for(var i = 0; i < links.length; i++) 182 if(links[i].rel.toLowerCase() == 'stylesheet') 183 destDoc.write('<link type="text/css" rel="stylesheet" href="' + links[i].href + '"></link>'); 184} 185 186dp.sh.Utils.FixForBlogger = function(str) 187{ 188 return (dp.sh.isBloggerMode == true) ? str.replace(/<br\s*\/?>|<br\s*\/?>/gi, '\n') : str; 189} 190 191// 192// Common reusable regular expressions 193// 194dp.sh.RegexLib = { 195 MultiLineCComments : new RegExp('/\\*[\\s\\S]*?\\*/', 'gm'), 196 SingleLineCComments : new RegExp('//.*$', 'gm'), 197 SingleLinePerlComments : new RegExp('#.*$', 'gm'), 198 DoubleQuotedString : new RegExp('"(?:\\.|(\\\\\\")|[^\\""\\n])*"','g'), 199 SingleQuotedString : new RegExp("'(?:\\.|(\\\\\\')|[^\\''\\n])*'", 'g') 200}; 201 202// 203// Match object 204// 205dp.sh.Match = function(value, index, css) 206{ 207 this.value = value; 208 this.index = index; 209 this.length = value.length; 210 this.css = css; 211} 212 213// 214// Highlighter object 215// 216dp.sh.Highlighter = function() 217{ 218 this.noGutter = false; 219 this.addControls = true; 220 this.collapse = false; 221 this.tabsToSpaces = true; 222 this.wrapColumn = 80; 223 this.showColumns = true; 224} 225 226// static callback for the match sorting 227dp.sh.Highlighter.SortCallback = function(m1, m2) 228{ 229 // sort matches by index first 230 if(m1.index < m2.index) 231 return -1; 232 else if(m1.index > m2.index) 233 return 1; 234 else 235 { 236 // if index is the same, sort by length 237 if(m1.length < m2.length) 238 return -1; 239 else if(m1.length > m2.length) 240 return 1; 241 } 242 return 0; 243} 244 245dp.sh.Highlighter.prototype.CreateElement = function(name) 246{ 247 var result = document.createElement(name); 248 result.highlighter = this; 249 return result; 250} 251 252// gets a list of all matches for a given regular expression 253dp.sh.Highlighter.prototype.GetMatches = function(regex, css) 254{ 255 var index = 0; 256 var match = null; 257 258 while((match = regex.exec(this.code)) != null) 259 this.matches[this.matches.length] = new dp.sh.Match(match[0], match.index, css); 260} 261 262dp.sh.Highlighter.prototype.AddBit = function(str, css) 263{ 264 if(str == null || str.length == 0) 265 return; 266 267 var span = this.CreateElement('SPAN'); 268 269// str = str.replace(/&/g, '&'); 270 str = str.replace(/ /g, ' '); 271 str = str.replace(/</g, '<'); 272// str = str.replace(/</g, '<'); 273// str = str.replace(/>/g, '>'); 274 str = str.replace(/\n/gm, ' <br>'); 275 276 // when adding a piece of code, check to see if it has line breaks in it 277 // and if it does, wrap individual line breaks with span tags 278 if(css != null) 279 { 280 if((/br/gi).test(str)) 281 { 282 var lines = str.split(' <br>'); 283 284 for(var i = 0; i < lines.length; i++) 285 { 286 span = this.CreateElement('SPAN'); 287 span.className = css; 288 span.innerHTML = lines[i]; 289 290 this.div.appendChild(span); 291 292 // don't add a <BR> for the last line 293 if(i + 1 < lines.length) 294 this.div.appendChild(this.CreateElement('BR')); 295 } 296 } 297 else 298 { 299 span.className = css; 300 span.innerHTML = str; 301 this.div.appendChild(span); 302 } 303 } 304 else 305 { 306 span.innerHTML = str; 307 this.div.appendChild(span); 308 } 309} 310 311// checks if one match is inside any other match 312dp.sh.Highlighter.prototype.IsInside = function(match) 313{ 314 if(match == null || match.length == 0) 315 return false; 316 317 for(var i = 0; i < this.matches.length; i++) 318 { 319 var c = this.matches[i]; 320 321 if(c == null) 322 continue; 323 324 if((match.index > c.index) && (match.index < c.index + c.length)) 325 return true; 326 } 327 328 return false; 329} 330 331dp.sh.Highlighter.prototype.ProcessRegexList = function() 332{ 333 for(var i = 0; i < this.regexList.length; i++) 334 this.GetMatches(this.regexList[i].regex, this.regexList[i].css); 335} 336 337dp.sh.Highlighter.prototype.ProcessSmartTabs = function(code) 338{ 339 var lines = code.split('\n'); 340 var result = ''; 341 var tabSize = 4; 342 var tab = '\t'; 343 344 // This function inserts specified amount of spaces in the string 345 // where a tab is while removing that given tab. 346 function InsertSpaces(line, pos, count) 347 { 348 var left = line.substr(0, pos); 349 var right = line.substr(pos + 1, line.length); // pos + 1 will get rid of the tab 350 var spaces = ''; 351 352 for(var i = 0; i < count; i++) 353 spaces += ' '; 354 355 return left + spaces + right; 356 } 357 358 // This function process one line for 'smart tabs' 359 function ProcessLine(line, tabSize) 360 { 361 if(line.indexOf(tab) == -1) 362 return line; 363 364 var pos = 0; 365 366 while((pos = line.indexOf(tab)) != -1) 367 { 368 // This is pretty much all there is to the 'smart tabs' logic. 369 // Based on the position within the line and size of a tab, 370 // calculate the amount of spaces we need to insert. 371 var spaces = tabSize - pos % tabSize; 372 373 line = InsertSpaces(line, pos, spaces); 374 } 375 376 return line; 377 } 378 379 // Go through all the lines and do the 'smart tabs' magic. 380 for(var i = 0; i < lines.length; i++) 381 result += ProcessLine(lines[i], tabSize) + '\n'; 382 383 return result; 384} 385 386dp.sh.Highlighter.prototype.SwitchToList = function() 387{ 388 // thanks to Lachlan Donald from SitePoint.com for this <br/> tag fix. 389 var html = this.div.innerHTML.replace(/<(br)\/?>/gi, '\n'); 390 var lines = html.split('\n'); 391 392 if(this.addControls == true) 393 this.bar.appendChild(dp.sh.Toolbar.Create(this)); 394 395 // add columns ruler 396 if(this.showColumns) 397 { 398 var div = this.CreateElement('div'); 399 var columns = this.CreateElement('div'); 400 var showEvery = 10; 401 var i = 1; 402 403 while(i <= 150) 404 { 405 if(i % showEvery == 0) 406 { 407 div.innerHTML += i; 408 i += (i + '').length; 409 } 410 else 411 { 412 div.innerHTML += '·'; 413 i++; 414 } 415 } 416 417 columns.className = 'columns'; 418 columns.appendChild(div); 419 this.bar.appendChild(columns); 420 } 421 422 for(var i = 0, lineIndex = this.firstLine; i < lines.length - 1; i++, lineIndex++) 423 { 424 var li = this.CreateElement('LI'); 425 var span = this.CreateElement('SPAN'); 426 427 // uses .line1 and .line2 css styles for alternating lines 428 li.className = (i % 2 == 0) ? 'alt' : ''; 429 span.innerHTML = lines[i] + ' '; 430 431 li.appendChild(span); 432 this.ol.appendChild(li); 433 } 434 435 this.div.innerHTML = ''; 436} 437 438dp.sh.Highlighter.prototype.Highlight = function(code) 439{ 440 function Trim(str) 441 { 442 return str.replace(/^\s*(.*?)[\s\n]*$/g, '$1'); 443 } 444 445 function Chop(str) 446 { 447 return str.replace(/\n*$/, '').replace(/^\n*/, ''); 448 } 449 450 function Unindent(str) 451 { 452 var lines = dp.sh.Utils.FixForBlogger(str).split('\n'); 453 var indents = new Array(); 454 var regex = new RegExp('^\\s*', 'g'); 455 var min = 1000; 456 457 // go through every line and check for common number of indents 458 for(var i = 0; i < lines.length && min > 0; i++) 459 { 460 if(Trim(lines[i]).length == 0) 461 continue; 462 463 var matches = regex.exec(lines[i]); 464 465 if(matches != null && matches.length > 0) 466 min = Math.min(matches[0].length, min); 467 } 468 469 // trim minimum common number of white space from the begining of every line 470 if(min > 0) 471 for(var i = 0; i < lines.length; i++) 472 lines[i] = lines[i].substr(min); 473 474 return lines.join('\n'); 475 } 476 477 // This function returns a portions of the string from pos1 to pos2 inclusive 478 function Copy(string, pos1, pos2) 479 { 480 return string.substr(pos1, pos2 - pos1); 481 } 482 483 var pos = 0; 484 485 if(code == null) 486 code = ''; 487 488 this.originalCode = code; 489 this.code = Chop(Unindent(code)); 490 this.div = this.CreateElement('DIV'); 491 this.bar = this.CreateElement('DIV'); 492 this.ol = this.CreateElement('OL'); 493 this.matches = new Array(); 494 495 this.div.className = 'dp-highlighter'; 496 this.div.highlighter = this; 497 498 this.bar.className = 'bar'; 499 500 // set the first line 501 this.ol.start = this.firstLine; 502 503 if(this.CssClass != null) 504 this.ol.className = this.CssClass; 505 506 if(this.collapse) 507 this.div.className += ' collapsed'; 508 509 if(this.noGutter) 510 this.div.className += ' nogutter'; 511 512 // replace tabs with spaces 513 if(this.tabsToSpaces == true) 514 this.code = this.ProcessSmartTabs(this.code); 515 516 this.ProcessRegexList(); 517 518 // if no matches found, add entire code as plain text 519 if(this.matches.length == 0) 520 { 521 this.AddBit(this.code, null); 522 this.SwitchToList(); 523 this.div.appendChild(this.bar); 524 this.div.appendChild(this.ol); 525 return; 526 } 527 528 // sort the matches 529 this.matches = this.matches.sort(dp.sh.Highlighter.SortCallback); 530 531 // The following loop checks to see if any of the matches are inside 532 // of other matches. This process would get rid of highligted strings 533 // inside comments, keywords inside strings and so on. 534 for(var i = 0; i < this.matches.length; i++) 535 if(this.IsInside(this.matches[i])) 536 this.matches[i] = null; 537 538 // Finally, go through the final list of matches and pull the all 539 // together adding everything in between that isn't a match. 540 for(var i = 0; i < this.matches.length; i++) 541 { 542 var match = this.matches[i]; 543 544 if(match == null || match.length == 0) 545 continue; 546 547 this.AddBit(Copy(this.code, pos, match.index), null); 548 this.AddBit(match.value, match.css); 549 550 pos = match.index + match.length; 551 } 552 553 this.AddBit(this.code.substr(pos), null); 554 555 this.SwitchToList(); 556 this.div.appendChild(this.bar); 557 this.div.appendChild(this.ol); 558} 559 560dp.sh.Highlighter.prototype.GetKeywords = function(str) 561{ 562 return '\\b' + str.replace(/ /g, '\\b|\\b') + '\\b'; 563} 564 565dp.sh.BloggerMode = function() 566{ 567 dp.sh.isBloggerMode = true; 568} 569 570// highlightes all elements identified by name and gets source code from specified property 571dp.sh.HighlightAll = function(name, showGutter /* optional */, showControls /* optional */, collapseAll /* optional */, firstLine /* optional */, showColumns /* optional */) 572{ 573 function FindValue() 574 { 575 var a = arguments; 576 577 for(var i = 0; i < a.length; i++) 578 { 579 if(a[i] == null) 580 continue; 581 582 if(typeof(a[i]) == 'string' && a[i] != '') 583 return a[i] + ''; 584 585 if(typeof(a[i]) == 'object' && a[i].value != '') 586 return a[i].value + ''; 587 } 588 589 return null; 590 } 591 592 function IsOptionSet(value, list) 593 { 594 for(var i = 0; i < list.length; i++) 595 if(list[i] == value) 596 return true; 597 598 return false; 599 } 600 601 function GetOptionValue(name, list, defaultValue) 602 { 603 var regex = new RegExp('^' + name + '\\[(\\w+)\\]$', 'gi'); 604 var matches = null; 605 606 for(var i = 0; i < list.length; i++) 607 if((matches = regex.exec(list[i])) != null) 608 return matches[1]; 609 610 return defaultValue; 611 } 612 613 function FindTagsByName(list, name, tagName) 614 { 615 var tags = document.getElementsByTagName(tagName); 616 617 for(var i = 0; i < tags.length; i++) 618 if(tags[i].getAttribute('name') == name) 619 list.push(tags[i]); 620 } 621 622 var elements = []; 623 var highlighter = null; 624 var registered = {}; 625 var propertyName = 'innerHTML'; 626 627 // for some reason IE doesn't find <pre/> by name, however it does see them just fine by tag name... 628 FindTagsByName(elements, name, 'pre'); 629 FindTagsByName(elements, name, 'textarea'); 630 631 if(elements.length == 0) 632 return; 633 634 // register all brushes 635 for(var brush in dp.sh.Brushes) 636 { 637 var aliases = dp.sh.Brushes[brush].Aliases; 638 639 if(aliases == null) 640 continue; 641 642 for(var i = 0; i < aliases.length; i++) 643 registered[aliases[i]] = brush; 644 } 645 646 for(var i = 0; i < elements.length; i++) 647 { 648 var element = elements[i]; 649 var options = FindValue( 650 element.attributes['class'], element.className, 651 element.attributes['language'], element.language 652 ); 653 var language = ''; 654 655 if(options == null) 656 continue; 657 658 options = options.split(':'); 659 660 language = options[0].toLowerCase(); 661 662 if(registered[language] == null) 663 continue; 664 665 // instantiate a brush 666 highlighter = new dp.sh.Brushes[registered[language]](); 667 668 // hide the original element 669 element.style.display = 'none'; 670 671 highlighter.noGutter = (showGutter == null) ? IsOptionSet('nogutter', options) : !showGutter; 672 highlighter.addControls = (showControls == null) ? !IsOptionSet('nocontrols', options) : showControls; 673 highlighter.collapse = (collapseAll == null) ? IsOptionSet('collapse', options) : collapseAll; 674 highlighter.showColumns = (showColumns == null) ? IsOptionSet('showcolumns', options) : showColumns; 675 676 // write out custom brush style 677 var headNode = document.getElementsByTagName('head')[0]; 678 if(highlighter.Style && headNode) 679 { 680 var styleNode = document.createElement('style'); 681 styleNode.setAttribute('type', 'text/css'); 682 683 if(styleNode.styleSheet) // for IE 684 { 685 styleNode.styleSheet.cssText = highlighter.Style; 686 } 687 else // for everyone else 688 { 689 var textNode = document.createTextNode(highlighter.Style); 690 styleNode.appendChild(textNode); 691 } 692 693 headNode.appendChild(styleNode); 694 } 695 696 // first line idea comes from Andrew Collington, thanks! 697 highlighter.firstLine = (firstLine == null) ? parseInt(GetOptionValue('firstline', options, 1)) : firstLine; 698 699 highlighter.Highlight(element[propertyName]); 700 701 highlighter.source = element; 702 703 element.parentNode.insertBefore(highlighter.div, element); 704 } 705} 706