Github: JSON reformatter

Reformats JSON(P) files in the github tree view for readability.

  1. // ==UserScript==
  2. // @name Github: JSON reformatter
  3. // @namespace http://github.com/johan/
  4. // @description Reformats JSON(P) files in the github tree view for readability.
  5. // @include https://github.com/*/blob/*/*.json*
  6. // @match https://github.com/*/blob/*/*.json*
  7. // @version 0.0.1.20140419225639
  8. // ==/UserScript==
  9.  
  10. var $json, $ln, $o_js, $o_ln // json and line numbers jQuery objects + originals
  11. , spc = ' '
  12. , js_css = // all custom formatting for our node.js-style-indented foldable json
  13. '.json{white-space:pre-wrap;font-family:monospace;}' +
  14. '.json .callback{color:Blue;}' +
  15. '.json .prop{color:DarkGoldenRod;}' +
  16. '.json .str{color:RosyBrown;}' +
  17. '.json .null,.json .bool{color:CadetBlue;}' +
  18. '.json .num{color:#000;}' +
  19. // let :before rules remain visible but make this essentially "display: none":
  20. '.json .folded *{height:0;width:0;top:-999cm;left:-999cm;white-space:normal;'+
  21. 'position:absolute;color:transparent;}' +
  22. '.json .folded.arr:before{color:#666;content:"[\\002026 ]'+ spc +'";}' +// […]
  23. '.json .folded.obj:before{color:#666;content:"{\\002026 }'+ spc +'";}' +// {…}
  24. '.json .folded{background:#FFF;}' +
  25. '.json .folded:hover{font-weight:700;color:#000;}' +
  26. '.json .folded{cursor:se-resize;}' +
  27. '.json .unfolded.hovered{background:rgba(255,192,203,0.5);}' +
  28. '.json .unfolded{cursor:nw-resize;}';
  29.  
  30. var JSONFormatter = (function() {
  31. var toString = Object.prototype.toString, BR = '<br\n/>', re =
  32. // This regex attempts to match a JSONP structure (ws includes Unicode ws)
  33. // * optional leading ws
  34. // * callback name (any valid function name as per ECMA-262 Edition 3 specs)
  35. // * optional ws
  36. // * open parenthesis
  37. // * optional ws
  38. // * either { or [, the only two valid characters to start a JSON string
  39. // * any character, any number of times
  40. // * either } or ], the only two valid closing characters of a JSON string
  41. // * optional trailing ws and semicolon
  42. // (this of course misses anything that has comments, more than one callback
  43. // -- or otherwise requires modification before use by a proper JSON parser)
  44. /^[\s\u200B\uFEFF]*([\w$\[\]\.]+)[\s\u200B\uFEFF]*\([\s\u200B\uFEFF]*([\[{][\s\S]*[\]}])[\s\u200B\uFEFF]*\)([\s\u200B\uFEFF;]*)$/m;
  45.  
  46. function detectJSONP(s) {
  47. var js = s, cb = '', se = '', match;
  48. if ('string' !== typeof s) return wrapJSONP(s, cb, se);
  49. if ((match = re.exec(s)) && 4 === match.length) {
  50. cb = match[1];
  51. js = match[2];
  52. se = match[3].replace(/[^;]+/g, '');
  53. }
  54.  
  55. try {
  56. return wrapJSONP(JSON.parse(js), cb, se);
  57. }
  58. catch (e) {
  59. return error(e, s);
  60. }
  61. }
  62.  
  63. // Convert a JSON value / JSONP response into a formatted HTML document
  64. function wrapJSONP(val, callback, semicolon) {
  65. var output = span(value(val, callback ? '' : null, callback && BR),
  66. 'json');
  67. if (callback)
  68. output = span(callback +'(', 'callback') + output +
  69. span(')'+ semicolon, 'callback');
  70. return output;
  71. }
  72.  
  73. // utility functions
  74.  
  75. function isArray(obj) {
  76. return '[object Array]' === toString.call(obj);
  77. }
  78.  
  79. // Wrap a fragment in a span of class className
  80. function span(html, className) {
  81. return '<span class=\''+ className +'\'>'+ html +'</span>';
  82. }
  83.  
  84. // Produce an error document for when parsing fails
  85. function error(e, data) {
  86. return span('Error parsing JSON: '+ e, 'error') +'<h1>Content:</h1>'+
  87. span(html(data), 'json');
  88. }
  89.  
  90. // escaping functions
  91.  
  92. function html(s, isAttribute) {
  93. if (s == null) return '';
  94. s = (s+'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
  95. return isAttribute ? s.replace(/'/g, '&apos;') : s;
  96. }
  97.  
  98. var js = JSON.stringify('\b\f\n\r\t').length === 12 ?
  99. function saneJSEscaper(s, noQuotes) {
  100. s = html(JSON.stringify(s).slice(1, -1));
  101. return noQuotes ? s : '&quot;'+ s +'&quot;';
  102. }
  103. : function insaneEscaper(s, noQuotes) {
  104. // undo all damage of an \uXXXX-tastic Mozilla JSON serializer
  105. var had = { '\b': 'b' // return
  106. , '\f': 'f' // these
  107. , '\r': 'r' // to the
  108. , '\n': 'n' // tidy
  109. , '\t': 't' // form
  110. }, ws; // below
  111. for (ws in had)
  112. if (-1 === s.indexOf(ws))
  113. delete had[ws];
  114.  
  115. s = JSON.stringify(s).slice(1, -1);
  116.  
  117. for (ws in had)
  118. s = s.replace(new RegExp('\\\\u000'+(ws.charCodeAt().toString(16)), 'ig'),
  119. '\\'+ had[ws]);
  120.  
  121. s = html(s);
  122. return noQuotes ? s : '&quot;'+ s +'&quot;';
  123. };
  124.  
  125. // conversion functions
  126.  
  127. // Convert JSON value (Boolean, Number, String, Array, Object, null)
  128. // into an HTML fragment
  129. function value(v, indent, nl) {
  130. var output;
  131. switch (typeof v) {
  132. case 'boolean':
  133. output = span(html(v), 'bool');
  134. break;
  135.  
  136. case 'number':
  137. output = span(html(v), 'num');
  138. break;
  139.  
  140. case 'string':
  141. if (/^(\w+):\/\/[^\s]+$/i.test(v)) {
  142. output = '&quot;<a href=\''+ html(v, !!'attribute') +'\'>' +
  143. js(v, 1) +
  144. '</a>&quot;';
  145. } else {
  146. output = span(js(v), 'str');
  147. }
  148. break;
  149.  
  150. case 'object':
  151. if (null === v) {
  152. output = span('null', 'null');
  153. } else {
  154. indent = indent == null ? '' : indent +'&nbsp; ';
  155. if (isArray(v)) {
  156. output = array(v, indent, nl);
  157. } else {
  158. output = object(v, indent, nl);
  159. }
  160. }
  161. break;
  162. }
  163. return output;
  164. }
  165.  
  166. // Convert an Object to an HTML fragment
  167. function object(obj, indent, nl) {
  168. var output = '';
  169. for (var key in obj) {
  170. if (output) output += BR + indent +', ';
  171. output += span(js(key), 'prop') +': ' +
  172. value(obj[key], indent, BR);
  173. }
  174. if (!output) return '{}';
  175. return '<span class=\'unfolded obj\'><span class=content>' +
  176. (nl ? nl + indent : '') + '{ '+ output + BR + indent + '}' +
  177. '</span></span>';
  178. }
  179.  
  180. // Convert an Array into an HTML fragment
  181. function array(a, indent, nl) {
  182. for (var i = 0, output = ''; i < a.length; i++) {
  183. if (output) output += BR + indent +', ';
  184. output += value(a[i], indent, '');
  185. }
  186. if (!output) return '[]';
  187. return '<span class=\'unfolded arr\'><span class=content>' +
  188. (nl ? nl + indent : '') +'[ '+ output + BR +
  189. indent +']</span></span>';
  190. }
  191.  
  192. // Takes a string of JSON and returns a string of HTML.
  193. // Be sure to call JSONFormatter.init(document) once, too (for styling / UX).
  194. function JSONFormatter(s) {
  195. return detectJSONP(s);
  196. }
  197.  
  198. // Pass the document that you render the HTML into, to set up css and events.
  199. JSONFormatter.init = function init(doc, css) {
  200. doc = doc || document;
  201. var head = doc.getElementsByTagName('head')[0] || doc.documentElement
  202. , node = doc.getElementById('json-format') || doc.createElement('style');
  203. if (node.id) return; else node.id = 'json-format';
  204. node.textContent = css || js_css;
  205. head.appendChild(node);
  206. doc.addEventListener('click', function folding(e) {
  207. var elem = e.target, is, is_json = elem;
  208. while (is_json && is_json.className != 'json')
  209. is_json = is_json.parentNode;
  210. if (!is_json) return; // only do folding/unfolding on json nodes
  211.  
  212. do {
  213. if (/^a$/i.test(elem.nodeName)) return;
  214. is = elem.className || '';
  215. } while (!/\b(un)?folded /.test(is) && (elem = elem.parentNode));
  216. if (elem) {
  217. elem.className = /unfolded /.test(is)
  218. ? is.replace('unfolded ', 'folded ')
  219. : is.replace('folded ', 'unfolded ');
  220. }
  221. }, false);
  222. };
  223.  
  224. return JSONFormatter;
  225. })();
  226.  
  227. function mode_switch() {
  228. $o_ln.toggle(); $ln.toggle();
  229. $o_js.toggle(); $json.toggle();
  230. }
  231.  
  232. function mode_pick(to) {
  233. var json = 'orig' === to ? 'hide' : 'show'
  234. , orig = 'orig' === to ? 'show' : 'hide';
  235. return function(e) {
  236. $json[json](); $ln[json]();
  237. $o_js[orig](); $o_ln[orig]();
  238. e.preventDefault();
  239. };
  240. }
  241.  
  242. function init() {
  243. $o_ln = $('#files .file .data .line_numbers');
  244. $o_js = $('#files .file .data .highlight pre');
  245. var el_ln = $o_ln.get(0).cloneNode(false)
  246. , el_js = $o_js.get(0).cloneNode(false)
  247. , json = $o_js.text().replace(/\u00A0+/g,'')
  248. , html;
  249. if (1 === $o_js.length) try {
  250. html = JSONFormatter(json);
  251. $ln = $(el_ln).hide(); $o_ln.before($ln);
  252. $json = $(el_js).hide(); $o_js.before($json);
  253. $json.css('padding-left', '1em'); // this looks much nicer
  254. $json.closest('td').css('vertical-align', 'top'); // ditto – not "middle"
  255. $json.html(html);
  256. for (var ln = '', lines = 1+$json.find('br').length, n = 1; n <= lines; n++)
  257. ln += '<span id="L'+ n +'" rel="#L'+ n +'">'+ n +'</span>\n';
  258. $ln.html(ln);
  259. JSONFormatter.init(document);
  260. mode_switch();
  261.  
  262. var $actions = $('#files .file .meta .actions');
  263. $actions.prepend('<li><a id="orig" href="#orig">source</a></li>');
  264. $actions.prepend('<li><a id="json" href="#json">json</a></li>');
  265. $actions.find('#orig').click(mode_pick('orig'));
  266. $actions.find('#json').click(mode_pick('json'));
  267. } catch(e) { console.error(e); }
  268. }
  269.  
  270. // This block of code injects our source in the content scope and then calls the
  271. // passed callback there. The whole script runs in both GM and page content, but
  272. // since we have no other code that does anything, the Greasemonkey sandbox does
  273. // nothing at all when it has spawned the page script, which gets to use jQuery.
  274. // (jQuery unfortunately degrades much when run in Mozilla's javascript sandbox)
  275. if ('object' === typeof opera && opera.extension) {
  276. this.__proto__ = window; // bleed the web page's js into our execution scope
  277. document.addEventListener('DOMContentLoaded', init, false); // GM-style init
  278. } else (function(run_me_in_page_scope) { // for Chrome or Firefox+Greasemonkey
  279. if ('undefined' == typeof __RUNS_IN_PAGE_SCOPE__) { // unsandbox, please!
  280. var src = arguments.callee.caller.toString(),
  281. script = document.createElement('script');
  282. script.setAttribute("type", "application/javascript");
  283. script.innerHTML = "const __RUNS_IN_PAGE_SCOPE__ = true;\n("+ src +')();';
  284. document.documentElement.appendChild(script);
  285. document.documentElement.removeChild(script);
  286. } else { // unsandboxed -- here we go!
  287. run_me_in_page_scope();
  288. }
  289. })(init);

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址