Code.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431
  1. /* globals prettyPrintOne, vkbeautify, xpath */
  2. /**
  3. * Code operations.
  4. *
  5. * @author n1474335 [n1474335@gmail.com]
  6. * @copyright Crown Copyright 2016
  7. * @license Apache-2.0
  8. *
  9. * @namespace
  10. */
  11. var Code = {
  12. /**
  13. * @constant
  14. * @default
  15. */
  16. LANGUAGES: ["default-code", "default-markup", "bash", "bsh", "c", "cc", "coffee", "cpp", "cs", "csh", "cv", "cxx", "cyc", "htm", "html", "in.tag", "java", "javascript", "js", "json", "m", "mxml", "perl", "pl", "pm", "py", "python", "rb", "rc", "rs", "ruby", "rust", "sh", "uq.val", "xhtml", "xml", "xsl"],
  17. /**
  18. * @constant
  19. * @default
  20. */
  21. LINE_NUMS: false,
  22. /**
  23. * Syntax highlighter operation.
  24. *
  25. * @param {string} input
  26. * @param {Object[]} args
  27. * @returns {html}
  28. */
  29. runSyntaxHighlight: function(input, args) {
  30. var language = args[0],
  31. lineNums = args[1];
  32. return "<code class='prettyprint'>" + prettyPrintOne(Utils.escapeHtml(input), language, lineNums) + "</code>";
  33. },
  34. /**
  35. * @constant
  36. * @default
  37. */
  38. BEAUTIFY_INDENT: "\\t",
  39. /**
  40. * XML Beautify operation.
  41. *
  42. * @param {string} input
  43. * @param {Object[]} args
  44. * @returns {string}
  45. */
  46. runXmlBeautify: function(input, args) {
  47. var indentStr = args[0];
  48. return vkbeautify.xml(input, indentStr);
  49. },
  50. /**
  51. * JSON Beautify operation.
  52. *
  53. * @param {string} input
  54. * @param {Object[]} args
  55. * @returns {string}
  56. */
  57. runJsonBeautify: function(input, args) {
  58. var indentStr = args[0];
  59. if (!input) return "";
  60. return vkbeautify.json(input, indentStr);
  61. },
  62. /**
  63. * CSS Beautify operation.
  64. *
  65. * @param {string} input
  66. * @param {Object[]} args
  67. * @returns {string}
  68. */
  69. runCssBeautify: function(input, args) {
  70. var indentStr = args[0];
  71. return vkbeautify.css(input, indentStr);
  72. },
  73. /**
  74. * SQL Beautify operation.
  75. *
  76. * @param {string} input
  77. * @param {Object[]} args
  78. * @returns {string}
  79. */
  80. runSqlBeautify: function(input, args) {
  81. var indentStr = args[0];
  82. return vkbeautify.sql(input, indentStr);
  83. },
  84. /**
  85. * @constant
  86. * @default
  87. */
  88. PRESERVE_COMMENTS: false,
  89. /**
  90. * XML Minify operation.
  91. *
  92. * @param {string} input
  93. * @param {Object[]} args
  94. * @returns {string}
  95. */
  96. runXmlMinify: function(input, args) {
  97. var preserveComments = args[0];
  98. return vkbeautify.xmlmin(input, preserveComments);
  99. },
  100. /**
  101. * JSON Minify operation.
  102. *
  103. * @param {string} input
  104. * @param {Object[]} args
  105. * @returns {string}
  106. */
  107. runJsonMinify: function(input, args) {
  108. if (!input) return "";
  109. return vkbeautify.jsonmin(input);
  110. },
  111. /**
  112. * CSS Minify operation.
  113. *
  114. * @param {string} input
  115. * @param {Object[]} args
  116. * @returns {string}
  117. */
  118. runCssMinify: function(input, args) {
  119. var preserveComments = args[0];
  120. return vkbeautify.cssmin(input, preserveComments);
  121. },
  122. /**
  123. * SQL Minify operation.
  124. *
  125. * @param {string} input
  126. * @param {Object[]} args
  127. * @returns {string}
  128. */
  129. runSqlMinify: function(input, args) {
  130. return vkbeautify.sqlmin(input);
  131. },
  132. /**
  133. * Generic Code Beautify operation.
  134. *
  135. * Yeeeaaah...
  136. *
  137. * I'm not proud of this code, but seriously, try writing a generic lexer and parser that
  138. * correctly generates an AST for multiple different languages. I have tried, and I can tell
  139. * you it's pretty much impossible.
  140. *
  141. * This basically works. That'll have to be good enough. It's not meant to produce working code,
  142. * just slightly more readable code.
  143. *
  144. * Things that don't work:
  145. * - For loop formatting
  146. * - Do-While loop formatting
  147. * - Switch/Case indentation
  148. * - Bit shift operators
  149. *
  150. * @author n1474335 [n1474335@gmail.com]
  151. * @param {string} input
  152. * @param {Object[]} args
  153. * @returns {string}
  154. */
  155. runGenericBeautify: function(input, args) {
  156. var code = input,
  157. t = 0,
  158. preservedTokens = [],
  159. m;
  160. // Remove strings
  161. var sstrings = /'([^'\\]|\\.)*'/g;
  162. while ((m = sstrings.exec(code))) {
  163. code = preserveToken(code, m, t++);
  164. sstrings.lastIndex = m.index;
  165. }
  166. var dstrings = /"([^"\\]|\\.)*"/g;
  167. while ((m = dstrings.exec(code))) {
  168. code = preserveToken(code, m, t++);
  169. dstrings.lastIndex = m.index;
  170. }
  171. // Remove comments
  172. var scomments = /\/\/[^\n\r]*/g;
  173. while ((m = scomments.exec(code))) {
  174. code = preserveToken(code, m, t++);
  175. scomments.lastIndex = m.index;
  176. }
  177. var mcomments = /\/\*[\s\S]*?\*\//gm;
  178. while ((m = mcomments.exec(code))) {
  179. code = preserveToken(code, m, t++);
  180. mcomments.lastIndex = m.index;
  181. }
  182. var hcomments = /(^|\n)#[^\n\r#]+/g;
  183. while ((m = hcomments.exec(code))) {
  184. code = preserveToken(code, m, t++);
  185. hcomments.lastIndex = m.index;
  186. }
  187. // Remove regexes
  188. var regexes = /\/.*?[^\\]\/[gim]{0,3}/gi;
  189. while ((m = regexes.exec(code))) {
  190. code = preserveToken(code, m, t++);
  191. regexes.lastIndex = m.index;
  192. }
  193. // Create newlines after ;
  194. code = code.replace(/;/g, ";\n");
  195. // Create newlines after { and around }
  196. code = code.replace(/{/g, "{\n");
  197. code = code.replace(/}/g, "\n}\n");
  198. // Remove carriage returns
  199. code = code.replace(/\r/g, "");
  200. // Remove all indentation
  201. code = code.replace(/^\s+/g, "");
  202. code = code.replace(/\n\s+/g, "\n");
  203. // Remove trailing spaces
  204. code = code.replace(/\s*$/g, "");
  205. // Remove newlines before {
  206. code = code.replace(/\n{/g, "{");
  207. // Indent
  208. var i = 0,
  209. level = 0;
  210. while (i < code.length) {
  211. switch (code[i]) {
  212. case "{":
  213. level++;
  214. break;
  215. case "\n":
  216. if (i+1 >= code.length) break;
  217. if (code[i+1] === "}") level--;
  218. var indent = (level >= 0) ? Array(level*4+1).join(" ") : "";
  219. code = code.substring(0, i+1) + indent + code.substring(i+1);
  220. if (level > 0) i += level*4;
  221. break;
  222. }
  223. i++;
  224. }
  225. // Add strategic spaces
  226. code = code.replace(/\s*([!<>=+-/*]?)=\s*/g, " $1= ");
  227. code = code.replace(/\s*<([=]?)\s*/g, " <$1 ");
  228. code = code.replace(/\s*>([=]?)\s*/g, " >$1 ");
  229. code = code.replace(/([^+])\+([^+=])/g, "$1 + $2");
  230. code = code.replace(/([^-])-([^-=])/g, "$1 - $2");
  231. code = code.replace(/([^*])\*([^*=])/g, "$1 * $2");
  232. code = code.replace(/([^/])\/([^/=])/g, "$1 / $2");
  233. code = code.replace(/\s*,\s*/g, ", ");
  234. code = code.replace(/\s*{/g, " {");
  235. code = code.replace(/}\n/g, "}\n\n");
  236. // Just... don't look at this
  237. code = code.replace(/(if|for|while|with|elif|elseif)\s*\(([^\n]*)\)\s*\n([^{])/gim, "$1 ($2)\n $3");
  238. code = code.replace(/(if|for|while|with|elif|elseif)\s*\(([^\n]*)\)([^{])/gim, "$1 ($2) $3");
  239. code = code.replace(/else\s*\n([^{])/gim, "else\n $1");
  240. code = code.replace(/else\s+([^{])/gim, "else $1");
  241. // Remove strategic spaces
  242. code = code.replace(/\s+;/g, ";");
  243. code = code.replace(/\{\s+\}/g, "{}");
  244. code = code.replace(/\[\s+\]/g, "[]");
  245. code = code.replace(/}\s*(else|catch|except|finally|elif|elseif|else if)/gi, "} $1");
  246. // Replace preserved tokens
  247. var ptokens = /###preservedToken(\d+)###/g;
  248. while ((m = ptokens.exec(code))) {
  249. var ti = parseInt(m[1], 10);
  250. code = code.substring(0, m.index) + preservedTokens[ti] + code.substring(m.index + m[0].length);
  251. ptokens.lastIndex = m.index;
  252. }
  253. return code;
  254. /**
  255. * Replaces a matched token with a placeholder value.
  256. */
  257. function preserveToken(str, match, t) {
  258. preservedTokens[t] = match[0];
  259. return str.substring(0, match.index) +
  260. "###preservedToken" + t + "###" +
  261. str.substring(match.index + match[0].length);
  262. }
  263. },
  264. /**
  265. * @constant
  266. * @default
  267. */
  268. XPATH_INITIAL: "",
  269. /**
  270. * @constant
  271. * @default
  272. */
  273. XPATH_DELIMITER: "\\n",
  274. /**
  275. * XPath expression operation.
  276. *
  277. * @author Mikescher (https://github.com/Mikescher | https://mikescher.com)
  278. * @param {string} input
  279. * @param {Object[]} args
  280. * @returns {string}
  281. */
  282. runXpath:function(input, args) {
  283. var query = args[0],
  284. delimiter = args[1];
  285. var xml;
  286. try {
  287. xml = $.parseXML(input);
  288. } catch (err) {
  289. return "Invalid input XML.";
  290. }
  291. var result;
  292. try {
  293. result = xpath.evaluate(xml, query);
  294. } catch (err) {
  295. return "Invalid XPath. Details:\n" + err.message;
  296. }
  297. var serializer = new XMLSerializer();
  298. var nodeToString = function(node) {
  299. switch (node.nodeType) {
  300. case Node.ELEMENT_NODE: return serializer.serializeToString(node);
  301. case Node.ATTRIBUTE_NODE: return node.value;
  302. case Node.COMMENT_NODE: return node.data;
  303. case Node.DOCUMENT_NODE: return serializer.serializeToString(node);
  304. default: throw new Error("Unknown Node Type: " + node.nodeType);
  305. }
  306. };
  307. return Object.keys(result).map(function(key) {
  308. return result[key];
  309. }).slice(0, -1) // all values except last (length)
  310. .map(nodeToString)
  311. .join(delimiter);
  312. },
  313. /**
  314. * @constant
  315. * @default
  316. */
  317. CSS_SELECTOR_INITIAL: "",
  318. /**
  319. * @constant
  320. * @default
  321. */
  322. CSS_QUERY_DELIMITER: "\\n",
  323. /**
  324. * CSS selector operation.
  325. *
  326. * @author Mikescher (https://github.com/Mikescher | https://mikescher.com)
  327. * @author n1474335 [n1474335@gmail.com]
  328. * @param {string} input
  329. * @param {Object[]} args
  330. * @returns {string}
  331. */
  332. runCSSQuery: function(input, args) {
  333. var query = args[0],
  334. delimiter = args[1],
  335. parser = new DOMParser(),
  336. html,
  337. result;
  338. if (!query.length || !input.length) {
  339. return "";
  340. }
  341. try {
  342. html = parser.parseFromString(input, "text/html");
  343. } catch (err) {
  344. return "Invalid input HTML.";
  345. }
  346. try {
  347. result = html.querySelectorAll(query);
  348. } catch (err) {
  349. return "Invalid CSS Selector. Details:\n" + err.message;
  350. }
  351. var nodeToString = function(node) {
  352. switch (node.nodeType) {
  353. case Node.ELEMENT_NODE: return node.outerHTML;
  354. case Node.ATTRIBUTE_NODE: return node.value;
  355. case Node.COMMENT_NODE: return node.data;
  356. case Node.TEXT_NODE: return node.wholeText;
  357. case Node.DOCUMENT_NODE: return node.outerHTML;
  358. default: throw new Error("Unknown Node Type: " + node.nodeType);
  359. }
  360. };
  361. return Array.apply(null, Array(result.length))
  362. .map(function(_, i) {
  363. return result[i];
  364. })
  365. .map(nodeToString)
  366. .join(delimiter);
  367. },
  368. };