import Utils from "../Utils.js"; import jsesc from "jsesc"; /** * String utility operations. * * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 * * @namespace */ const StrUtils = { /** * @constant * @default */ CASE_SCOPE: ["All", "Word", "Sentence", "Paragraph"], /** * To Upper case operation. * * @param {string} input * @param {Object[]} args * @returns {string} */ runUpper: function (input, args) { const scope = args[0]; switch (scope) { case "Word": return input.replace(/(\b\w)/gi, function(m) { return m.toUpperCase(); }); case "Sentence": return input.replace(/(?:\.|^)\s*(\b\w)/gi, function(m) { return m.toUpperCase(); }); case "Paragraph": return input.replace(/(?:\n|^)\s*(\b\w)/gi, function(m) { return m.toUpperCase(); }); case "All": /* falls through */ default: return input.toUpperCase(); } }, /** * To Upper case operation. * * @param {string} input * @param {Object[]} args * @returns {string} */ runLower: function (input, args) { return input.toLowerCase(); }, /** * @constant * @default */ SPLIT_DELIM_OPTIONS: [ {name: "Comma", value: ","}, {name: "Space", value: " "}, {name: "Line feed", value: "\\n"}, {name: "CRLF", value: "\\r\\n"}, {name: "Semi-colon", value: ";"}, {name: "Colon", value: ":"}, {name: "Nothing (separate chars)", value: ""} ], /** * @constant * @default */ JOIN_DELIM_OPTIONS: [ {name: "Line feed", value: "\\n"}, {name: "CRLF", value: "\\r\\n"}, {name: "Space", value: " "}, {name: "Comma", value: ","}, {name: "Semi-colon", value: ";"}, {name: "Colon", value: ":"}, {name: "Nothing (join chars)", value: ""} ], /** * Split operation. * * @param {string} input * @param {Object[]} args * @returns {string} */ runSplit: function(input, args) { let splitDelim = args[0], joinDelim = args[1], sections = input.split(splitDelim); return sections.join(joinDelim); }, /** * @constant * @default */ DELIMITER_OPTIONS: ["Line feed", "CRLF", "Space", "Comma", "Semi-colon", "Colon", "Nothing (separate chars)"], /** * Filter operation. * * @author Mikescher (https://github.com/Mikescher | https://mikescher.com) * @param {string} input * @param {Object[]} args * @returns {string} */ runFilter: function(input, args) { let delim = Utils.charRep[args[0]], regex, reverse = args[2]; try { regex = new RegExp(args[1]); } catch (err) { return "Invalid regex. Details: " + err.message; } const regexFilter = function(value) { return reverse ^ regex.test(value); }; return input.split(delim).filter(regexFilter).join(delim); }, /** * @constant * @default */ OFF_CHK_SAMPLE_DELIMITER: "\\n\\n", /** * Offset checker operation. * * @param {string} input * @param {Object[]} args * @returns {html} */ runOffsetChecker: function(input, args) { let sampleDelim = args[0], samples = input.split(sampleDelim), outputs = new Array(samples.length), i = 0, s = 0, match = false, inMatch = false, chr; if (!samples || samples.length < 2) { return "Not enough samples, perhaps you need to modify the sample delimiter or add more data?"; } // Initialise output strings outputs.fill("", 0, samples.length); // Loop through each character in the first sample for (i = 0; i < samples[0].length; i++) { chr = samples[0][i]; match = false; // Loop through each sample to see if the chars are the same for (s = 1; s < samples.length; s++) { if (samples[s][i] !== chr) { match = false; break; } match = true; } // Write output for each sample for (s = 0; s < samples.length; s++) { if (samples[s].length <= i) { if (inMatch) outputs[s] += ""; if (s === samples.length - 1) inMatch = false; continue; } if (match && !inMatch) { outputs[s] += "" + Utils.escapeHtml(samples[s][i]); if (samples[s].length === i + 1) outputs[s] += ""; if (s === samples.length - 1) inMatch = true; } else if (!match && inMatch) { outputs[s] += "" + Utils.escapeHtml(samples[s][i]); if (s === samples.length - 1) inMatch = false; } else { outputs[s] += Utils.escapeHtml(samples[s][i]); if (inMatch && samples[s].length === i + 1) { outputs[s] += ""; if (samples[s].length - 1 !== i) inMatch = false; } } if (samples[0].length - 1 === i) { if (inMatch) outputs[s] += ""; outputs[s] += Utils.escapeHtml(samples[s].substring(i + 1)); } } } return outputs.join(sampleDelim); }, /** * @constant * @default */ QUOTE_TYPES: ["Single", "Double", "Backtick"], /** * @constant * @default */ ESCAPE_LEVEL: ["Special chars", "Everything", "Minimal"], /** * Escape string operation. * * @author Vel0x [dalemy@microsoft.com] * @author n1474335 [n1474335@gmail.com] * * @param {string} input * @param {Object[]} args * @returns {string} * * @example * StrUtils.runEscape("Don't do that", []) * > "Don\'t do that" * StrUtils.runEscape(`Hello * World`, []) * > "Hello\nWorld" */ runEscape: function(input, args) { const level = args[0], quotes = args[1], jsonCompat = args[2], es6Compat = args[3], lowercaseHex = !args[4]; return jsesc(input, { quotes: quotes.toLowerCase(), es6: es6Compat, escapeEverything: level === "Everything", minimal: level === "Minimal", json: jsonCompat, lowercaseHex: lowercaseHex, }); }, /** * Unescape string operation. * * @author Vel0x [dalemy@microsoft.com] * @author n1474335 [n1474335@gmail.com] * * @param {string} input * @param {Object[]} args * @returns {string} * * @example * StrUtils.runUnescape("Don\'t do that", []) * > "Don't do that" * StrUtils.runUnescape("Hello\nWorld", []) * > `Hello * World` */ runUnescape: function(input, args) { return Utils.parseEscapedChars(input); }, /** * Head lines operation. * * @param {string} input * @param {Object[]} args * @returns {string} */ runHead: function(input, args) { let delimiter = args[0], number = args[1]; delimiter = Utils.charRep[delimiter]; const splitInput = input.split(delimiter); return splitInput .filter((line, lineIndex) => { lineIndex += 1; if (number < 0) { return lineIndex <= splitInput.length + number; } else { return lineIndex <= number; } }) .join(delimiter); }, /** * Tail lines operation. * * @param {string} input * @param {Object[]} args * @returns {string} */ runTail: function(input, args) { let delimiter = args[0], number = args[1]; delimiter = Utils.charRep[delimiter]; const splitInput = input.split(delimiter); return splitInput .filter((line, lineIndex) => { lineIndex += 1; if (number < 0) { return lineIndex > -number; } else { return lineIndex > splitInput.length - number; } }) .join(delimiter); }, /** * @constant * @default */ HAMMING_DELIM: "\\n\\n", /** * @constant * @default */ HAMMING_INPUT_TYPE: ["Raw string", "Hex"], /** * @constant * @default */ HAMMING_UNIT: ["Byte", "Bit"], /** * Hamming Distance operation. * * @author GCHQ Contributor [2] * * @param {string} input * @param {Object[]} args * @returns {string} */ runHamming: function(input, args) { const delim = args[0], byByte = args[1] === "Byte", inputType = args[2], samples = input.split(delim); if (samples.length !== 2) { return "Error: You can only calculae the edit distance between 2 strings. Please ensure exactly two inputs are provided, separated by the specified delimiter."; } if (samples[0].length !== samples[1].length) { return "Error: Both inputs must be of the same length."; } if (inputType === "Hex") { samples[0] = Utils.fromHex(samples[0]); samples[1] = Utils.fromHex(samples[1]); } else { samples[0] = Utils.strToByteArray(samples[0]); samples[1] = Utils.strToByteArray(samples[1]); } let dist = 0; for (let i = 0; i < samples[0].length; i++) { const lhs = samples[0][i], rhs = samples[1][i]; if (byByte && lhs !== rhs) { dist++; } else if (!byByte) { let xord = lhs ^ rhs; while (xord) { dist++; xord &= xord - 1; } } } return dist.toString(); }, }; export default StrUtils;