1/* 2Copyright (c) 2009 James Padolsey. All rights reserved. 3 4Redistribution and use in source and binary forms, with or without 5modification, are permitted provided that the following conditions 6are met: 7 8 1. Redistributions of source code must retain the above copyright 9 notice, this list of conditions and the following disclaimer. 10 11 2. Redistributions in binary form must reproduce the above copyright 12 notice, this list of conditions and the following disclaimer in the 13 documentation and/or other materials provided with the distribution. 14 15THIS SOFTWARE IS PROVIDED BY James Padolsey ``AS IS'' AND 16ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18ARE DISCLAIMED. IN NO EVENT SHALL James Padolsey OR CONTRIBUTORS BE LIABLE 19FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25SUCH DAMAGE. 26 27The views and conclusions contained in the software and documentation are 28those of the authors and should not be interpreted as representing official 29policies, either expressed or implied, of James Padolsey. 30 31 AUTHOR James Padolsey (http://james.padolsey.com) 32 VERSION 1.03.0 33 UPDATED 29-10-2011 34 CONTRIBUTORS 35 David Waller 36 Benjamin Drucker 37 38*/ 39 40var prettyPrint = (function(){ 41 42 /* These "util" functions are not part of the core 43 functionality but are all necessary - mostly DOM helpers */ 44 45 var util = { 46 47 el: function(type, attrs) { 48 49 /* Create new element */ 50 var el = document.createElement(type), attr; 51 52 /*Copy to single object */ 53 attrs = util.merge({}, attrs); 54 55 /* Add attributes to el */ 56 if (attrs && attrs.style) { 57 var styles = attrs.style; 58 util.applyCSS( el, attrs.style ); 59 delete attrs.style; 60 } 61 for (attr in attrs) { 62 if (attrs.hasOwnProperty(attr)) { 63 el[attr] = attrs[attr]; 64 } 65 } 66 67 return el; 68 69 }, 70 71 applyCSS: function(el, styles) { 72 /* Applies CSS to a single element */ 73 for (var prop in styles) { 74 if (styles.hasOwnProperty(prop)) { 75 try{ 76 /* Yes, IE6 SUCKS! */ 77 el.style[prop] = styles[prop]; 78 }catch(e){} 79 } 80 } 81 }, 82 83 txt: function(t) { 84 /* Create text node */ 85 return document.createTextNode(t); 86 }, 87 88 row: function(cells, type, cellType) { 89 90 /* Creates new <tr> */ 91 cellType = cellType || 'td'; 92 93 /* colSpan is calculated by length of null items in array */ 94 var colSpan = util.count(cells, null) + 1, 95 tr = util.el('tr'), td, 96 attrs = { 97 style: util.getStyles(cellType, type), 98 colSpan: colSpan, 99 onmouseover: function() { 100 var tds = this.parentNode.childNodes; 101 util.forEach(tds, function(cell){ 102 if (cell.nodeName.toLowerCase() !== 'td') { return; } 103 util.applyCSS(cell, util.getStyles('td_hover', type)); 104 }); 105 }, 106 onmouseout: function() { 107 var tds = this.parentNode.childNodes; 108 util.forEach(tds, function(cell){ 109 if (cell.nodeName.toLowerCase() !== 'td') { return; } 110 util.applyCSS(cell, util.getStyles('td', type)); 111 }); 112 } 113 }; 114 115 util.forEach(cells, function(cell){ 116 117 if (cell === null) { return; } 118 /* Default cell type is <td> */ 119 td = util.el(cellType, attrs); 120 121 if (cell.nodeType) { 122 /* IsDomElement */ 123 td.appendChild(cell); 124 } else { 125 /* IsString */ 126 td.innerHTML = util.shorten(cell.toString()); 127 } 128 129 tr.appendChild(td); 130 }); 131 132 return tr; 133 }, 134 135 hRow: function(cells, type){ 136 /* Return new <th> */ 137 return util.row(cells, type, 'th'); 138 }, 139 140 table: function(headings, type){ 141 142 headings = headings || []; 143 144 /* Creates new table: */ 145 var attrs = { 146 thead: { 147 style:util.getStyles('thead',type) 148 }, 149 tbody: { 150 style:util.getStyles('tbody',type) 151 }, 152 table: { 153 style:util.getStyles('table',type) 154 } 155 }, 156 tbl = util.el('table', attrs.table), 157 thead = util.el('thead', attrs.thead), 158 tbody = util.el('tbody', attrs.tbody); 159 160 if (headings.length) { 161 tbl.appendChild(thead); 162 thead.appendChild( util.hRow(headings, type) ); 163 } 164 tbl.appendChild(tbody); 165 166 return { 167 /* Facade for dealing with table/tbody 168 Actual table node is this.node: */ 169 node: tbl, 170 tbody: tbody, 171 thead: thead, 172 appendChild: function(node) { 173 this.tbody.appendChild(node); 174 }, 175 addRow: function(cells, _type, cellType){ 176 this.appendChild(util.row.call(util, cells, (_type || type), cellType)); 177 return this; 178 } 179 }; 180 }, 181 182 shorten: function(str) { 183 var max = 40; 184 str = str.replace(/^\s\s*|\s\s*$|\n/g,''); 185 return str.length > max ? (str.substring(0, max-1) + '...') : str; 186 }, 187 188 htmlentities: function(str) { 189 return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>'); 190 }, 191 192 merge: function(target, source) { 193 194 /* Merges two (or more) objects, 195 giving the last one precedence */ 196 197 if ( typeof target !== 'object' ) { 198 target = {}; 199 } 200 201 for (var property in source) { 202 203 if ( source.hasOwnProperty(property) ) { 204 205 var sourceProperty = source[ property ]; 206 207 if ( typeof sourceProperty === 'object' ) { 208 target[ property ] = util.merge( target[ property ], sourceProperty ); 209 continue; 210 } 211 212 target[ property ] = sourceProperty; 213 214 } 215 216 } 217 218 for (var a = 2, l = arguments.length; a < l; a++) { 219 util.merge(target, arguments[a]); 220 } 221 222 return target; 223 }, 224 225 count: function(arr, item) { 226 var count = 0; 227 for (var i = 0, l = arr.length; i< l; i++) { 228 if (arr[i] === item) { 229 count++; 230 } 231 } 232 return count; 233 }, 234 235 thead: function(tbl) { 236 return tbl.getElementsByTagName('thead')[0]; 237 }, 238 239 forEach: function(arr, max, fn) { 240 241 if (!fn) { 242 fn = max; 243 } 244 245 /* Helper: iteration */ 246 var len = arr.length, 247 index = -1; 248 249 while (++index < len) { 250 if(fn( arr[index], index, arr ) === false) { 251 break; 252 } 253 } 254 255 return true; 256 }, 257 258 type: function(v){ 259 try { 260 /* Returns type, e.g. "string", "number", "array" etc. 261 Note, this is only used for precise typing. */ 262 if (v === null) { return 'null'; } 263 if (v === undefined) { return 'undefined'; } 264 var oType = Object.prototype.toString.call(v).match(/\s(.+?)\]/)[1].toLowerCase(); 265 if (v.nodeType) { 266 if (v.nodeType === 1) { 267 return 'domelement'; 268 } 269 return 'domnode'; 270 } 271 if (/^(string|number|array|regexp|function|date|boolean)$/.test(oType)) { 272 return oType; 273 } 274 if (typeof v === 'object') { 275 return v.jquery && typeof v.jquery === 'string' ? 'jquery' : 'object'; 276 } 277 if (v === window || v === document) { 278 return 'object'; 279 } 280 return 'default'; 281 } catch(e) { 282 return 'default'; 283 } 284 }, 285 286 within: function(ref) { 287 /* Check existence of a val within an object 288 RETURNS KEY */ 289 return { 290 is: function(o) { 291 for (var i in ref) { 292 if (ref[i] === o) { 293 return i; 294 } 295 } 296 return ''; 297 } 298 }; 299 }, 300 301 common: { 302 circRef: function(obj, key, settings) { 303 return util.expander( 304 '[POINTS BACK TO <strong>' + (key) + '</strong>]', 305 'Click to show this item anyway', 306 function() { 307 this.parentNode.appendChild( prettyPrintThis(obj,{maxDepth:1}) ); 308 } 309 ); 310 }, 311 depthReached: function(obj, settings) { 312 return util.expander( 313 '[DEPTH REACHED]', 314 'Click to show this item anyway', 315 function() { 316 try { 317 this.parentNode.appendChild( prettyPrintThis(obj,{maxDepth:1}) ); 318 } catch(e) { 319 this.parentNode.appendChild( 320 util.table(['ERROR OCCURED DURING OBJECT RETRIEVAL'],'error').addRow([e.message]).node 321 ); 322 } 323 } 324 ); 325 } 326 }, 327
328 getStyles: function(el, type) { 329 type = prettyPrintThis.settings.styles[type] || {}; 330 return util.merge( 331 {}, prettyPrintThis.settings.styles['default'][el], type[el] 332 ); 333 }, 334 335 expander: function(text, title, clickFn) { 336 return util.el('a', { 337 innerHTML: util.shorten(text) + ' <b style="visibility:hidden;">[+]</b>', 338 title: title, 339 onmouseover: function() { 340 this.getElementsByTagName('b')[0].style.visibility = 'visible'; 341 }, 342 onmouseout: function() { 343 this.getElementsByTagName('b')[0].style.visibility = 'hidden'; 344 }, 345 onclick: function() { 346 this.style.display = 'none'; 347 clickFn.call(this); 348 return false; 349 }, 350 style: { 351 cursor: 'pointer' 352 } 353 }); 354 }, 355 356 stringify: function(obj) { 357 358 /* Bit of an ugly duckling! 359 - This fn returns an ATTEMPT at converting an object/array/anyType 360 into a string, kinda like a JSON-deParser 361 - This is used for when |settings.expanded === false| */ 362 363 var type = util.type(obj), 364 str, first = true; 365 if ( type === 'array' ) { 366 str = '['; 367 util.forEach(obj, function(item,i){ 368 str += (i===0?'':', ') + util.stringify(item); 369 }); 370 return str + ']'; 371 } 372 if (typeof obj === 'object') { 373 str = '{'; 374 for (var i in obj){ 375 if (obj.hasOwnProperty(i)) { 376 str += (first?'':', ') + i + ':' + util.stringify(obj[i]); 377 first = false; 378 } 379 } 380 return str + '}'; 381 } 382 if (type === 'regexp') { 383 return '/' + obj.source + '/'; 384 } 385 if (type === 'string') { 386 return '"' + obj.replace(/"/g,'\\"') + '"'; 387 } 388 return obj.toString(); 389 }, 390 391 headerGradient: (function(){ 392 393 var canvas = document.createElement('canvas'); 394 if (!canvas.getContext) { return ''; } 395 var cx = canvas.getContext('2d'); 396 canvas.height = 30; 397 canvas.width = 1; 398 399 var linearGrad = cx.createLinearGradient(0,0,0,30); 400 linearGrad.addColorStop(0,'rgba(0,0,0,0)'); 401 linearGrad.addColorStop(1,'rgba(0,0,0,0.25)'); 402 403 cx.fillStyle = linearGrad; 404 cx.fillRect(0,0,1,30); 405 406 var dataURL = canvas.toDataURL && canvas.toDataURL(); 407 return 'url(' + (dataURL || '') + ')'; 408 409 })() 410 411 }; 412 413 // Main.. 414 var prettyPrintThis = function(obj, options) { 415 416 /* 417 * obj :: Object to be printed 418 * options :: Options (merged with config) 419 */ 420 421 options = options || {}; 422 423 var settings = util.merge( {}, prettyPrintThis.config, options ), 424 container = util.el('div'), 425 config = prettyPrintThis.config, 426 currentDepth = 0, 427 stack = {}, 428 hasRunOnce = false; 429 430 /* Expose per-call settings. 431 Note: "config" is overwritten (where necessary) by options/"settings" 432 So, if you need to access/change *DEFAULT* settings then go via ".config" */ 433 prettyPrintThis.settings = settings; 434 435 var typeDealer = { 436 string : function(item){ 437 return util.txt('"' + util.shorten(item.replace(/"/g,'\\"')) + '"'); 438 }, 439 number : function(item) { 440 return util.txt(item); 441 }, 442 regexp : function(item) { 443 444 var miniTable = util.table(['RegExp',null], 'regexp'); 445 var flags = util.table(); 446 var span = util.expander( 447 '/' + item.source + '/', 448 'Click to show more', 449 function() { 450 this.parentNode.appendChild(miniTable.node); 451 } 452 ); 453 454 flags 455 .addRow(['g', item.global]) 456 .addRow(['i', item.ignoreCase]) 457 .addRow(['m', item.multiline]); 458 459 miniTable 460 .addRow(['source', '/' + item.source + '/']) 461 .addRow(['flags', flags.node]) 462 .addRow(['lastIndex', item.lastIndex]); 463 464 return settings.expanded ? miniTable.node : span; 465 }, 466 domelement : function(element, depth) { 467 468 var miniTable = util.table(['DOMElement',null], 'domelement'), 469 props = ['id', 'className', 'innerHTML', 'src', 'href'], elname = element.nodeName || ''; 470 471 miniTable.addRow(['tag', '<' + elname.toLowerCase() + '>']); 472 473 util.forEach(props, function(prop){ 474 if ( element[prop] ) { 475 miniTable.addRow([ prop, util.htmlentities(element[prop]) ]); 476 } 477 }); 478 479 return settings.expanded ? miniTable.node : util.expander( 480 'DOMElement (' + elname.toLowerCase() + ')', 481 'Click to show more', 482 function() { 483 this.parentNode.appendChild(miniTable.node); 484 } 485 ); 486 }, 487 domnode : function(node){ 488
489 /* Deals with all DOMNodes that aren't elements (nodeType !== 1) */ 490 var miniTable = util.table(['DOMNode',null], 'domelement'), 491 data = util.htmlentities( (node.data || 'UNDEFINED').replace(/\n/g,'\\n') ); 492 miniTable 493 .addRow(['nodeType', node.nodeType + ' (' + node.nodeName + ')']) 494 .addRow(['data', data]); 495 496 return settings.expanded ? miniTable.node : util.expander( 497 'DOMNode', 498 'Click to show more', 499 function() { 500 this.parentNode.appendChild(miniTable.node); 501 } 502 ); 503 }, 504 jquery : function(obj, depth, key) { 505 return typeDealer['array'](obj, depth, key, true); 506 }, 507 object : function(obj, depth, key) { 508 509 /* Checking depth + circular refs */ 510 /* Note, check for circular refs before depth; just makes more sense */ 511 var stackKey = util.within(stack).is(obj); 512 if ( stackKey ) { 513 return util.common.circRef(obj, stackKey, settings); 514 } 515 stack[key||'TOP'] = obj; 516 if (depth === settings.maxDepth) { 517 return util.common.depthReached(obj, settings); 518 } 519 520 var table = util.table(['Object', null],'object'), 521 isEmpty = true; 522 523 for (var i in obj) { 524 if (!obj.hasOwnProperty || obj.hasOwnProperty(i)) { 525 var item = obj[i], 526 type = util.type(item); 527 isEmpty = false; 528 try { 529 table.addRow([i, typeDealer[ type ](item, depth+1, i)], type); 530 } catch(e) { 531 /* Security errors are thrown on certain Window/DOM properties */ 532 if (window.console && window.console.log) { 533 console.log(e.message); 534 } 535 } 536 } 537 } 538 539 if (isEmpty) { 540 table.addRow(['<small>[empty]</small>']); 541 } else { 542 table.thead.appendChild( 543 util.hRow(['key','value'], 'colHeader') 544 ); 545 } 546 547 var ret = (settings.expanded || hasRunOnce) ? table.node : util.expander( 548 util.stringify(obj), 549 'Click to show more', 550 function() { 551 this.parentNode.appendChild(table.node); 552 } 553 ); 554 555 hasRunOnce = true; 556 557 return ret; 558 559 }, 560 array : function(arr, depth, key, jquery) { 561 562 /* Checking depth + circular refs */ 563 /* Note, check for circular refs before depth; just makes more sense */ 564 var stackKey = util.within(stack).is(arr); 565 if ( stackKey ) { 566 return util.common.circRef(arr, stackKey); 567 } 568 stack[key||'TOP'] = arr; 569 if (depth === settings.maxDepth) { 570 return util.common.depthReached(arr); 571 } 572 573 /* Accepts a table and modifies it */ 574 var me = jquery ? 'jQuery' : 'Array', table = util.table([me + '(' + arr.length + ')', null], jquery ? 'jquery' : me.toLowerCase()), 575 isEmpty = true, 576 count = 0; 577 578 if (jquery){ 579 table.addRow(['selector',arr.selector]); 580 } 581 582 util.forEach(arr, function(item,i){ 583 if (settings.maxArray >= 0 && ++count > settings.maxArray) { 584 table.addRow([ 585 i + '..' + (arr.length-1), 586 typeDealer[ util.type(item) ]('...', depth+1, i) 587 ]); 588 return false; 589 } 590 isEmpty = false; 591 table.addRow([i, typeDealer[ util.type(item) ](item, depth+1, i)]); 592 }); 593 594 if (!jquery){ 595 if (isEmpty) { 596 table.addRow(['<small>[empty]</small>']); 597 } else { 598 table.thead.appendChild( util.hRow(['index','value'], 'colHeader') ); 599 } 600 } 601 602 return settings.expanded ? table.node : util.expander( 603 util.stringify(arr), 604 'Click to show more', 605 function() { 606 this.parentNode.appendChild(table.node); 607 } 608 ); 609 610 }, 611 'function' : function(fn, depth, key) { 612 613 /* Checking JUST circular refs */ 614 var stackKey = util.within(stack).is(fn); 615 if ( stackKey ) { return util.common.circRef(fn, stackKey); } 616 stack[key||'TOP'] = fn; 617 618 var miniTable = util.table(['Function',null], 'function'), 619 argsTable = util.table(['Arguments']), 620 args = fn.toString().match(/\((.+?)\)/), 621 body = fn.toString().match(/\(.*?\)\s+?\{?([\S\s]+)/)[1].replace(/\}?$/,''); 622 623 miniTable 624 .addRow(['arguments', args ? args[1].replace(/[^\w_,\s]/g,'') : '<small>[none/native]</small>']) 625 .addRow(['body', body]); 626 627 return settings.expanded ? miniTable.node : util.expander( 628 'function(){...}', 629 'Click to see more about this function.', 630 function(){ 631 this.parentNode.appendChild(miniTable.node); 632 } 633 ); 634 }, 635 'date' : function(date) { 636 637 var miniTable = util.table(['Date',null], 'date'), 638 sDate = date.toString().split(/\s/); 639 640 /* TODO: Make this work well in IE! */ 641 miniTable 642 .addRow(['Time', sDate[4]]) 643 .addRow(['Date', sDate.slice(0,4).join('-')]); 644 645 return settings.expanded ? miniTable.node : util.expander( 646 'Date (timestamp): ' + (+date), 647 'Click to see a little more info about this date', 648 function() { 649 this.parentNode.appendChild(miniTable.node); 650 } 651 ); 652 653 }, 654 'boolean' : function(bool) { 655 return util.txt( bool.toString().toUpperCase() ); 656 }, 657 'undefined' : function() { 658 return util.txt('UNDEFINED'); 659 }, 660 'null' : function() { 661 return util.txt('NULL'); 662 }, 663 'default' : function() { 664 /* When a type cannot be found */ 665 return util.txt('prettyPrint: TypeNotFound Error'); 666 } 667 }; 668 669 container.appendChild( typeDealer[ (settings.forceObject) ? 'object' : util.type(obj) ](obj, currentDepth) ); 670 671 return container; 672 673 }; 674 675 /* Configuration */ 676
677 /* All items can be overwridden by passing an 678 "options" object when calling prettyPrint */ 679 prettyPrintThis.config = { 680 681 /* Try setting this to false to save space */ 682 expanded: true, 683 684 forceObject: false, 685 maxDepth: 3, 686 maxArray: -1, // default is unlimited 687 styles: { 688 array: { 689 th: { 690 backgroundColor: '#6DBD2A', 691 color: 'white' 692 } 693 }, 694 'function': { 695 th: { 696 backgroundColor: '#D82525' 697 } 698 }, 699 regexp: { 700 th: { 701 backgroundColor: '#E2F3FB', 702 color: '#000' 703 } 704 }, 705 object: { 706 th: { 707 backgroundColor: '#1F96CF' 708 } 709 }, 710 jquery : { 711 th: { 712 backgroundColor: '#FBF315' 713 } 714 }, 715 error: { 716 th: { 717 backgroundColor: 'red', 718 color: 'yellow' 719 } 720 }, 721 domelement: { 722 th: { 723 backgroundColor: '#F3801E' 724 } 725 }, 726 date: { 727 th: { 728 backgroundColor: '#A725D8' 729 } 730 }, 731 colHeader: { 732 th: { 733 backgroundColor: '#EEE', 734 color: '#000', 735 textTransform: 'uppercase' 736 } 737 }, 738 'default': { 739 table: { 740 borderCollapse: 'collapse', 741 width: '100%' 742 }, 743 td: { 744 padding: '5px', 745 fontSize: '12px', 746 backgroundColor: '#FFF', 747 color: '#222', 748 border: '1px solid #000', 749 verticalAlign: 'top', 750 fontFamily: '"Consolas","Lucida Console",Courier,mono', 751 whiteSpace: 'nowrap' 752 }, 753 td_hover: { 754 /* Styles defined here will apply to all tr:hover > td, 755 - Be aware that "inheritable" properties (e.g. fontWeight) WILL BE INHERITED */ 756 }, 757 th: { 758 padding: '5px', 759 fontSize: '12px', 760 backgroundColor: '#222', 761 color: '#EEE', 762 textAlign: 'left', 763 border: '1px solid #000', 764 verticalAlign: 'top', 765 fontFamily: '"Consolas","Lucida Console",Courier,mono', 766 backgroundImage: util.headerGradient, 767 backgroundRepeat: 'repeat-x' 768 } 769 } 770 } 771 }; 772 773 return prettyPrintThis; 774 775})();
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.