Code.js 11 KB

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