/** * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; import * as JsDiff from "diff"; import OperationError from "../errors/OperationError.mjs"; /** * Diff operation */ class Diff extends Operation { /** * Diff constructor */ constructor() { super(); this.name = "Diff"; this.module = "Diff"; this.description = "Compares two inputs (separated by the specified delimiter) and highlights the differences between them."; this.infoURL = "https://wikipedia.org/wiki/File_comparison"; this.inputType = "string"; this.outputType = "html"; this.args = [ { "name": "Sample delimiter", "type": "binaryString", "value": "\\n\\n" }, { "name": "Diff by", "type": "option", "value": ["Character", "Word", "Line", "Sentence", "CSS", "JSON"] }, { "name": "Show added", "type": "boolean", "value": true }, { "name": "Show removed", "type": "boolean", "value": true }, { "name": "Show subtraction", "type": "boolean", "value": false }, { "name": "Ignore whitespace", "type": "boolean", "value": false, "hint": "Relevant for word and line" }, { "name": "Show +/- notation", "hint": "Works only in Line diff mode.", "type": "boolean", "value": false } ]; } /** * @param {string} input * @param {Object[]} args * @returns {html} */ run(input, args) { const [ sampleDelim, diffBy, showAdded, showRemoved, showSubtraction, ignoreWhitespace, showNotation ] = args, samples = input.split(sampleDelim); let output = "", diff; // Node and Webpack load modules slightly differently const jsdiff = JsDiff.default ? JsDiff.default : JsDiff; if (!samples || samples.length !== 2) { throw new OperationError("Incorrect number of samples, perhaps you need to modify the sample delimiter or add more samples?"); } // +/- notation only works properly with Line diff mode // For other modes, it falls back to HTML format and this option is ignored (see hint). const useNotation = showNotation && diffBy === "Line"; if (useNotation) { const patch = jsdiff.createTwoFilesPatch("original", "modified", samples[0], samples[1], "", "", { context: Infinity, ignoreWhitespace: ignoreWhitespace, }); const lines = patch.split("\n"); for (let i = 0; i < lines.length; i++) { const line = lines[i]; if (line.startsWith("@@")) { if (showSubtraction) output += line + "\n"; } else if (line.startsWith("+") && !line.startsWith("+++")) { if (showAdded) output += line + "\n"; } else if (line.startsWith("-") && !line.startsWith("---")) { if (showRemoved) output += line + "\n"; } else if (line.startsWith("===") || line.startsWith("---") || line.startsWith("+++") || line.startsWith("\\")) { continue; } else if (line.length > 0 && !showSubtraction) { output += line.substring(1) + "\n"; } } return output.slice(0, -1); } switch (diffBy) { case "Character": diff = jsdiff.diffChars(samples[0], samples[1]); break; case "Word": if (ignoreWhitespace) { diff = jsdiff.diffWords(samples[0], samples[1]); } else { diff = jsdiff.diffWordsWithSpace(samples[0], samples[1]); } break; case "Line": if (ignoreWhitespace) { diff = jsdiff.diffTrimmedLines(samples[0], samples[1]); } else { diff = jsdiff.diffLines(samples[0], samples[1]); } break; case "Sentence": diff = jsdiff.diffSentences(samples[0], samples[1]); break; case "CSS": diff = jsdiff.diffCss(samples[0], samples[1]); break; case "JSON": diff = jsdiff.diffJson(samples[0], samples[1]); break; default: throw new OperationError("Invalid 'Diff by' option."); } for (let i = 0; i < diff.length; i++) { if (diff[i].added) { if (showAdded) output += "" + Utils.escapeHtml(diff[i].value) + ""; } else if (diff[i].removed) { if (showRemoved) output += "" + Utils.escapeHtml(diff[i].value) + ""; } else if (!showSubtraction) { output += Utils.escapeHtml(diff[i].value); } } return output; } } export default Diff;