json-format-dealer.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486
  1. // ==ClosureCompiler==
  2. // @compilation_level ADVANCED_OPTIMIZATIONS
  3. // @output_file_name background.js
  4. // @externs_url http://closure-compiler.googlecode.com/svn/trunk/contrib/externs/chrome_extensions.js
  5. // @js_externs var console = {assert: function(){}};
  6. // @formatting pretty_print
  7. // ==/ClosureCompiler==
  8. /** @license
  9. JSON Formatter | MIT License
  10. Copyright 2012 Callum Locke
  11. Permission is hereby granted, free of charge, to any person obtaining a copy of
  12. this software and associated documentation files (the "Software"), to deal in
  13. the Software without restriction, including without limitation the rights to
  14. use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
  15. of the Software, and to permit persons to whom the Software is furnished to do
  16. so, subject to the following conditions:
  17. The above copyright notice and this permission notice shall be included in all
  18. copies or substantial portions of the Software.
  19. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  20. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  21. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  22. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  23. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  24. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  25. SOFTWARE.
  26. */
  27. /*jshint eqeqeq:true, forin:true, strict:true */
  28. /*global chrome, console */
  29. var JsonFormatDealer = (function () {
  30. "use strict" ;
  31. // Constants
  32. var
  33. TYPE_STRING = 1,
  34. TYPE_NUMBER = 2,
  35. TYPE_OBJECT = 3,
  36. TYPE_ARRAY = 4,
  37. TYPE_BOOL = 5,
  38. TYPE_NULL = 6
  39. ;
  40. // Utility functions
  41. function removeComments (str) {
  42. str = ('__' + str + '__').split('');
  43. var mode = {
  44. singleQuote: false,
  45. doubleQuote: false,
  46. regex: false,
  47. blockComment: false,
  48. lineComment: false,
  49. condComp: false
  50. };
  51. for (var i = 0, l = str.length; i < l; i++) {
  52. if (mode.regex) {
  53. if (str[i] === '/' && str[i-1] !== '\\') {
  54. mode.regex = false;
  55. }
  56. continue;
  57. }
  58. if (mode.singleQuote) {
  59. if (str[i] === "'" && str[i-1] !== '\\') {
  60. mode.singleQuote = false;
  61. }
  62. continue;
  63. }
  64. if (mode.doubleQuote) {
  65. if (str[i] === '"' && str[i-1] !== '\\') {
  66. mode.doubleQuote = false;
  67. }
  68. continue;
  69. }
  70. if (mode.blockComment) {
  71. if (str[i] === '*' && str[i+1] === '/') {
  72. str[i+1] = '';
  73. mode.blockComment = false;
  74. }
  75. str[i] = '';
  76. continue;
  77. }
  78. if (mode.lineComment) {
  79. if (str[i+1] === '\n' || str[i+1] === '\r') {
  80. mode.lineComment = false;
  81. }
  82. str[i] = '';
  83. continue;
  84. }
  85. if (mode.condComp) {
  86. if (str[i-2] === '@' && str[i-1] === '*' && str[i] === '/') {
  87. mode.condComp = false;
  88. }
  89. continue;
  90. }
  91. mode.doubleQuote = str[i] === '"';
  92. mode.singleQuote = str[i] === "'";
  93. if (str[i] === '/') {
  94. if (str[i+1] === '*' && str[i+2] === '@') {
  95. mode.condComp = true;
  96. continue;
  97. }
  98. if (str[i+1] === '*') {
  99. str[i] = '';
  100. mode.blockComment = true;
  101. continue;
  102. }
  103. if (str[i+1] === '/') {
  104. str[i] = '';
  105. mode.lineComment = true;
  106. continue;
  107. }
  108. mode.regex = true;
  109. }
  110. }
  111. return str.join('').slice(2, -2);
  112. }
  113. // function spin(seconds) {
  114. // // spin - Hog the CPU for the specified number of seconds
  115. // // (for simulating long processing times in development)
  116. // var stop = +new Date() + (seconds*1000) ;
  117. // while (new Date() < stop) {}
  118. // return true ;
  119. // }
  120. // Record current version (in case future update wants to know)
  121. localStorage.jfVersion = '0.5.6' ;
  122. // Template elements
  123. var templates,
  124. baseSpan = document.createElement('span') ;
  125. function getSpanBoth(innerText,className) {
  126. var span = baseSpan.cloneNode(false) ;
  127. span.className = className ;
  128. span.innerText = innerText ;
  129. return span ;
  130. }
  131. function getSpanText(innerText) {
  132. var span = baseSpan.cloneNode(false) ;
  133. span.innerText = innerText ;
  134. return span ;
  135. }
  136. function getSpanClass(className) {
  137. var span = baseSpan.cloneNode(false) ;
  138. span.className = className ;
  139. return span ;
  140. }
  141. // Create template nodes
  142. var templatesObj = {
  143. t_kvov: getSpanClass('kvov'),
  144. t_exp: getSpanClass('e'),
  145. t_key: getSpanClass('k'),
  146. t_string: getSpanClass('s'),
  147. t_number: getSpanClass('n'),
  148. t_null: getSpanBoth('null', 'nl'),
  149. t_true: getSpanBoth('true','bl'),
  150. t_false: getSpanBoth('false','bl'),
  151. t_oBrace: getSpanBoth('{','b'),
  152. t_cBrace: getSpanBoth('}','b'),
  153. t_oBracket: getSpanBoth('[','b'),
  154. t_cBracket: getSpanBoth(']','b'),
  155. t_ellipsis: getSpanClass('ell'),
  156. t_blockInner: getSpanClass('blockInner'),
  157. t_colonAndSpace: document.createTextNode(':\u00A0'),
  158. t_commaText: document.createTextNode(','),
  159. t_dblqText: document.createTextNode('"')
  160. } ;
  161. // Core recursive DOM-building function
  162. function getKvovDOM(value, keyName) {
  163. var type,
  164. kvov,
  165. nonZeroSize,
  166. templates = templatesObj, // bring into scope for tiny speed boost
  167. objKey,
  168. keySpan,
  169. valueElement
  170. ;
  171. // Establish value type
  172. if (typeof value === 'string')
  173. type = TYPE_STRING ;
  174. else if (typeof value === 'number')
  175. type = TYPE_NUMBER ;
  176. else if (value === false || value === true )
  177. type = TYPE_BOOL ;
  178. else if (value === null)
  179. type = TYPE_NULL ;
  180. else if (value instanceof Array)
  181. type = TYPE_ARRAY ;
  182. else
  183. type = TYPE_OBJECT ;
  184. // Root node for this kvov
  185. kvov = templates.t_kvov.cloneNode(false) ;
  186. // Add an 'expander' first (if this is object/array with non-zero size)
  187. if (type === TYPE_OBJECT || type === TYPE_ARRAY) {
  188. nonZeroSize = false ;
  189. for (objKey in value) {
  190. if (value.hasOwnProperty(objKey)) {
  191. nonZeroSize = true ;
  192. break ; // no need to keep counting; only need one
  193. }
  194. }
  195. if (nonZeroSize)
  196. kvov.appendChild( templates.t_exp.cloneNode(false) ) ;
  197. }
  198. // If there's a key, add that before the value
  199. if (keyName !== false) { // NB: "" is a legal keyname in JSON
  200. // This kvov must be an object property
  201. kvov.classList.add('objProp') ;
  202. // Create a span for the key name
  203. keySpan = templates.t_key.cloneNode(false) ;
  204. keySpan.textContent = JSON.stringify(keyName).slice(1,-1) ; // remove quotes
  205. // Add it to kvov, with quote marks
  206. kvov.appendChild(templates.t_dblqText.cloneNode(false)) ;
  207. kvov.appendChild( keySpan ) ;
  208. kvov.appendChild(templates.t_dblqText.cloneNode(false)) ;
  209. // Also add ":&nbsp;" (colon and non-breaking space)
  210. kvov.appendChild( templates.t_colonAndSpace.cloneNode(false) ) ;
  211. }
  212. else {
  213. // This is an array element instead
  214. kvov.classList.add('arrElem') ;
  215. }
  216. // Generate DOM for this value
  217. var blockInner, childKvov ;
  218. switch (type) {
  219. case TYPE_STRING:
  220. // If string is a URL, get a link, otherwise get a span
  221. var innerStringEl = baseSpan.cloneNode(false),
  222. escapedString = JSON.stringify(value)
  223. ;
  224. escapedString = escapedString.substring(1, escapedString.length-1) ; // remove quotes
  225. if (value[0] === 'h' && value.substring(0, 4) === 'http') { // crude but fast - some false positives, but rare, and UX doesn't suffer terribly from them.
  226. var innerStringA = document.createElement('A') ;
  227. innerStringA.href = value ;
  228. innerStringA.innerText = escapedString ;
  229. innerStringEl.appendChild(innerStringA) ;
  230. }
  231. else {
  232. innerStringEl.innerText = escapedString ;
  233. }
  234. valueElement = templates.t_string.cloneNode(false) ;
  235. valueElement.appendChild(templates.t_dblqText.cloneNode(false)) ;
  236. valueElement.appendChild(innerStringEl) ;
  237. valueElement.appendChild(templates.t_dblqText.cloneNode(false)) ;
  238. kvov.appendChild(valueElement) ;
  239. break ;
  240. case TYPE_NUMBER:
  241. // Simply add a number element (span.n)
  242. valueElement = templates.t_number.cloneNode(false) ;
  243. valueElement.innerText = value ;
  244. kvov.appendChild(valueElement) ;
  245. break ;
  246. case TYPE_OBJECT:
  247. // Add opening brace
  248. kvov.appendChild( templates.t_oBrace.cloneNode(true) ) ;
  249. // If any properties, add a blockInner containing k/v pair(s)
  250. if (nonZeroSize) {
  251. // Add ellipsis (empty, but will be made to do something when kvov is collapsed)
  252. kvov.appendChild( templates.t_ellipsis.cloneNode(false) ) ;
  253. // Create blockInner, which indents (don't attach yet)
  254. blockInner = templates.t_blockInner.cloneNode(false) ;
  255. // For each key/value pair, add as a kvov to blockInner
  256. var count = 0, k, comma ;
  257. for (k in value) {
  258. if (value.hasOwnProperty(k)) {
  259. count++ ;
  260. childKvov = getKvovDOM(value[k], k) ;
  261. // Add comma
  262. comma = templates.t_commaText.cloneNode() ;
  263. childKvov.appendChild(comma) ;
  264. blockInner.appendChild( childKvov ) ;
  265. }
  266. }
  267. // Now remove the last comma
  268. childKvov.removeChild(comma) ;
  269. // Add blockInner
  270. kvov.appendChild( blockInner ) ;
  271. }
  272. // Add closing brace
  273. kvov.appendChild( templates.t_cBrace.cloneNode(true) ) ;
  274. break ;
  275. case TYPE_ARRAY:
  276. // Add opening bracket
  277. kvov.appendChild( templates.t_oBracket.cloneNode(true) ) ;
  278. // If non-zero length array, add blockInner containing inner vals
  279. if (nonZeroSize) {
  280. // Add ellipsis
  281. kvov.appendChild( templates.t_ellipsis.cloneNode(false) ) ;
  282. // Create blockInner (which indents) (don't attach yet)
  283. blockInner = templates.t_blockInner.cloneNode(false) ;
  284. // For each key/value pair, add the markup
  285. for (var i=0, length=value.length, lastIndex=length-1; i<length; i++) {
  286. // Make a new kvov, with no key
  287. childKvov = getKvovDOM(value[i], false) ;
  288. // Add comma if not last one
  289. if (i < lastIndex)
  290. childKvov.appendChild( templates.t_commaText.cloneNode() ) ;
  291. // Append the child kvov
  292. blockInner.appendChild( childKvov ) ;
  293. }
  294. // Add blockInner
  295. kvov.appendChild( blockInner ) ;
  296. }
  297. // Add closing bracket
  298. kvov.appendChild( templates.t_cBracket.cloneNode(true) ) ;
  299. break ;
  300. case TYPE_BOOL:
  301. if (value)
  302. kvov.appendChild( templates.t_true.cloneNode(true) ) ;
  303. else
  304. kvov.appendChild( templates.t_false.cloneNode(true) ) ;
  305. break ;
  306. case TYPE_NULL:
  307. kvov.appendChild( templates.t_null.cloneNode(true) ) ;
  308. break ;
  309. }
  310. return kvov ;
  311. }
  312. // Function to convert object to an HTML string
  313. function jsonObjToHTML(obj, jsonpFunctionName) {
  314. // spin(5) ;
  315. // Format object (using recursive kvov builder)
  316. var rootKvov = getKvovDOM(obj, false) ;
  317. // The whole DOM is now built.
  318. // Set class on root node to identify it
  319. rootKvov.classList.add('rootKvov') ;
  320. // Make div#formattedJson and append the root kvov
  321. var divFormattedJson = document.createElement('DIV') ;
  322. divFormattedJson.id = 'formattedJson' ;
  323. divFormattedJson.appendChild( rootKvov ) ;
  324. // Convert it to an HTML string (shame about this step, but necessary for passing it through to the content page)
  325. var returnHTML = divFormattedJson.outerHTML ;
  326. // Top and tail with JSONP padding if necessary
  327. if (jsonpFunctionName !== null) {
  328. returnHTML =
  329. '<div id="jsonpOpener">' + jsonpFunctionName + ' ( </div>' +
  330. returnHTML +
  331. '<div id="jsonpCloser">)</div>' ;
  332. }
  333. // Return the HTML
  334. return returnHTML ;
  335. }
  336. // Listen for requests from content pages wanting to set up a port
  337. var dealTheMsg = function(msg) {
  338. var jsonpFunctionName = null ;
  339. var port = JsonFormatEntrance;
  340. if (msg.type === 'SENDING TEXT') {
  341. // Try to parse as JSON
  342. var obj,
  343. text = msg.text ;
  344. try {
  345. obj = new Function('return ' + text)() ;
  346. }
  347. catch(e){
  348. // Not JSON; could be JSONP though.
  349. // Try stripping 'padding' (if any), and try parsing it again
  350. text = text.trim() ;
  351. // Find where the first paren is (and exit if none)
  352. var indexOfParen ;
  353. if ( ! (indexOfParen = text.indexOf('(') ) ) {
  354. port.postMessage(['NOT JSON', 'no opening parenthesis']) ;
  355. port.disconnect() ;
  356. return ;
  357. }
  358. // Get the substring up to the first "(", with any comments/whitespace stripped out
  359. var firstBit = removeComments( text.substring(0,indexOfParen) ).trim() ;
  360. if ( ! firstBit.match(/^[a-zA-Z_$][\.\[\]'"0-9a-zA-Z_$]*$/) ) {
  361. // The 'firstBit' is NOT a valid function identifier.
  362. port.postMessage(['NOT JSON', 'first bit not a valid function name']) ;
  363. port.disconnect() ;
  364. return ;
  365. }
  366. // Find last parenthesis (exit if none)
  367. var indexOfLastParen ;
  368. if ( ! (indexOfLastParen = text.lastIndexOf(')') ) ) {
  369. port.postMessage(['NOT JSON', 'no closing paren']) ;
  370. port.disconnect() ;
  371. return ;
  372. }
  373. // Check that what's after the last parenthesis is just whitespace, comments, and possibly a semicolon (exit if anything else)
  374. var lastBit = removeComments(text.substring(indexOfLastParen+1)).trim() ;
  375. if ( lastBit !== "" && lastBit !== ';' ) {
  376. port.postMessage(['NOT JSON', 'last closing paren followed by invalid characters']) ;
  377. port.disconnect() ;
  378. return ;
  379. }
  380. // So, it looks like a valid JS function call, but we don't know whether it's JSON inside the parentheses...
  381. // Check if the 'argument' is actually JSON (and record the parsed result)
  382. text = text.substring(indexOfParen+1, indexOfLastParen) ;
  383. try {
  384. obj = JSON.parse(text) ;
  385. }
  386. catch(e2) {
  387. // Just some other text that happens to be in a function call.
  388. // Respond as not JSON, and exit
  389. port.postMessage(['NOT JSON', 'looks like a function call, but the parameter is not valid JSON']) ;
  390. return ;
  391. }
  392. jsonpFunctionName = firstBit ;
  393. }
  394. // If still running, we now have obj, which is valid JSON.
  395. // Ensure it's not a number or string (technically valid JSON, but no point prettifying it)
  396. if (typeof obj !== 'object' && typeof obj !== 'array') {
  397. port.postMessage(['NOT JSON', 'technically JSON but not an object or array']) ;
  398. port.disconnect() ;
  399. return ;
  400. }
  401. // And send it the message to confirm that we're now formatting (so it can show a spinner)
  402. port.postMessage(['FORMATTING' /*, JSON.stringify(localStorage)*/]) ;
  403. // Do formatting
  404. var html = jsonObjToHTML(obj, jsonpFunctionName) ;
  405. // Post the HTML string to the content script
  406. port.postMessage(['FORMATTED', html]) ;
  407. // Disconnect
  408. port.disconnect() ;
  409. }
  410. };
  411. /**
  412. * 发送消息
  413. * @param {[type]} config [description]
  414. * @return {[type]}
  415. */
  416. var postMessage = function(msg){
  417. dealTheMsg(msg);
  418. };
  419. /**
  420. * 断开连接
  421. * @return {[type]}
  422. */
  423. var disconnect = function(){
  424. };
  425. return {
  426. postMessage : postMessage,
  427. disconnect : disconnect
  428. };
  429. })() ;