automatic.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. /**
  2. * Json Page Automatic Format Via FeHelper
  3. * @author zhaoxianlie
  4. */
  5. // json with bigint supported
  6. Tarp.require('../static/vendor/json-bigint/index');
  7. module.exports = (() => {
  8. "use strict";
  9. const JSON_SORT_TYPE_KEY = 'json_sort_type_key';
  10. // 用于记录最原始的json串
  11. let originalJsonStr = '';
  12. let curSortType = 0;
  13. // JSONP形式下的callback name
  14. let funcName = null;
  15. let jsonObj = null;
  16. let fnTry = null;
  17. let fnCatch = null;
  18. let _htmlFragment = [
  19. '<style type="text/css">.mod-contentscript #formattingMsg{position:absolute;top:0;font-size:14px;color:#333;margin:5px;}#formattingMsg .x-loading{width:12px;height:12px;border:1px solid #f00;border-radius:50%;box-shadow:0 0 10px 2px;color:#c00;border-right-color:transparent;border-top-color:transparent;animation:spin-right 1s linear infinite normal;animation-delay:0s;margin:0 5px 0 0;display:inline-block}#formattingMsg .x-loading:before{display:block;width:8px;height:8px;margin:1px;border:2px solid #f00;content:" ";border-radius:50%;border-left-color:transparent;border-bottom-color:transparent}@keyframes spin-right{from{transform:rotate(0deg);opacity:.2}50%{transform:rotate(180deg);opacity:1.0}to{transform:rotate(360deg);opacity:.2}}</style>',
  20. '<div class="mod-json mod-contentscript"><div class="rst-item">',
  21. '<div class="jf-sort" style="display:none"><span class="x-stitle">JSON排序:</span><label for="sort_null">默认</label><input type="radio" name="jsonsort" id="sort_null" value="0" checked="checked"><label for="sort_asc">升序</label><input type="radio" name="jsonsort" id="sort_asc" value="1"><label for="sort_desc">降序</label><input type="radio" name="jsonsort" id="sort_desc" value="-1"></div>',
  22. '<div id="formattingMsg"><span class="x-loading"></span>格式化中...</div>',
  23. '<div id="jfCallbackName_start" class="callback-name"></div>',
  24. '<div id="jfContent"></div>',
  25. '<pre id="jfContent_pre"></pre>',
  26. '<div id="jfCallbackName_end" class="callback-name"></div>',
  27. '</div></div>'
  28. ].join('');
  29. let _loadCss = function () {
  30. let cssUrl = chrome.extension.getURL('json-format/without-ui.css');
  31. $('<link id="_fehelper_fcp_css_" href="' + cssUrl + '" rel="stylesheet" type="text/css" />').appendTo('head');
  32. };
  33. /**
  34. * 从页面提取JSON文本
  35. * @returns {string}
  36. * @private
  37. */
  38. let _getJsonText = function () {
  39. let pre = $('body>pre:eq(0)')[0] || {textContent: ""};
  40. let source = pre.textContent.trim();
  41. if (!source) {
  42. source = (document.body.textContent || '').trim()
  43. }
  44. if (!source) {
  45. return false;
  46. }
  47. // 如果body的内容还包含HTML标签,肯定不是合法的json了
  48. // 如果是合法的json,也只可能有一个text节点
  49. let nodes = document.body.childNodes;
  50. let newSource = '';
  51. for (let i = 0, len = nodes.length; i < len; i++) {
  52. if (nodes[i].nodeType === Node.TEXT_NODE) {
  53. newSource += nodes[i].textContent;
  54. } else if (nodes[i].nodeType === Node.ELEMENT_NODE) {
  55. let tagName = nodes[i].tagName.toLowerCase();
  56. let html = (nodes[i].textContent || '').trim();
  57. // 如果是pre标签,则看内容是不是和source一样,一样则continue
  58. if (tagName === 'pre' && html === source) {
  59. } else if ((nodes[i].offsetWidth === 0 || nodes[i].offsetHeight === 0 || !html) && ['script', 'link'].indexOf(tagName) === -1) {
  60. // 如果用户安装迅雷或者其他的插件,也回破坏页面结构,需要兼容一下
  61. } else {
  62. return false;
  63. }
  64. } else {
  65. return false;
  66. }
  67. }
  68. return (newSource || '').trim() || source;
  69. };
  70. /**
  71. * 此方法用于将Unicode码解码为正常字符串
  72. * @param {Object} text
  73. */
  74. let _uniDecode = function (text) {
  75. try {
  76. text = decodeURIComponent(text);
  77. } catch (e) {
  78. }
  79. text = text.replace(/(\\)?\\u/gi, "%u").replace('%u0025', '%25');
  80. text = unescape(text.toString().replace(/%2B/g, "+"));
  81. let matches = text.match(/(%u00([0-9A-F]{2}))/gi);
  82. if (matches) {
  83. for (let matchid = 0; matchid < matches.length; matchid++) {
  84. let code = matches[matchid].substring(1, 3);
  85. let x = Number("0x" + code);
  86. if (x >= 128) {
  87. text = text.replace(matches[matchid], code);
  88. }
  89. }
  90. }
  91. text = unescape(text.toString().replace(/%2B/g, "+"));
  92. return text;
  93. };
  94. /**
  95. * 获取一个JSON的所有Key数量
  96. * @param json
  97. * @returns {number}
  98. * @private
  99. */
  100. let _getAllKeysCount = function (json) {
  101. let count = 0;
  102. if (typeof json === 'object') {
  103. let keys = Object.keys(json);
  104. count += keys.length;
  105. keys.forEach(key => {
  106. if (json[key] && typeof json[key] === 'object') {
  107. count += _getAllKeysCount(json[key]);
  108. }
  109. });
  110. }
  111. return count;
  112. };
  113. /**
  114. * 执行format操作
  115. * @private
  116. */
  117. let _format = function (options) {
  118. let source = _getJsonText();
  119. if (!source) {
  120. return;
  121. }
  122. if (options && options['AUTO_TEXT_DECODE']) {
  123. source = _uniDecode(source);
  124. }
  125. // 下面校验给定字符串是否为一个合法的json
  126. try {
  127. // 再看看是不是jsonp的格式
  128. let reg = /^([\w\.]+)\(\s*([\s\S]*)\s*\)$/gm;
  129. let reTry = /^(try\s*\{\s*)?/g;
  130. let reCatch = /([;\s]*\}\s*catch\s*\(\s*\S+\s*\)\s*\{([\s\S])*\})?[;\s]*$/g;
  131. // 检测是否有try-catch包裹
  132. let sourceReplaced = source.replace(reTry, function () {
  133. fnTry = fnTry ? fnTry : arguments[1];
  134. return '';
  135. }).replace(reCatch, function () {
  136. fnCatch = fnCatch ? fnCatch : arguments[1];
  137. return '';
  138. }).trim();
  139. let matches = reg.exec(sourceReplaced);
  140. if (matches != null && (fnTry && fnCatch || !fnTry && !fnCatch)) {
  141. funcName = matches[1];
  142. source = matches[2];
  143. } else {
  144. reg = /^([\{\[])/;
  145. if (!reg.test(source)) {
  146. return;
  147. }
  148. }
  149. // 这里可能会throw exception
  150. jsonObj = JSON.parse(source);
  151. } catch (ex) {
  152. // new Function的方式,能自动给key补全双引号,但是不支持bigint,所以是下下策,放在try-catch里搞
  153. try {
  154. jsonObj = new Function("return " + source)();
  155. } catch (exx) {
  156. try {
  157. // 再给你一次机会,是不是下面这种情况: "{\"ret\":\"0\", \"msg\":\"ok\"}"
  158. jsonObj = new Function("return '" + source + "'")();
  159. if (typeof jsonObj === 'string') {
  160. // 最后给你一次机会,是个字符串,老夫给你再转一次
  161. jsonObj = new Function("return " + jsonObj)();
  162. }
  163. } catch (exxx) {
  164. return;
  165. }
  166. }
  167. }
  168. // 是json格式,可以进行JSON自动格式化
  169. if (jsonObj != null && typeof jsonObj === "object") {
  170. try {
  171. // 要尽量保证格式化的东西一定是一个json,所以需要把内容进行JSON.stringify处理
  172. source = JSON.stringify(jsonObj);
  173. } catch (ex) {
  174. // 通过JSON反解不出来的,一定有问题
  175. return;
  176. }
  177. // JSON的所有key不能超过预设的值,比如 10000 个,要不然自动格式化会比较卡
  178. if (options && options['MAX_JSON_KEYS_NUMBER']) {
  179. let keysCount = _getAllKeysCount(jsonObj);
  180. if (keysCount > options['MAX_JSON_KEYS_NUMBER']) {
  181. let msg = '当前JSON共 <b style="color:red">' + keysCount + '</b> 个Key,大于预设值' + options['MAX_JSON_KEYS_NUMBER'] + ',已取消自动格式化;可到FeHelper设置页调整此配置!';
  182. return toast(msg);
  183. }
  184. }
  185. _loadCss();
  186. $('body').html(_htmlFragment);
  187. originalJsonStr = source;
  188. // 获取上次记录的排序方式
  189. curSortType = parseInt(localStorage.getItem(JSON_SORT_TYPE_KEY) || 0);
  190. _didFormat(curSortType);
  191. // 初始化
  192. $('[name=jsonsort][value=' + curSortType + ']').attr('checked', 1);
  193. $('.jf-sort').slideDown(1000);
  194. _bindSortEvent();
  195. }
  196. };
  197. let _didFormat = function (sortType) {
  198. sortType = sortType || 0;
  199. let source = originalJsonStr;
  200. if (sortType !== 0) {
  201. let jsonObj = Tarp.require('../json-format/jsonabc').sortObj(JSON.parse(originalJsonStr), parseInt(sortType), true);
  202. source = JSON.stringify(jsonObj);
  203. }
  204. // 格式化
  205. Tarp.require('../json-format/format-lib').format(source);
  206. // 如果是JSONP格式的,需要把方法名也显示出来
  207. if (funcName != null) {
  208. if (fnTry && fnCatch) {
  209. $('#jfCallbackName_start').html('<pre style="padding:0">' + fnTry + '</pre>' + funcName + '(');
  210. $('#jfCallbackName_end').html(')<br><pre style="padding:0">' + fnCatch + '</pre>');
  211. } else {
  212. $('#jfCallbackName_start').html(funcName + '(');
  213. $('#jfCallbackName_end').html(')');
  214. }
  215. }
  216. localStorage.setItem(JSON_SORT_TYPE_KEY, sortType);
  217. };
  218. let _bindSortEvent = function () {
  219. $('[name=jsonsort]').click(function (e) {
  220. let sortType = parseInt(this.value);
  221. if (sortType !== curSortType) {
  222. _didFormat(sortType);
  223. curSortType = sortType;
  224. }
  225. });
  226. };
  227. return {
  228. format: _format
  229. };
  230. })();