1// 2// Begin anonymous function. This is used to contain local scope variables without polutting global scope. 3// 4if (typeof(SyntaxHighlighter) == 'undefined') var SyntaxHighlighter = function() { 5 6// CommonJS 7if (typeof(require) != 'undefined' && typeof(XRegExp) == 'undefined') 8{ 9 XRegExp = require('XRegExp').XRegExp; 10} 11 12// Shortcut object which will be assigned to the SyntaxHighlighter variable. 13// This is a shorthand for local reference in order to avoid long namespace 14// references to SyntaxHighlighter.whatever... 15var sh = { 16 defaults : { 17 /** Additional CSS class names to be added to highlighter elements. */ 18 'class-name' : '', 19 20 /** First line number. */ 21 'first-line' : 1, 22 23 /** 24 * Pads line numbers. Possible values are: 25 * 26 * false - don't pad line numbers. 27 * true - automaticaly pad numbers with minimum required number of leading zeroes. 28 * [int] - length up to which pad line numbers. 29 */ 30 'pad-line-numbers' : false, 31 32 /** Lines to highlight. */ 33 'highlight' : null, 34 35 /** Title to be displayed above the code block. */ 36 'title' : null, 37 38 /** Enables or disables smart tabs. */ 39 'smart-tabs' : true, 40 41 /** Gets or sets tab size. */ 42 'tab-size' : 4, 43 44 /** Enables or disables gutter. */ 45 'gutter' : true, 46 47 /** Enables or disables toolbar. */ 48 'toolbar' : true, 49 50 /** Enables quick code copy and paste from double click. */ 51 'quick-code' : true, 52 53 /** Forces code view to be collapsed. */ 54 'collapse' : false, 55 56 /** Enables or disables automatic links. */ 57 'auto-links' : true, 58 59 /** Gets or sets light mode. Equavalent to turning off gutter and toolbar. */ 60 'light' : false, 61 62 'unindent' : true, 63 64 'html-script' : false 65 }, 66 67 config : { 68 space : ' ', 69 70 /** Enables use of <SCRIPT type="syntaxhighlighter" /> tags. */ 71 useScriptTags : true, 72 73 /** Blogger mode flag. */ 74 bloggerMode : false, 75 76 stripBrs : false, 77 78 /** Name of the tag that SyntaxHighlighter will automatically look for. */ 79 tagName : 'pre', 80 81 strings : { 82 expandSource : 'expand source', 83 help : '?', 84 alert: 'SyntaxHighlighter\n\n', 85 noBrush : 'Can\'t find brush for: ', 86 brushNotHtmlScript : 'Brush wasn\'t configured for html-script option: ', 87 88 // this is populated by the build script 89 aboutDialog : '@ABOUT@' 90 } 91 }, 92 93 /** Internal 'global' variables. */ 94 vars : { 95 discoveredBrushes : null, 96 highlighters : {} 97 }, 98 99 /** This object is populated by user included external brush files. */ 100 brushes : {}, 101 102 /** Common regular expressions. */ 103 regexLib : { 104 multiLineCComments : /\/\*[\s\S]*?\*\//gm, 105 singleLineCComments : /\/\/.*$/gm, 106 singleLinePerlComments : /#.*$/gm, 107 doubleQuotedString : /"([^\\"\n]|\\.)*"/g, 108 singleQuotedString : /'([^\\'\n]|\\.)*'/g, 109 multiLineDoubleQuotedString : new XRegExp('"([^\\\\"]|\\\\.)*"', 'gs'), 110 multiLineSingleQuotedString : new XRegExp("'([^\\\\']|\\\\.)*'", 'gs'), 111 xmlComments : /(<|<)!--[\s\S]*?--(>|>)/gm, 112 url : /\w+:\/\/[\w-.\/?%&=:@;#]*/g, 113 114 /** <?= ?> tags. */ 115 phpScriptTags : { left: /(<|<)\?(?:=|php)?/g, right: /\?(>|>)/g, 'eof' : true }, 116 117 /** <%= %> tags. */ 118 aspScriptTags : { left: /(<|<)%=?/g, right: /%(>|>)/g }, 119 120 /** <script> tags. */ 121 scriptScriptTags : { left: /(<|<)\s*script.*?(>|>)/gi, right: /(<|<)\/\s*script\s*(>|>)/gi } 122 }, 123 124 toolbar: { 125 /** 126 * Generates HTML markup for the toolbar. 127 * @param {Highlighter} highlighter Highlighter instance. 128 * @return {String} Returns HTML markup. 129 */ 130 getHtml: function(highlighter) 131 { 132 var html = '<div class="toolbar">', 133 items = sh.toolbar.items, 134 list = items.list 135 ; 136 137 function defaultGetHtml(highlighter, name) 138 { 139 return sh.toolbar.getButtonHtml(highlighter, name, sh.config.strings[name]); 140 }; 141 142 for (var i = 0; i < list.length; i++) 143 html += (items[list[i]].getHtml || defaultGetHtml)(highlighter, list[i]); 144 145 html += '</div>'; 146 147 return html; 148 }, 149 150 /** 151 * Generates HTML markup for a regular button in the toolbar. 152 * @param {Highlighter} highlighter Highlighter instance. 153 * @param {String} commandName Command name that would be executed. 154 * @param {String} label Label text to display. 155 * @return {String} Returns HTML markup. 156 */ 157 getButtonHtml: function(highlighter, commandName, label) 158 { 159 return '<span><a href="#" class="toolbar_item' 160 + ' command_' + commandName 161 + ' ' + commandName 162 + '">' + label + '</a></span>' 163 ; 164 }, 165 166 /** 167 * Event handler for a toolbar anchor. 168 */ 169 handler: function(e) 170 { 171 var target = e.target, 172 className = target.className || '' 173 ; 174 175 function getValue(name) 176 { 177 var r = new RegExp(name + '_(\\w+)'), 178 match = r.exec(className) 179 ; 180 181 return match ? match[1] : null; 182 }; 183 184 var highlighter = getHighlighterById(findParentElement(target, '.syntaxhighlighter').id), 185 commandName = getValue('command') 186 ; 187 188 // execute the toolbar command 189 if (highlighter && commandName) 190 sh.toolbar.items[commandName].execute(highlighter); 191 192 // disable default A click behaviour 193 e.preventDefault(); 194 }, 195 196 /** Collection of toolbar items. */ 197 items : { 198 // Ordered lis of items in the toolbar. Can't expect `for (var n in items)` to be consistent. 199 list: ['expandSource', 'help'], 200 201 expandSource: { 202 getHtml: function(highlighter) 203 { 204 if (highlighter.getParam('collapse') != true) 205 return ''; 206 207 var title = highlighter.getParam('title'); 208 return sh.toolbar.getButtonHtml(highlighter, 'expandSource', title ? title : sh.config.strings.expandSource); 209 }, 210 211 execute: function(highlighter) 212 { 213 var div = getHighlighterDivById(highlighter.id); 214 removeClass(div, 'collapsed'); 215 } 216 }, 217 218 /** Command to display the about dialog window. */ 219 help: { 220 execute: function(highlighter) 221 { 222 var wnd = popup('', '_blank', 500, 250, 'scrollbars=0'), 223 doc = wnd.document 224 ; 225 226 doc.write(sh.config.strings.aboutDialog); 227 doc.close(); 228 wnd.focus(); 229 } 230 } 231 } 232 }, 233 234 /** 235 * Finds all elements on the page which should be processes by SyntaxHighlighter. 236 * 237 * @param {Object} globalParams Optional parameters which override element's 238 * parameters. Only used if element is specified. 239 * 240 * @param {Object} element Optional element to highlight. If none is 241 * provided, all elements in the current document 242 * are returned which qualify. 243 * 244 * @return {Array} Returns list of <code>{ target: DOMElement, params: Object }</code> objects. 245 */ 246 findElements: function(globalParams, element) 247 { 248 var elements = element ? [element] : toArray(document.getElementsByTagName(sh.config.tagName)), 249 conf = sh.config, 250 result = [] 251 ; 252 253 // support for <SCRIPT TYPE="syntaxhighlighter" /> feature 254 if (conf.useScriptTags) 255 elements = elements.concat(getSyntaxHighlighterScriptTags()); 256 257 if (elements.length === 0) 258 return result; 259 260 for (var i = 0; i < elements.length; i++) 261 { 262 var item = { 263 target: elements[i], 264 // local params take precedence over globals 265 params: merge(globalParams, parseParams(elements[i].className)) 266 }; 267 268 if (item.params['brush'] == null) 269 continue; 270 271 result.push(item); 272 } 273 274 return result; 275 }, 276 277 /** 278 * Shorthand to highlight all elements on the page that are marked as 279 * SyntaxHighlighter source code. 280 * 281 * @param {Object} globalParams Optional parameters which override element's 282 * parameters. Only used if element is specified. 283 * 284 * @param {Object} element Optional element to highlight. If none is 285 * provided, all elements in the current document 286 * are highlighted. 287 */ 288 highlight: function(globalParams, element) 289 { 290 var elements = this.findElements(globalParams, element), 291 propertyName = 'innerHTML', 292 highlighter = null, 293 conf = sh.config 294 ; 295 296 if (elements.length === 0) 297 return; 298 299 for (var i = 0; i < elements.length; i++) 300 { 301 var element = elements[i], 302 target = element.target, 303 params = element.params, 304 brushName = params.brush, 305 code 306 ; 307 308 if (brushName == null) 309 continue; 310 311 // Instantiate a brush 312 if (params['html-script'] == 'true' || sh.defaults['html-script'] == true) 313 { 314 highlighter = new sh.HtmlScript(brushName); 315 brushName = 'htmlscript'; 316 } 317 else 318 {
319 var brush = findBrush(brushName); 320 321 if (brush) 322 highlighter = new brush(); 323 else 324 continue; 325 } 326 327 code = target[propertyName]; 328 329 // remove CDATA from <SCRIPT/> tags if it's present 330 if (conf.useScriptTags) 331 code = stripCData(code); 332 333 // Inject title if the attribute is present 334 if ((target.title || '') != '') 335 params.title = target.title; 336 337 params['brush'] = brushName; 338 highlighter.init(params); 339 element = highlighter.getDiv(code); 340 341 // carry over ID 342 if ((target.id || '') != '') 343 element.id = target.id; 344 345 target.parentNode.replaceChild(element, target); 346 } 347 }, 348 349 /** 350 * Main entry point for the SyntaxHighlighter. 351 * @param {Object} params Optional params to apply to all highlighted elements. 352 */ 353 all: function(params) 354 { 355 attachEvent( 356 window, 357 'load', 358 function() { sh.highlight(params); } 359 ); 360 } 361}; // end of sh 362 363/** 364 * Checks if target DOM elements has specified CSS class. 365 * @param {DOMElement} target Target DOM element to check. 366 * @param {String} className Name of the CSS class to check for. 367 * @return {Boolean} Returns true if class name is present, false otherwise. 368 */ 369function hasClass(target, className) 370{ 371 return target.className.indexOf(className) != -1; 372}; 373 374/** 375 * Adds CSS class name to the target DOM element. 376 * @param {DOMElement} target Target DOM element. 377 * @param {String} className New CSS class to add. 378 */ 379function addClass(target, className) 380{ 381 if (!hasClass(target, className)) 382 target.className += ' ' + className; 383}; 384 385/** 386 * Removes CSS class name from the target DOM element. 387 * @param {DOMElement} target Target DOM element. 388 * @param {String} className CSS class to remove. 389 */ 390function removeClass(target, className) 391{ 392 target.className = target.className.replace(className, ''); 393}; 394 395/** 396 * Converts the source to array object. Mostly used for function arguments and 397 * lists returned by getElementsByTagName() which aren't Array objects. 398 * @param {List} source Source list. 399 * @return {Array} Returns array. 400 */ 401function toArray(source) 402{ 403 var result = []; 404 405 for (var i = 0; i < source.length; i++) 406 result.push(source[i]); 407 408 return result; 409}; 410 411/** 412 * Splits block of text into lines. 413 * @param {String} block Block of text. 414 * @return {Array} Returns array of lines. 415 */ 416function splitLines(block) 417{ 418 return block.split(/\r?\n/); 419} 420 421/** 422 * Generates HTML ID for the highlighter. 423 * @param {String} highlighterId Highlighter ID. 424 * @return {String} Returns HTML ID. 425 */ 426function getHighlighterId(id) 427{ 428 var prefix = 'highlighter_'; 429 return id.indexOf(prefix) == 0 ? id : prefix + id; 430}; 431 432/** 433 * Finds Highlighter instance by ID. 434 * @param {String} highlighterId Highlighter ID. 435 * @return {Highlighter} Returns instance of the highlighter. 436 */ 437function getHighlighterById(id) 438{ 439 return sh.vars.highlighters[getHighlighterId(id)]; 440}; 441 442/** 443 * Finds highlighter's DIV container. 444 * @param {String} highlighterId Highlighter ID. 445 * @return {Element} Returns highlighter's DIV element. 446 */ 447function getHighlighterDivById(id) 448{ 449 return document.getElementById(getHighlighterId(id)); 450}; 451 452/** 453 * Stores highlighter so that getHighlighterById() can do its thing. Each 454 * highlighter must call this method to preserve itself. 455 * @param {Highilghter} highlighter Highlighter instance. 456 */ 457function storeHighlighter(highlighter) 458{ 459 sh.vars.highlighters[getHighlighterId(highlighter.id)] = highlighter; 460}; 461 462/** 463 * Looks for a child or parent node which has specified classname. 464 * Equivalent to jQuery's $(container).find(".className") 465 * @param {Element} target Target element. 466 * @param {String} search Class name or node name to look for. 467 * @param {Boolean} reverse If set to true, will go up the node tree instead of down. 468 * @return {Element} Returns found child or parent element on null. 469 */ 470function findElement(target, search, reverse /* optional */) 471{ 472 if (target == null) 473 return null; 474 475 var nodes = reverse != true ? target.childNodes : [ target.parentNode ], 476 propertyToFind = { '#' : 'id', '.' : 'className' }[search.substr(0, 1)] || 'nodeName', 477 expectedValue, 478 found 479 ; 480 481 expectedValue = propertyToFind != 'nodeName' 482 ? search.substr(1) 483 : search.toUpperCase() 484 ; 485 486 // main return of the found node 487 if ((target[propertyToFind] || '').indexOf(expectedValue) != -1) 488 return target; 489 490 for (var i = 0; nodes && i < nodes.length && found == null; i++) 491 found = findElement(nodes[i], search, reverse); 492 493 return found; 494}; 495 496/** 497 * Looks for a parent node which has specified classname. 498 * This is an alias to <code>findElement(container, className, true)</code>. 499 * @param {Element} target Target element. 500 * @param {String} className Class name to look for. 501 * @return {Element} Returns found parent element on null. 502 */ 503function findParentElement(target, className) 504{ 505 return findElement(target, className, true); 506}; 507 508/** 509 * Finds an index of element in the array. 510 * @ignore 511 * @param {Object} searchElement 512 * @param {Number} fromIndex 513 * @return {Number} Returns index of element if found; -1 otherwise. 514 */ 515function indexOf(array, searchElement, fromIndex) 516{ 517 fromIndex = Math.max(fromIndex || 0, 0); 518 519 for (var i = fromIndex; i < array.length; i++) 520 if(array[i] == searchElement) 521 return i; 522 523 return -1; 524}; 525 526/** 527 * Generates a unique element ID. 528 */ 529function guid(prefix) 530{ 531 return (prefix || '') + Math.round(Math.random() * 1000000).toString(); 532}; 533 534/** 535 * Merges two objects. Values from obj2 override values in obj1. 536 * Function is NOT recursive and works only for one dimensional objects. 537 * @param {Object} obj1 First object. 538 * @param {Object} obj2 Second object. 539 * @return {Object} Returns combination of both objects. 540 */ 541function merge(obj1, obj2) 542{ 543 var result = {}, name; 544 545 for (name in obj1) 546 result[name] = obj1[name]; 547 548 for (name in obj2) 549 result[name] = obj2[name]; 550 551 return result; 552}; 553 554/** 555 * Attempts to convert string to boolean. 556 * @param {String} value Input string. 557 * @return {Boolean} Returns true if input was "true", false if input was "false" and value otherwise. 558 */ 559function toBoolean(value) 560{ 561 var result = { "true" : true, "false" : false }[value]; 562 return result == null ? value : result; 563}; 564 565/** 566 * Opens up a centered popup window. 567 * @param {String} url URL to open in the window. 568 * @param {String} name Popup name. 569 * @param {int} width Popup width. 570 * @param {int} height Popup height. 571 * @param {String} options window.open() options. 572 * @return {Window} Returns window instance. 573 */ 574function popup(url, name, width, height, options) 575{ 576 var x = (screen.width - width) / 2, 577 y = (screen.height - height) / 2 578 ; 579 580 options += ', left=' + x +
581 ', top=' + y + 582 ', width=' + width + 583 ', height=' + height 584 ; 585 options = options.replace(/^,/, ''); 586 587 var win = window.open(url, name, options); 588 win.focus(); 589 return win; 590}; 591 592/** 593 * Adds event handler to the target object. 594 * @param {Object} obj Target object. 595 * @param {String} type Name of the event. 596 * @param {Function} func Handling function. 597 */ 598function attachEvent(obj, type, func, scope) 599{ 600 function handler(e) 601 { 602 e = e || window.event; 603 604 if (!e.target) 605 { 606 e.target = e.srcElement; 607 e.preventDefault = function() 608 { 609 this.returnValue = false; 610 }; 611 } 612 613 func.call(scope || window, e); 614 }; 615 616 if (obj.attachEvent) 617 { 618 obj.attachEvent('on' + type, handler); 619 } 620 else 621 { 622 obj.addEventListener(type, handler, false); 623 } 624}; 625 626/** 627 * Displays an alert. 628 * @param {String} str String to display. 629 */ 630function alert(str) 631{ 632 window.alert(sh.config.strings.alert + str); 633}; 634 635/** 636 * Finds a brush by its alias. 637 * 638 * @param {String} alias Brush alias. 639 * @param {Boolean} showAlert Suppresses the alert if false. 640 * @return {Brush} Returns bursh constructor if found, null otherwise. 641 */ 642function findBrush(alias, showAlert) 643{ 644 var brushes = sh.vars.discoveredBrushes, 645 result = null 646 ; 647 648 if (brushes == null) 649 { 650 brushes = {}; 651 652 // Find all brushes 653 for (var brush in sh.brushes) 654 { 655 var info = sh.brushes[brush], 656 aliases = info.aliases 657 ; 658 659 if (aliases == null) 660 continue; 661 662 // keep the brush name 663 info.brushName = brush.toLowerCase(); 664 665 for (var i = 0; i < aliases.length; i++) 666 brushes[aliases[i]] = brush; 667 } 668 669 sh.vars.discoveredBrushes = brushes; 670 } 671 672 result = sh.brushes[brushes[alias]]; 673 674 if (result == null && showAlert) 675 alert(sh.config.strings.noBrush + alias); 676 677 return result; 678}; 679 680/** 681 * Executes a callback on each line and replaces each line with result from the callback. 682 * @param {Object} str Input string. 683 * @param {Object} callback Callback function taking one string argument and returning a string. 684 */ 685function eachLine(str, callback) 686{ 687 var lines = splitLines(str); 688 689 for (var i = 0; i < lines.length; i++) 690 lines[i] = callback(lines[i], i); 691 692 // include \r to enable copy-paste on windows (ie8) without getting everything on one line 693 return lines.join('\r\n'); 694}; 695 696/** 697 * This is a special trim which only removes first and last empty lines 698 * and doesn't affect valid leading space on the first line. 699 * 700 * @param {String} str Input string 701 * @return {String} Returns string without empty first and last lines. 702 */ 703function trimFirstAndLastLines(str) 704{ 705 return str.replace(/^[ ]*[\n]+|[\n]*[ ]*$/g, ''); 706}; 707 708/** 709 * Parses key/value pairs into hash object. 710 * 711 * Understands the following formats: 712 * - name: word; 713 * - name: [word, word]; 714 * - name: "string"; 715 * - name: 'string'; 716 * 717 * For example: 718 * name1: value; name2: [value, value]; name3: 'value' 719 * 720 * @param {String} str Input string. 721 * @return {Object} Returns deserialized object. 722 */ 723function parseParams(str) 724{ 725 var match, 726 result = {}, 727 arrayRegex = new XRegExp("^\\[(?<values>(.*?))\\]$"), 728 regex = new XRegExp( 729 "(?<name>[\\w-]+)" + 730 "\\s*:\\s*" + 731 "(?<value>" + 732 "[\\w-%#]+|" + // word 733 "\\[.*?\\]|" + // [] array 734 '".*?"|' + // "" string 735 "'.*?'" + // '' string 736 ")\\s*;?", 737 "g" 738 ) 739 ; 740 741 while ((match = regex.exec(str)) != null) 742 { 743 var value = match.value 744 .replace(/^['"]|['"]$/g, '') // strip quotes from end of strings 745 ; 746 747 // try to parse array value 748 if (value != null && arrayRegex.test(value)) 749 { 750 var m = arrayRegex.exec(value); 751 value = m.values.length > 0 ? m.values.split(/\s*,\s*/) : []; 752 } 753 754 result[match.name] = value; 755 } 756 757 return result; 758}; 759 760/** 761 * Wraps each line of the string into <code/> tag with given style applied to it. 762 * 763 * @param {String} str Input string. 764 * @param {String} css Style name to apply to the string. 765 * @return {String} Returns input string with each line surrounded by <span/> tag. 766 */ 767function wrapLinesWithCode(str, css) 768{ 769 if (str == null || str.length == 0 || str == '\n') 770 return str; 771 772 str = str.replace(/</g, '<'); 773 774 // Replace two or more sequential spaces with leaving last space untouched. 775 str = str.replace(/ {2,}/g, function(m) 776 { 777 var spaces = ''; 778 779 for (var i = 0; i < m.length - 1; i++) 780 spaces += sh.config.space; 781 782 return spaces + ' '; 783 }); 784 785 // Split each line and apply <span class="...">...</span> to them so that
786 // leading spaces aren't included. 787 if (css != null) 788 str = eachLine(str, function(line) 789 { 790 if (line.length == 0) 791 return ''; 792 793 var spaces = ''; 794 795 line = line.replace(/^( | )+/, function(s) 796 { 797 spaces = s; 798 return ''; 799 }); 800 801 if (line.length == 0) 802 return spaces; 803 804 return spaces + '<code class="' + css + '">' + line + '</code>'; 805 }); 806 807 return str; 808}; 809 810/** 811 * Pads number with zeros until it's length is the same as given length. 812 * 813 * @param {Number} number Number to pad. 814 * @param {Number} length Max string length with. 815 * @return {String} Returns a string padded with proper amount of '0'. 816 */ 817function padNumber(number, length) 818{ 819 var result = number.toString(); 820 821 while (result.length < length) 822 result = '0' + result; 823 824 return result; 825}; 826 827/** 828 * Replaces tabs with spaces. 829 * 830 * @param {String} code Source code. 831 * @param {Number} tabSize Size of the tab. 832 * @return {String} Returns code with all tabs replaces by spaces. 833 */ 834function processTabs(code, tabSize) 835{ 836 var tab = ''; 837 838 for (var i = 0; i < tabSize; i++) 839 tab += ' '; 840 841 return code.replace(/\t/g, tab); 842}; 843 844/** 845 * Replaces tabs with smart spaces. 846 * 847 * @param {String} code Code to fix the tabs in. 848 * @param {Number} tabSize Number of spaces in a column. 849 * @return {String} Returns code with all tabs replaces with roper amount of spaces. 850 */ 851function processSmartTabs(code, tabSize) 852{ 853 var lines = splitLines(code), 854 tab = '\t', 855 spaces = '' 856 ; 857 858 // Create a string with 1000 spaces to copy spaces from... 859 // It's assumed that there would be no indentation longer than that. 860 for (var i = 0; i < 50; i++) 861 spaces += ' '; // 20 spaces * 50 862 863 // This function inserts specified amount of spaces in the string 864 // where a tab is while removing that given tab. 865 function insertSpaces(line, pos, count) 866 { 867 return line.substr(0, pos) 868 + spaces.substr(0, count) 869 + line.substr(pos + 1, line.length) // pos + 1 will get rid of the tab 870 ; 871 }; 872 873 // Go through all the lines and do the 'smart tabs' magic. 874 code = eachLine(code, function(line) 875 { 876 if (line.indexOf(tab) == -1) 877 return line; 878 879 var pos = 0; 880 881 while ((pos = line.indexOf(tab)) != -1) 882 { 883 // This is pretty much all there is to the 'smart tabs' logic. 884 // Based on the position within the line and size of a tab, 885 // calculate the amount of spaces we need to insert. 886 var spaces = tabSize - pos % tabSize; 887 line = insertSpaces(line, pos, spaces); 888 } 889 890 return line; 891 }); 892 893 return code; 894}; 895 896/** 897 * Performs various string fixes based on configuration. 898 */ 899function fixInputString(str) 900{ 901 var br = /<br\s*\/?>|<br\s*\/?>/gi; 902 903 if (sh.config.bloggerMode == true) 904 str = str.replace(br, '\n'); 905 906 if (sh.config.stripBrs == true) 907 str = str.replace(br, ''); 908 909 return str; 910}; 911 912/** 913 * Removes all white space at the begining and end of a string. 914 * 915 * @param {String} str String to trim. 916 * @return {String} Returns string without leading and following white space characters. 917 */ 918function trim(str) 919{ 920 return str.replace(/^\s+|\s+$/g, ''); 921}; 922 923/** 924 * Unindents a block of text by the lowest common indent amount. 925 * @param {String} str Text to unindent. 926 * @return {String} Returns unindented text block. 927 */ 928function unindent(str) 929{ 930 var lines = splitLines(fixInputString(str)), 931 indents = new Array(), 932 regex = /^\s*/, 933 min = 1000 934 ; 935
936 // go through every line and check for common number of indents 937 for (var i = 0; i < lines.length && min > 0; i++) 938 { 939 var line = lines[i]; 940 941 if (trim(line).length == 0) 942 continue; 943 944 var matches = regex.exec(line); 945 946 // In the event that just one line doesn't have leading white space 947 // we can't unindent anything, so bail completely. 948 if (matches == null) 949 return str; 950 951 min = Math.min(matches[0].length, min); 952 } 953 954 // trim minimum common number of white space from the begining of every line 955 if (min > 0) 956 for (var i = 0; i < lines.length; i++) 957 lines[i] = lines[i].substr(min); 958 959 return lines.join('\n'); 960}; 961 962/** 963 * Callback method for Array.sort() which sorts matches by 964 * index position and then by length. 965 * 966 * @param {Match} m1 Left object. 967 * @param {Match} m2 Right object. 968 * @return {Number} Returns -1, 0 or -1 as a comparison result. 969 */ 970function matchesSortCallback(m1, m2) 971{ 972 // sort matches by index first 973 if(m1.index < m2.index) 974 return -1; 975 else if(m1.index > m2.index) 976 return 1; 977 else 978 { 979 // if index is the same, sort by length 980 if(m1.length < m2.length) 981 return -1; 982 else if(m1.length > m2.length) 983 return 1; 984 } 985 986 return 0; 987}; 988 989/** 990 * Executes given regular expression on provided code and returns all 991 * matches that are found. 992 * 993 * @param {String} code Code to execute regular expression on. 994 * @param {Object} regex Regular expression item info from <code>regexList</code> collection. 995 * @return {Array} Returns a list of Match objects. 996 */ 997function getMatches(code, regexInfo) 998{ 999 function defaultAdd(match, regexInfo) 1000 { 1001 return match[0]; 1002 }; 1003 1004 var index = 0, 1005 match = null, 1006 matches = [], 1007 func = regexInfo.func ? regexInfo.func : defaultAdd 1008 ; 1009 1010 while((match = regexInfo.regex.exec(code)) != null) 1011 { 1012 var resultMatch = func(match, regexInfo); 1013 1014 if (typeof(resultMatch) == 'string') 1015 resultMatch = [new sh.Match(resultMatch, match.index, regexInfo.css)]; 1016 1017 matches = matches.concat(resultMatch); 1018 } 1019 1020 return matches; 1021}; 1022 1023/** 1024 * Turns all URLs in the code into <a/> tags. 1025 * @param {String} code Input code. 1026 * @return {String} Returns code with </a> tags. 1027 */ 1028function processUrls(code) 1029{ 1030 var gt = /(.*)((>|<).*)/; 1031 1032 return code.replace(sh.regexLib.url, function(m) 1033 { 1034 var suffix = '', 1035 match = null 1036 ; 1037 1038 // We include < and > in the URL for the common cases like <http://google.com> 1039 // The problem is that they get transformed into <http://google.com> 1040 // Where as > easily looks like part of the URL string. 1041 1042 if (match = gt.exec(m)) 1043 { 1044 m = match[1]; 1045 suffix = match[2]; 1046 } 1047 1048 return '<a href="' + m + '">' + m + '</a>' + suffix; 1049 }); 1050}; 1051 1052/** 1053 * Finds all <SCRIPT TYPE="syntaxhighlighter" /> elementss. 1054 * @return {Array} Returns array of all found SyntaxHighlighter tags. 1055 */ 1056function getSyntaxHighlighterScriptTags() 1057{ 1058 var tags = document.getElementsByTagName('script'), 1059 result = [] 1060 ; 1061 1062 for (var i = 0; i < tags.length; i++) 1063 if (tags[i].type == 'syntaxhighlighter') 1064 result.push(tags[i]); 1065 1066 return result; 1067}; 1068 1069/** 1070 * Strips <![CDATA[]]> from <SCRIPT /> content because it should be used 1071 * there in most cases for XHTML compliance. 1072 * @param {String} original Input code. 1073 * @return {String} Returns code without leading <![CDATA[]]> tags. 1074 */ 1075function stripCData(original) 1076{ 1077 var left = '<![CDATA[', 1078 right = ']]>', 1079 // for some reason IE inserts some leading blanks here 1080 copy = trim(original), 1081 changed = false, 1082 leftLength = left.length, 1083 rightLength = right.length 1084 ; 1085 1086 if (copy.indexOf(left) == 0) 1087 { 1088 copy = copy.substring(leftLength); 1089 changed = true; 1090 } 1091 1092 var copyLength = copy.length; 1093 1094 if (copy.indexOf(right) == copyLength - rightLength) 1095 { 1096 copy = copy.substring(0, copyLength - rightLength); 1097 changed = true; 1098 } 1099 1100 return changed ? copy : original; 1101}; 1102 1103 1104/** 1105 * Quick code mouse double click handler. 1106 */ 1107function quickCodeHandler(e) 1108{ 1109 var target = e.target, 1110 highlighterDiv = findParentElement(target, '.syntaxhighlighter'), 1111 container = findParentElement(target, '.container'), 1112 textarea = document.createElement('textarea'), 1113 highlighter 1114 ; 1115 1116 if (!container || !highlighterDiv || findElement(container, 'textarea')) 1117 return; 1118 1119 highlighter = getHighlighterById(highlighterDiv.id); 1120 1121 // add source class name 1122 addClass(highlighterDiv, 'source'); 1123 1124 // Have to go over each line and grab it's text, can't just do it on the 1125 // container because Firefox loses all \n where as Webkit doesn't. 1126 var lines = container.childNodes, 1127 code = [] 1128 ; 1129 1130 for (var i = 0; i < lines.length; i++) 1131 code.push(lines[i].innerText || lines[i].textContent); 1132 1133 // using \r instead of \r or \r\n makes this work equally well on IE, FF and Webkit 1134 code = code.join('\r'); 1135 1136 // For Webkit browsers, replace nbsp with a breaking space 1137 code = code.replace(/\u00a0/g, " "); 1138 1139 // inject <textarea/> tag 1140 textarea.appendChild(document.createTextNode(code)); 1141 container.appendChild(textarea); 1142 1143 // preselect all text 1144 textarea.focus(); 1145 textarea.select(); 1146 1147 // set up handler for lost focus 1148 attachEvent(textarea, 'blur', function(e) 1149 { 1150 textarea.parentNode.removeChild(textarea); 1151 removeClass(highlighterDiv, 'source'); 1152 }); 1153}; 1154 1155/** 1156 * Match object. 1157 */ 1158sh.Match = function(value, index, css) 1159{ 1160 this.value = value; 1161 this.index = index; 1162 this.length = value.length; 1163 this.css = css; 1164 this.brushName = null; 1165}; 1166 1167sh.Match.prototype.toString = function() 1168{ 1169 return this.value; 1170}; 1171 1172/** 1173 * Simulates HTML code with a scripting language embedded. 1174 * 1175 * @param {String} scriptBrushName Brush name of the scripting language. 1176 */ 1177sh.HtmlScript = function(scriptBrushName) 1178{ 1179 var brushClass = findBrush(scriptBrushName), 1180 scriptBrush, 1181 xmlBrush = new sh.brushes.Xml(), 1182 bracketsRegex = null, 1183 ref = this, 1184 methodsToExpose = 'getDiv getHtml init'.split(' ') 1185 ; 1186 1187 if (brushClass == null) 1188 return; 1189 1190 scriptBrush = new brushClass(); 1191 1192 for(var i = 0; i < methodsToExpose.length; i++) 1193 // make a closure so we don't lose the name after i changes 1194 (function() { 1195 var name = methodsToExpose[i]; 1196 1197 ref[name] = function() 1198 { 1199 return xmlBrush[name].apply(xmlBrush, arguments); 1200 }; 1201 })(); 1202 1203 if (scriptBrush.htmlScript == null) 1204 { 1205 alert(sh.config.strings.brushNotHtmlScript + scriptBrushName); 1206 return; 1207 } 1208 1209 xmlBrush.regexList.push( 1210 { regex: scriptBrush.htmlScript.code, func: process } 1211 ); 1212 1213 function offsetMatches(matches, offset) 1214 { 1215 for (var j = 0; j < matches.length; j++) 1216 matches[j].index += offset; 1217 } 1218 1219 function process(match, info) 1220 {
1221 var code = match.code, 1222 matches = [], 1223 regexList = scriptBrush.regexList, 1224 offset = match.index + match.left.length, 1225 htmlScript = scriptBrush.htmlScript, 1226 result 1227 ; 1228 1229 // add all matches from the code 1230 for (var i = 0; i < regexList.length; i++) 1231 { 1232 result = getMatches(code, regexList[i]); 1233 offsetMatches(result, offset); 1234 matches = matches.concat(result); 1235 } 1236 1237 // add left script bracket 1238 if (htmlScript.left != null && match.left != null) 1239 { 1240 result = getMatches(match.left, htmlScript.left); 1241 offsetMatches(result, match.index); 1242 matches = matches.concat(result); 1243 } 1244 1245 // add right script bracket 1246 if (htmlScript.right != null && match.right != null) 1247 { 1248 result = getMatches(match.right, htmlScript.right); 1249 offsetMatches(result, match.index + match[0].lastIndexOf(match.right)); 1250 matches = matches.concat(result); 1251 } 1252 1253 for (var j = 0; j < matches.length; j++) 1254 matches[j].brushName = brushClass.brushName; 1255 1256 return matches; 1257 } 1258}; 1259 1260/** 1261 * Main Highlither class. 1262 * @constructor 1263 */ 1264sh.Highlighter = function() 1265{ 1266 // not putting any code in here because of the prototype inheritance 1267}; 1268 1269sh.Highlighter.prototype = { 1270 /** 1271 * Returns value of the parameter passed to the highlighter. 1272 * @param {String} name Name of the parameter. 1273 * @param {Object} defaultValue Default value. 1274 * @return {Object} Returns found value or default value otherwise. 1275 */ 1276 getParam: function(name, defaultValue) 1277 { 1278 var result = this.params[name]; 1279 return toBoolean(result == null ? defaultValue : result); 1280 }, 1281 1282 /** 1283 * Shortcut to document.createElement(). 1284 * @param {String} name Name of the element to create (DIV, A, etc). 1285 * @return {HTMLElement} Returns new HTML element. 1286 */ 1287 create: function(name) 1288 { 1289 return document.createElement(name); 1290 }, 1291 1292 /** 1293 * Applies all regular expression to the code and stores all found 1294 * matches in the `this.matches` array. 1295 * @param {Array} regexList List of regular expressions. 1296 * @param {String} code Source code. 1297 * @return {Array} Returns list of matches. 1298 */ 1299 findMatches: function(regexList, code) 1300 { 1301 var result = []; 1302 1303 if (regexList != null) 1304 for (var i = 0; i < regexList.length; i++) 1305 // BUG: length returns len+1 for array if methods added to prototype chain ([email protected]) 1306 if (typeof (regexList[i]) == "object") 1307 result = result.concat(getMatches(code, regexList[i])); 1308 1309 // sort and remove nested the matches 1310 return this.removeNestedMatches(result.sort(matchesSortCallback)); 1311 }, 1312 1313 /** 1314 * Checks to see if any of the matches are inside of other matches. 1315 * This process would get rid of highligted strings inside comments, 1316 * keywords inside strings and so on. 1317 */ 1318 removeNestedMatches: function(matches) 1319 { 1320 // Optimized by Jose Prado (http://joseprado.com) 1321 for (var i = 0; i < matches.length; i++) 1322 { 1323 if (matches[i] === null) 1324 continue; 1325 1326 var itemI = matches[i], 1327 itemIEndPos = itemI.index + itemI.length 1328 ; 1329 1330 for (var j = i + 1; j < matches.length && matches[i] !== null; j++) 1331 { 1332 var itemJ = matches[j]; 1333 1334 if (itemJ === null) 1335 continue; 1336 else if (itemJ.index > itemIEndPos) 1337 break; 1338 else if (itemJ.index == itemI.index && itemJ.length > itemI.length) 1339 matches[i] = null; 1340 else if (itemJ.index >= itemI.index && itemJ.index < itemIEndPos) 1341 matches[j] = null; 1342 } 1343 } 1344 1345 return matches; 1346 }, 1347 1348 /** 1349 * Creates an array containing integer line numbers starting from the 'first-line' param. 1350 * @return {Array} Returns array of integers. 1351 */ 1352 figureOutLineNumbers: function(code) 1353 { 1354 var lines = [], 1355 firstLine = parseInt(this.getParam('first-line')) 1356 ; 1357 1358 eachLine(code, function(line, index) 1359 { 1360 lines.push(index + firstLine); 1361 }); 1362 1363 return lines; 1364 }, 1365 1366 /** 1367 * Determines if specified line number is in the highlighted list. 1368 */ 1369 isLineHighlighted: function(lineNumber) 1370 { 1371 var list = this.getParam('highlight', []); 1372 1373 if (typeof(list) != 'object' && list.push == null) 1374 list = [ list ]; 1375 1376 return indexOf(list, lineNumber.toString()) != -1; 1377 }, 1378 1379 /** 1380 * Generates HTML markup for a single line of code while determining alternating line style. 1381 * @param {Integer} lineNumber Line number. 1382 * @param {String} code Line HTML markup. 1383 * @return {String} Returns HTML markup. 1384 */
1385 getLineHtml: function(lineIndex, lineNumber, code) 1386 { 1387 var classes = [ 1388 'line', 1389 'number' + lineNumber, 1390 'index' + lineIndex, 1391 'alt' + (lineNumber % 2 == 0 ? 1 : 2).toString() 1392 ]; 1393 1394 if (this.isLineHighlighted(lineNumber)) 1395 classes.push('highlighted'); 1396 1397 if (lineNumber == 0) 1398 classes.push('break'); 1399 1400 return '<div class="' + classes.join(' ') + '">' + code + '</div>'; 1401 }, 1402 1403 /** 1404 * Generates HTML markup for line number column. 1405 * @param {String} code Complete code HTML markup. 1406 * @param {Array} lineNumbers Calculated line numbers. 1407 * @return {String} Returns HTML markup. 1408 */ 1409 getLineNumbersHtml: function(code, lineNumbers) 1410 { 1411 var html = '', 1412 count = splitLines(code).length, 1413 firstLine = parseInt(this.getParam('first-line')), 1414 pad = this.getParam('pad-line-numbers') 1415 ; 1416 1417 if (pad == true) 1418 pad = (firstLine + count - 1).toString().length; 1419 else if (isNaN(pad) == true) 1420 pad = 0; 1421 1422 for (var i = 0; i < count; i++) 1423 { 1424 var lineNumber = lineNumbers ? lineNumbers[i] : firstLine + i, 1425 code = lineNumber == 0 ? sh.config.space : padNumber(lineNumber, pad) 1426 ; 1427 1428 html += this.getLineHtml(i, lineNumber, code); 1429 } 1430 1431 return html; 1432 }, 1433 1434 /** 1435 * Splits block of text into individual DIV lines. 1436 * @param {String} code Code to highlight. 1437 * @param {Array} lineNumbers Calculated line numbers. 1438 * @return {String} Returns highlighted code in HTML form. 1439 */ 1440 getCodeLinesHtml: function(html, lineNumbers) 1441 { 1442 html = trim(html); 1443 1444 var lines = splitLines(html), 1445 padLength = this.getParam('pad-line-numbers'), 1446 firstLine = parseInt(this.getParam('first-line')), 1447 html = '', 1448 brushName = this.getParam('brush') 1449 ; 1450 1451 for (var i = 0; i < lines.length; i++) 1452 { 1453 var line = lines[i], 1454 indent = /^( |\s)+/.exec(line), 1455 spaces = null, 1456 lineNumber = lineNumbers ? lineNumbers[i] : firstLine + i; 1457 ; 1458 1459 if (indent != null) 1460 { 1461 spaces = indent[0].toString(); 1462 line = line.substr(spaces.length);
1463 spaces = spaces.replace(' ', sh.config.space); 1464 } 1465 1466 line = trim(line); 1467 1468 if (line.length == 0) 1469 line = sh.config.space; 1470 1471 html += this.getLineHtml( 1472 i, 1473 lineNumber, 1474 (spaces != null ? '<code class="' + brushName + ' spaces">' + spaces + '</code>' : '') + line 1475 ); 1476 } 1477 1478 return html; 1479 }, 1480 1481 /** 1482 * Returns HTML for the table title or empty string if title is null. 1483 */ 1484 getTitleHtml: function(title) 1485 { 1486 return title ? '<caption>' + title + '</caption>' : ''; 1487 }, 1488 1489 /** 1490 * Finds all matches in the source code. 1491 * @param {String} code Source code to process matches in. 1492 * @param {Array} matches Discovered regex matches. 1493 * @return {String} Returns formatted HTML with processed mathes. 1494 */ 1495 getMatchesHtml: function(code, matches) 1496 { 1497 var pos = 0, 1498 result = '', 1499 brushName = this.getParam('brush', '') 1500 ; 1501 1502 function getBrushNameCss(match) 1503 { 1504 var result = match ? (match.brushName || brushName) : brushName; 1505 return result ? result + ' ' : ''; 1506 }; 1507 1508 // Finally, go through the final list of matches and pull the all 1509 // together adding everything in between that isn't a match. 1510 for (var i = 0; i < matches.length; i++) 1511 { 1512 var match = matches[i], 1513 matchBrushName 1514 ; 1515 1516 if (match === null || match.length === 0) 1517 continue; 1518 1519 matchBrushName = getBrushNameCss(match); 1520 1521 result += wrapLinesWithCode(code.substr(pos, match.index - pos), matchBrushName + 'plain') 1522 + wrapLinesWithCode(match.value, matchBrushName + match.css) 1523 ; 1524 1525 pos = match.index + match.length + (match.offset || 0); 1526 } 1527 1528 // don't forget to add whatever's remaining in the string 1529 result += wrapLinesWithCode(code.substr(pos), getBrushNameCss() + 'plain'); 1530 1531 return result; 1532 }, 1533 1534 /** 1535 * Generates HTML markup for the whole syntax highlighter. 1536 * @param {String} code Source code. 1537 * @return {String} Returns HTML markup. 1538 */ 1539 getHtml: function(code) 1540 { 1541 var html = '', 1542 classes = [ 'syntaxhighlighter' ], 1543 tabSize, 1544 matches, 1545 lineNumbers 1546 ; 1547 1548 // process light mode 1549 if (this.getParam('light') == true) 1550 this.params.toolbar = this.params.gutter = false; 1551 1552 className = 'syntaxhighlighter'; 1553 1554 if (this.getParam('collapse') == true) 1555 classes.push('collapsed'); 1556 1557 if ((gutter = this.getParam('gutter')) == false) 1558 classes.push('nogutter'); 1559 1560 // add custom user style name 1561 classes.push(this.getParam('class-name')); 1562 1563 // add brush alias to the class name for custom CSS 1564 classes.push(this.getParam('brush')); 1565 1566 code = trimFirstAndLastLines(code) 1567 .replace(/\r/g, ' ') // IE lets these buggers through 1568 ; 1569 1570 tabSize = this.getParam('tab-size'); 1571 1572 // replace tabs with spaces 1573 code = this.getParam('smart-tabs') == true 1574 ? processSmartTabs(code, tabSize) 1575 : processTabs(code, tabSize) 1576 ; 1577 1578 // unindent code by the common indentation 1579 if (this.getParam('unindent')) 1580 code = unindent(code); 1581 1582 if (gutter) 1583 lineNumbers = this.figureOutLineNumbers(code); 1584 1585 // find matches in the code using brushes regex list 1586 matches = this.findMatches(this.regexList, code); 1587 // processes found matches into the html 1588 html = this.getMatchesHtml(code, matches); 1589 // finally, split all lines so that they wrap well 1590 html = this.getCodeLinesHtml(html, lineNumbers); 1591 1592 // finally, process the links 1593 if (this.getParam('auto-links')) 1594 html = processUrls(html); 1595 1596 if (typeof(navigator) != 'undefined' && navigator.userAgent && navigator.userAgent.match(/MSIE/)) 1597 classes.push('ie'); 1598 1599 html = 1600 '<div id="' + getHighlighterId(this.id) + '" class="' + classes.join(' ') + '">' 1601 + (this.getParam('toolbar') ? sh.toolbar.getHtml(this) : '') 1602 + '<table border="0" cellpadding="0" cellspacing="0">' 1603 + this.getTitleHtml(this.getParam('title')) 1604 + '<tbody>' 1605 + '<tr>' 1606 + (gutter ? '<td class="gutter">' + this.getLineNumbersHtml(code) + '</td>' : '') 1607 + '<td class="code">' 1608 + '<div class="container">' 1609 + html 1610 + '</div>' 1611 + '</td>' 1612 + '</tr>' 1613 + '</tbody>' 1614 + '</table>' 1615 + '</div>' 1616 ; 1617 1618 return html; 1619 }, 1620 1621 /** 1622 * Highlights the code and returns complete HTML. 1623 * @param {String} code Code to highlight. 1624 * @return {Element} Returns container DIV element with all markup. 1625 */ 1626 getDiv: function(code) 1627 { 1628 if (code === null) 1629 code = ''; 1630 1631 this.code = code; 1632 1633 var div = this.create('div'); 1634 1635 // create main HTML 1636 div.innerHTML = this.getHtml(code); 1637 1638 // set up click handlers 1639 if (this.getParam('toolbar')) 1640 attachEvent(findElement(div, '.toolbar'), 'click', sh.toolbar.handler); 1641 1642 if (this.getParam('quick-code')) 1643 attachEvent(findElement(div, '.code'), 'dblclick', quickCodeHandler); 1644 1645 return div; 1646 }, 1647 1648 /** 1649 * Initializes the highlighter/brush. 1650 * 1651 * Constructor isn't used for initialization so that nothing executes during necessary 1652 * `new SyntaxHighlighter.Highlighter()` call when setting up brush inheritence. 1653 * 1654 * @param {Hash} params Highlighter parameters. 1655 */ 1656 init: function(params) 1657 { 1658 this.id = guid(); 1659 1660 // register this instance in the highlighters list 1661 storeHighlighter(this); 1662 1663 // local params take precedence over defaults 1664 this.params = merge(sh.defaults, params || {}) 1665 1666 // process light mode 1667 if (this.getParam('light') == true) 1668 this.params.toolbar = this.params.gutter = false;
1669 }, 1670 1671 /** 1672 * Converts space separated list of keywords into a regular expression string. 1673 * @param {String} str Space separated keywords. 1674 * @return {String} Returns regular expression string. 1675 */ 1676 getKeywords: function(str) 1677 { 1678 str = str 1679 .replace(/^\s+|\s+$/g, '') 1680 .replace(/\s+/g, '|') 1681 ; 1682 1683 return '\\b(?:' + str + ')\\b'; 1684 }, 1685 1686 /** 1687 * Makes a brush compatible with the `html-script` functionality. 1688 * @param {Object} regexGroup Object containing `left` and `right` regular expressions. 1689 */ 1690 forHtmlScript: function(regexGroup) 1691 { 1692 var regex = { 'end' : regexGroup.right.source }; 1693 1694 if(regexGroup.eof) 1695 regex.end = "(?:(?:" + regex.end + ")|$)"; 1696 1697 this.htmlScript = { 1698 left : { regex: regexGroup.left, css: 'script' }, 1699 right : { regex: regexGroup.right, css: 'script' }, 1700 code : new XRegExp( 1701 "(?<left>" + regexGroup.left.source + ")" + 1702 "(?<code>.*?)" + 1703 "(?<right>" + regex.end + ")", 1704 "sgi" 1705 ) 1706 }; 1707 } 1708}; // end of Highlighter 1709 1710return sh; 1711}(); // end of anonymous function 1712 1713// CommonJS 1714typeof(exports) != 'undefined' ? exports.SyntaxHighlighter = SyntaxHighlighter : null;
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.