/* global console, jsonView */ /* * ViewJSON * Version 1.0 * A Google Chrome extension to display JSON in a user-friendly format * * This is a chromeified version of the JSONView Firefox extension by Ben Hollis: * http://jsonview.com * http://code.google.com/p/jsonview * * Also based on the XMLTree Chrome extension by Moonty & alan.stroop * https://chrome.google.com/extensions/detail/gbammbheopgpmaagmckhpjbfgdfkpadb * * port by Jamie Wilkinson (@jamiew) | http://jamiedubs.com | http://github.com/jamiew * MIT license / copyfree (f) F.A.T. Lab http://fffff.at * Speed Project Approved: 2h */ function collapse(evt) { var collapser = evt.target; var target = collapser.parentNode.getElementsByClassName('collapsible'); if (!target.length) { return; } target = target[0]; if (target.style.display === 'none') { var ellipsis = target.parentNode.getElementsByClassName('ellipsis')[0]; target.parentNode.removeChild(ellipsis); target.style.display = ''; } else { target.style.display = 'none'; var ellipsis = document.createElement('span'); ellipsis.className = 'ellipsis'; ellipsis.innerHTML = ' … '; target.parentNode.insertBefore(ellipsis, target); } collapser.innerHTML = (collapser.innerHTML === '-') ? '+' : '-'; } function addCollapser(item) { // This mainly filters out the root object (which shouldn't be collapsible) if (item.nodeName !== 'LI') { return; } var collapser = document.createElement('div'); collapser.className = 'collapser'; collapser.innerHTML = '-'; collapser.addEventListener('click', collapse, false); item.insertBefore(collapser, item.firstChild); } function jsonView(id, target) { this.debug = false; if (id.indexOf("#") !== -1) { this.idType = "id"; this.id = id.replace('#', ''); } else if (id.indexOf(".") !== -1) { this.idType = "class"; this.id = id.replace('.', ''); } else { if (this.debug) { console.log("Can't find that element"); } return; } this.data = document.getElementById(this.id).innerHTML; if (typeof(target) !== undefined) { if (target.indexOf("#") !== -1) { this.targetType = "id"; this.target = target.replace('#', ''); } else if (id.indexOf(".") !== -1) { this.targetType = "class"; this.target = target.replace('.', ''); } else { if (this.debug) { console.log("Can't find the target element"); } return; } } // Note: now using "*.json*" URI matching rather than these page regexes -- save CPU cycles! // var is_json = /^\s*(\{.*\})\s*$/.test(this.data); // var is_jsonp = /^.*\(\s*(\{.*\})\s*\)$/.test(this.data); // if(is_json || is_jsonp){ // Our manifest specifies that we only do URLs matching '.json', so attempt to sanitize any HTML // added by Chrome's "text/plain" or "text/html" handlers if (/^\(.*)\<\/pre\>$/.test(this.data)) { if (this.debug) { console.log("JSONView: data is wrapped in
...
, stripping HTML..."); } this.data = this.data.replace(/<(?:.|\s)*?>/g, ''); //Aggressively strip HTML. } // Test if what remains is JSON or JSONp var json_regex = /^\s*([\[\{].*[\}\]])\s*$/; // Ghetto, but it works var jsonp_regex = /^[\s\u200B\uFEFF]*([\w$\[\]\.]+)[\s\u200B\uFEFF]*\([\s\u200B\uFEFF]*([\[{][\s\S]*[\]}])[\s\u200B\uFEFF]*\);?[\s\u200B\uFEFF]*$/; var jsonp_regex2 = /([\[\{][\s\S]*[\]\}])\)/; // more liberal support... this allows us to pass the jsonp.json & jsonp2.json tests var is_json = json_regex.test(this.data); var is_jsonp = jsonp_regex.test(this.data); if (this.debug) { console.log("JSONView: is_json=" + is_json + " is_jsonp=" + is_jsonp); } if (is_json || is_jsonp) { if (this.debug) { console.log("JSONView: sexytime!"); } // JSONFormatter json->HTML prototype straight from Firefox JSONView // For reference: http://code.google.com/p/jsonview function JSONFormatter() { // No magic required. } JSONFormatter.prototype = { htmlEncode: function(t) { return t != null ? t.toString().replace(/&/g, "&").replace(/"/g, """).replace(//g, ">") : ''; }, decorateWithSpan: function(value, className) { return '' + this.htmlEncode(value) + ''; }, // Convert a basic JSON datatype (number, string, boolean, null, object, array) into an HTML fragment. valueToHTML: function(value) { var valueType = typeof value; var output = ""; if (value === null) { output += this.decorateWithSpan('null', 'null'); } else if (value && value.constructor === Array) { output += this.arrayToHTML(value); } else if (valueType === 'object') { output += this.objectToHTML(value); } else if (valueType === 'number') { output += this.decorateWithSpan(value, 'num'); } else if (valueType === 'string') { if (/^(http|https):\/\/[^\s]+$/.test(value)) { output += '' + this.htmlEncode(value) + ''; } else { output += this.decorateWithSpan('"' + value + '"', 'string'); } } else if (valueType === 'boolean') { output += this.decorateWithSpan(value, 'bool'); } return output; }, // Convert an array into an HTML fragment arrayToHTML: function(json) { var output = '[]'; if (!hasContents) { output = "[ ]"; } return output; }, // Convert a JSON object to an HTML fragment objectToHTML: function(json) { var output = '{}'; if (!hasContents) { output = "{ }"; } return output; }, // Convert a whole JSON object into a formatted HTML document. jsonToHTML: function(json, callback, uri) { var output = ''; if (callback) { output += '
' + callback + ' (
'; output += '
'; } else { output += '
'; } output += this.valueToHTML(json); output += '
'; if (callback) { output += '
)
'; } return this.toHTML(output, uri); }, // Produce an error document for when parsing fails. errorPage: function(error, data, uri) { // var output = '
' + this.stringbundle.GetStringFromName('errorParsing') + '
'; // output += '

' + this.stringbundle.GetStringFromName('docContents') + ':

'; var output = '
Error parsing JSON: ' + error.message + '
'; output += '

' + error.stack + ':

'; output += '
' + this.htmlEncode(data) + '
'; return this.toHTML(output, uri + ' - Error'); }, // Wrap the HTML fragment in a full document. Used by jsonToHTML and errorPage. toHTML: function(content) { return content; } }; // Sanitize & output -- all magic from JSONView Firefox this.jsonFormatter = new JSONFormatter(); // This regex attempts to match a JSONP structure: // * Any amount of whitespace (including unicode nonbreaking spaces) between the start of the file and the callback name // * Callback name (any valid JavaScript function name according to ECMA-262 Edition 3 spec) // * Any amount of whitespace (including unicode nonbreaking spaces) // * Open parentheses // * Any amount of whitespace (including unicode nonbreaking spaces) // * Either { or [, the only two valid characters to start a JSON string. // * Any character, any number of times // * Either } or ], the only two valid closing characters of a JSON string. // * Any amount of whitespace (including unicode nonbreaking spaces) // * A closing parenthesis, an optional semicolon, and any amount of whitespace (including unicode nonbreaking spaces) until the end of the file. // This will miss anything that has comments, or more than one callback, or requires modification before use. var outputDoc = ''; // text = text.match(jsonp_regex)[1]; var cleanData = '', callback = ''; var callback_results = jsonp_regex.exec(this.data); if (callback_results && callback_results.length === 3) { if (this.debug) { console.log("THIS IS JSONp"); } callback = callback_results[1]; cleanData = callback_results[2]; } else { if (this.debug) { console.log("Vanilla JSON"); } cleanData = this.data; } if (this.debug) { console.log(cleanData); } // Covert, and catch exceptions on failure try { // var jsonObj = this.nativeJSON.decode(cleanData); var jsonObj = JSON.parse(cleanData); if (jsonObj) { outputDoc = this.jsonFormatter.jsonToHTML(jsonObj, callback); } else { throw "There was no object!"; } } catch (e) { if (this.debug) { console.log(e); } outputDoc = this.jsonFormatter.errorPage(e, this.data); } var links = ''; if (this.targetType !== undefined) { this.idType = this.targetType; this.id = this.target; } var el; if (this.idType === "class") { el = document.getElementsByClassName(this.id); if (el) { el.className += el.className ? ' jsonViewOutput' : 'jsonViewOutput'; el.innerHTML = links + outputDoc; } } else if (this.idType === "id") { el = document.getElementById(this.id); if (el) { el.className += el.className ? ' jsonViewOutput' : 'jsonViewOutput'; el.innerHTML = links + outputDoc; } el.innerHTML = links + outputDoc; } var items = document.getElementsByClassName('collapsible'); for (var i = 0; i < items.length; i++) { addCollapser(items[i].parentNode); } } else { // console.log("JSONView: this is not json, not formatting."); } }