diff --git a/package.json b/package.json
index 119d915b93..b7451ecec5 100644
--- a/package.json
+++ b/package.json
@@ -117,6 +117,7 @@
"start": "grunt dev",
"build": "grunt prod",
"test": "grunt test",
- "docs": "grunt docs"
+ "docs": "grunt docs",
+ "lint": "grunt"
}
}
diff --git a/src/core/Recipe.mjs b/src/core/Recipe.mjs
index 1479215715..006e431c91 100755
--- a/src/core/Recipe.mjs
+++ b/src/core/Recipe.mjs
@@ -189,9 +189,11 @@ class Recipe {
}
// Present the results of the final operation
- // TODO try/catch
- output = await lastRunOp.present(output);
- dish.set(output, lastRunOp.presentType);
+ if (lastRunOp) {
+ // TODO try/catch
+ output = await lastRunOp.present(output);
+ dish.set(output, lastRunOp.presentType);
+ }
log.debug("Recipe complete");
return this.opList.length;
diff --git a/src/core/config/Categories.js b/src/core/config/Categories.js
index b74e932a5b..70e4ea4b29 100755
--- a/src/core/config/Categories.js
+++ b/src/core/config/Categories.js
@@ -119,6 +119,12 @@ const Categories = [
{
name: "Arithmetic / Logic",
ops: [
+ "Set Union",
+ "Set Intersection",
+ "Set Difference",
+ "Symmetric Difference",
+ "Cartesian Product",
+ "Power Set",
// "XOR",
// "XOR Brute Force",
// "OR",
diff --git a/src/core/config/OperationConfig.json b/src/core/config/OperationConfig.json
index 2f18363dab..ed42bcbb7c 100644
--- a/src/core/config/OperationConfig.json
+++ b/src/core/config/OperationConfig.json
@@ -1,4 +1,23 @@
{
+ "Cartesian Product": {
+ "module": "Default",
+ "description": "Get the cartesian product of two sets",
+ "inputType": "string",
+ "outputType": "string",
+ "flowControl": false,
+ "args": [
+ {
+ "name": "Sample delimiter",
+ "type": "binaryString",
+ "value": "\\n\\n"
+ },
+ {
+ "name": "Item delimiter",
+ "type": "binaryString",
+ "value": ","
+ }
+ ]
+ },
"From Base32": {
"module": "Default",
"description": "Base32 is a notation for encoding arbitrary byte data using a restricted set of symbols that can be conveniently used by humans and processed by computers. It uses a smaller set of characters than Base64, usually the uppercase alphabet and the numbers 2 to 7.",
@@ -155,6 +174,20 @@
}
]
},
+ "Power Set": {
+ "module": "Default",
+ "description": "Generate the power set of a set",
+ "inputType": "string",
+ "outputType": "string",
+ "flowControl": false,
+ "args": [
+ {
+ "name": "Item delimiter",
+ "type": "binaryString",
+ "value": ","
+ }
+ ]
+ },
"ROT13": {
"module": "Default",
"description": "A simple caesar substitution cipher which rotates alphabet characters by the specified amount (default 13).",
@@ -286,6 +319,63 @@
}
]
},
+ "Set Difference": {
+ "module": "Default",
+ "description": "Get the Difference of two sets",
+ "inputType": "string",
+ "outputType": "string",
+ "flowControl": false,
+ "args": [
+ {
+ "name": "Sample delimiter",
+ "type": "binaryString",
+ "value": "\\n\\n"
+ },
+ {
+ "name": "Item delimiter",
+ "type": "binaryString",
+ "value": ","
+ }
+ ]
+ },
+ "Set Intersection": {
+ "module": "Default",
+ "description": "Get the intersection of two sets",
+ "inputType": "string",
+ "outputType": "string",
+ "flowControl": false,
+ "args": [
+ {
+ "name": "Sample delimiter",
+ "type": "binaryString",
+ "value": "\\n\\n"
+ },
+ {
+ "name": "Item delimiter",
+ "type": "binaryString",
+ "value": ","
+ }
+ ]
+ },
+ "Set Union": {
+ "module": "Default",
+ "description": "Get the union of two sets",
+ "inputType": "string",
+ "outputType": "string",
+ "flowControl": false,
+ "args": [
+ {
+ "name": "Sample delimiter",
+ "type": "binaryString",
+ "value": "\\n\\n"
+ },
+ {
+ "name": "Item delimiter",
+ "type": "binaryString",
+ "value": ","
+ }
+ ]
+ },
"Show Base64 offsets": {
"module": "Default",
"description": "When a string is within a block of data and the whole block is Base64'd, the string itself could be represented in Base64 in three distinct ways depending on its offset within the block.
This operation shows all possible offsets for a given string so that each possible encoding can be considered.",
@@ -305,6 +395,25 @@
}
]
},
+ "Symmetric Difference": {
+ "module": "Default",
+ "description": "Get the symmetric difference of two sets",
+ "inputType": "string",
+ "outputType": "string",
+ "flowControl": false,
+ "args": [
+ {
+ "name": "Sample delimiter",
+ "type": "binaryString",
+ "value": "\\n\\n"
+ },
+ {
+ "name": "Item delimiter",
+ "type": "binaryString",
+ "value": ","
+ }
+ ]
+ },
"To Base32": {
"module": "Default",
"description": "Base32 is a notation for encoding arbitrary byte data using a restricted set of symbols that can be conveniently used by humans and processed by computers. It uses a smaller set of characters than Base64, usually the uppercase alphabet and the numbers 2 to 7.",
diff --git a/src/core/config/modules/Default.js b/src/core/config/modules/Default.js
new file mode 100644
index 0000000000..e221a25e09
--- /dev/null
+++ b/src/core/config/modules/Default.js
@@ -0,0 +1,197 @@
+import FlowControl from "../../FlowControl.js";
+import Arithmetic from "../../operations/Arithmetic.js";
+import Base from "../../operations/Base.js";
+import Base58 from "../../operations/Base58.js";
+import Base64 from "../../operations/Base64.js";
+import BCD from "../../operations/BCD.js";
+import BitwiseOp from "../../operations/BitwiseOp.js";
+import ByteRepr from "../../operations/ByteRepr.js";
+import Convert from "../../operations/Convert.js";
+import DateTime from "../../operations/DateTime.js";
+import Endian from "../../operations/Endian.js";
+import Entropy from "../../operations/Entropy.js";
+import Filetime from "../../operations/Filetime.js";
+import FileType from "../../operations/FileType.js";
+import Hexdump from "../../operations/Hexdump.js";
+import HTML from "../../operations/HTML.js";
+import MAC from "../../operations/MAC.js";
+import MorseCode from "../../operations/MorseCode.js";
+import MS from "../../operations/MS.js";
+import NetBIOS from "../../operations/NetBIOS.js";
+import Numberwang from "../../operations/Numberwang.js";
+import OS from "../../operations/OS.js";
+import OTP from "../../operations/OTP.js";
+import PHP from "../../operations/PHP.js";
+import QuotedPrintable from "../../operations/QuotedPrintable.js";
+import Rotate from "../../operations/Rotate.js";
+import SeqUtils from "../../operations/SeqUtils.js";
+import StrUtils from "../../operations/StrUtils.js";
+import Tidy from "../../operations/Tidy.js";
+import Unicode from "../../operations/Unicode.js";
+import UUID from "../../operations/UUID.js";
+import XKCD from "../../operations/XKCD.js";
+
+
+/**
+ * Default module.
+ *
+ * The Default module is for operations that are expected to be very commonly used or
+ * do not require any libraries. This module is loaded into the app at compile time.
+ *
+ * Libraries:
+ * - Utils.js
+ * - otp
+ * - crypto
+ * - bignumber.js
+ *
+ * @author n1474335 [n1474335@gmail.com]
+ * @copyright Crown Copyright 2017
+ * @license Apache-2.0
+ */
+const OpModules = typeof self === "undefined" ? {} : self.OpModules || {};
+
+OpModules.Default = {
+ "To Hexdump": Hexdump.runTo,
+ "From Hexdump": Hexdump.runFrom,
+ "To Hex": ByteRepr.runToHex,
+ "From Hex": ByteRepr.runFromHex,
+ "To Octal": ByteRepr.runToOct,
+ "From Octal": ByteRepr.runFromOct,
+ "To Charcode": ByteRepr.runToCharcode,
+ "From Charcode": ByteRepr.runFromCharcode,
+ "To Decimal": ByteRepr.runToDecimal,
+ "From Decimal": ByteRepr.runFromDecimal,
+ "To Binary": ByteRepr.runToBinary,
+ "From Binary": ByteRepr.runFromBinary,
+ "To Hex Content": ByteRepr.runToHexContent,
+ "From Hex Content": ByteRepr.runFromHexContent,
+ "To Base64": Base64.runTo,
+ "From Base64": Base64.runFrom,
+ "Show Base64 offsets": Base64.runOffsets,
+ "To Base32": Base64.runTo32,
+ "From Base32": Base64.runFrom32,
+ "To Base58": Base58.runTo,
+ "From Base58": Base58.runFrom,
+ "To Base": Base.runTo,
+ "From Base": Base.runFrom,
+ "To BCD": BCD.runToBCD,
+ "From BCD": BCD.runFromBCD,
+ "To HTML Entity": HTML.runToEntity,
+ "From HTML Entity": HTML.runFromEntity,
+ "Strip HTML tags": HTML.runStripTags,
+ "Parse colour code": HTML.runParseColourCode,
+ "Unescape Unicode Characters": Unicode.runUnescape,
+ "To Quoted Printable": QuotedPrintable.runTo,
+ "From Quoted Printable": QuotedPrintable.runFrom,
+ "Swap endianness": Endian.runSwapEndianness,
+ "ROT13": Rotate.runRot13,
+ "ROT47": Rotate.runRot47,
+ "Rotate left": Rotate.runRotl,
+ "Rotate right": Rotate.runRotr,
+ "Bit shift left": BitwiseOp.runBitShiftLeft,
+ "Bit shift right": BitwiseOp.runBitShiftRight,
+ "XOR": BitwiseOp.runXor,
+ "XOR Brute Force": BitwiseOp.runXorBrute,
+ "OR": BitwiseOp.runOr,
+ "NOT": BitwiseOp.runNot,
+ "AND": BitwiseOp.runAnd,
+ "ADD": BitwiseOp.runAdd,
+ "SUB": BitwiseOp.runSub,
+ "To Morse Code": MorseCode.runTo,
+ "From Morse Code": MorseCode.runFrom,
+ "Format MAC addresses": MAC.runFormat,
+ "Encode NetBIOS Name": NetBIOS.runEncodeName,
+ "Decode NetBIOS Name": NetBIOS.runDecodeName,
+ "Offset checker": StrUtils.runOffsetChecker,
+ "To Upper case": StrUtils.runUpper,
+ "To Lower case": StrUtils.runLower,
+ "Split": StrUtils.runSplit,
+ "Filter": StrUtils.runFilter,
+ "Escape string": StrUtils.runEscape,
+ "Unescape string": StrUtils.runUnescape,
+ "Head": StrUtils.runHead,
+ "Tail": StrUtils.runTail,
+ "Hamming Distance": StrUtils.runHamming,
+ "Remove whitespace": Tidy.runRemoveWhitespace,
+ "Remove null bytes": Tidy.runRemoveNulls,
+ "Drop bytes": Tidy.runDropBytes,
+ "Take bytes": Tidy.runTakeBytes,
+ "Pad lines": Tidy.runPad,
+ "Reverse": SeqUtils.runReverse,
+ "Sort": SeqUtils.runSort,
+ "Unique": SeqUtils.runUnique,
+ "Count occurrences": SeqUtils.runCount,
+ "Add line numbers": SeqUtils.runAddLineNumbers,
+ "Remove line numbers": SeqUtils.runRemoveLineNumbers,
+ "Expand alphabet range": SeqUtils.runExpandAlphRange,
+ "Convert distance": Convert.runDistance,
+ "Convert area": Convert.runArea,
+ "Convert mass": Convert.runMass,
+ "Convert speed": Convert.runSpeed,
+ "Convert data units": Convert.runDataSize,
+ "Parse UNIX file permissions": OS.runParseUnixPerms,
+ "Parse DateTime": DateTime.runParse,
+ "Translate DateTime Format": DateTime.runTranslateFormat,
+ "From UNIX Timestamp": DateTime.runFromUnixTimestamp,
+ "To UNIX Timestamp": DateTime.runToUnixTimestamp,
+ "Sleep": DateTime.runSleep,
+ "Microsoft Script Decoder": MS.runDecodeScript,
+ "Entropy": Entropy.runEntropy,
+ "Frequency distribution": Entropy.runFreqDistrib,
+ "Chi Square": Entropy.runChiSq,
+ "Detect File Type": FileType.runDetect,
+ "Scan for Embedded Files": FileType.runScanForEmbeddedFiles,
+ "Generate UUID": UUID.runGenerateV4,
+ "Numberwang": Numberwang.run,
+ "Generate TOTP": OTP.runTOTP,
+ "Generate HOTP": OTP.runHOTP,
+ "Fork": FlowControl.runFork,
+ "Merge": FlowControl.runMerge,
+ "Register": FlowControl.runRegister,
+ "Label": FlowControl.runComment,
+ "Jump": FlowControl.runJump,
+ "Conditional Jump": FlowControl.runCondJump,
+ "Return": FlowControl.runReturn,
+ "Comment": FlowControl.runComment,
+ "PHP Deserialize": PHP.runDeserialize,
+ "Sum": Arithmetic.runSum,
+ "Subtract": Arithmetic.runSub,
+ "Multiply": Arithmetic.runMulti,
+ "Divide": Arithmetic.runDiv,
+ "Mean": Arithmetic.runMean,
+ "Median": Arithmetic.runMedian,
+ "Standard Deviation": Arithmetic.runStdDev,
+ "Windows Filetime to UNIX Timestamp": Filetime.runFromFiletimeToUnix,
+ "UNIX Timestamp to Windows Filetime": Filetime.runToFiletimeFromUnix,
+ "XKCD Random Number": XKCD.runRandomNumber,
+
+
+ /*
+ Highlighting functions.
+
+ This is a temporary solution as highlighting should be entirely
+ overhauled at some point.
+ */
+ "From Base64-highlight": Base64.highlightFrom,
+ "From Base64-highlightReverse": Base64.highlightTo,
+ "To Base64-highlight": Base64.highlightTo,
+ "To Base64-highlightReverse": Base64.highlightFrom,
+ "From Hex-highlight": ByteRepr.highlightFrom,
+ "From Hex-highlightReverse": ByteRepr.highlightTo,
+ "To Hex-highlight": ByteRepr.highlightTo,
+ "To Hex-highlightReverse": ByteRepr.highlightFrom,
+ "From Charcode-highlight": ByteRepr.highlightFrom,
+ "From Charcode-highlightReverse": ByteRepr.highlightTo,
+ "To Charcode-highlight": ByteRepr.highlightTo,
+ "To Charcode-highlightReverse": ByteRepr.highlightFrom,
+ "From Binary-highlight": ByteRepr.highlightFromBinary,
+ "From Binary-highlightReverse": ByteRepr.highlightToBinary,
+ "To Binary-highlight": ByteRepr.highlightToBinary,
+ "To Binary-highlightReverse": ByteRepr.highlightFromBinary,
+ "From Hexdump-highlight": Hexdump.highlightFrom,
+ "From Hexdump-highlightReverse": Hexdump.highlightTo,
+ "To Hexdump-highlight": Hexdump.highlightTo,
+ "To Hexdump-highlightReverse": Hexdump.highlightFrom,
+};
+
+export default OpModules;
diff --git a/src/core/config/modules/Default.mjs b/src/core/config/modules/Default.mjs
index 8648aa8aa4..8d1469d782 100644
--- a/src/core/config/modules/Default.mjs
+++ b/src/core/config/modules/Default.mjs
@@ -5,14 +5,20 @@
* @copyright Crown Copyright 2018
* @license Apache-2.0
*/
+import CartesianProduct from "../../operations/CartesianProduct";
import FromBase32 from "../../operations/FromBase32";
import FromBase64 from "../../operations/FromBase64";
import FromHex from "../../operations/FromHex";
+import PowerSet from "../../operations/PowerSet";
import ROT13 from "../../operations/ROT13";
import ROT47 from "../../operations/ROT47";
import RotateLeft from "../../operations/RotateLeft";
import RotateRight from "../../operations/RotateRight";
+import SetDifference from "../../operations/SetDifference";
+import SetIntersection from "../../operations/SetIntersection";
+import SetUnion from "../../operations/SetUnion";
import ShowBase64Offsets from "../../operations/ShowBase64Offsets";
+import SymmetricDifference from "../../operations/SymmetricDifference";
import ToBase32 from "../../operations/ToBase32";
import ToBase64 from "../../operations/ToBase64";
import ToHex from "../../operations/ToHex";
@@ -20,14 +26,20 @@ import ToHex from "../../operations/ToHex";
const OpModules = typeof self === "undefined" ? {} : self.OpModules || {};
OpModules.Default = {
+ "Cartesian Product": CartesianProduct,
"From Base32": FromBase32,
"From Base64": FromBase64,
"From Hex": FromHex,
+ "Power Set": PowerSet,
"ROT13": ROT13,
"ROT47": ROT47,
"Rotate left": RotateLeft,
"Rotate right": RotateRight,
+ "Set Difference": SetDifference,
+ "Set Intersection": SetIntersection,
+ "Set Union": SetUnion,
"Show Base64 offsets": ShowBase64Offsets,
+ "Symmetric Difference": SymmetricDifference,
"To Base32": ToBase32,
"To Base64": ToBase64,
"To Hex": ToHex,
diff --git a/src/core/operations/CartesianProduct.mjs b/src/core/operations/CartesianProduct.mjs
new file mode 100644
index 0000000000..193654743a
--- /dev/null
+++ b/src/core/operations/CartesianProduct.mjs
@@ -0,0 +1,84 @@
+/**
+ * @author d98762625 [d98762625@gmail.com]
+ * @copyright Crown Copyright 2018
+ * @license Apache-2.0
+ */
+
+import Utils from "../Utils";
+import Operation from "../Operation";
+
+/**
+ * Set cartesian product operation
+ */
+class CartesianProduct extends Operation {
+
+ /**
+ * Cartesian Product constructor
+ */
+ constructor() {
+ super();
+
+ this.name = "Cartesian Product";
+ this.module = "Default";
+ this.description = "Get the cartesian product of two sets";
+ this.inputType = "string";
+ this.outputType = "string";
+ this.args = [
+ {
+ name: "Sample delimiter",
+ type: "binaryString",
+ value: Utils.escapeHtml("\\n\\n")
+ },
+ {
+ name: "Item delimiter",
+ type: "binaryString",
+ value: ","
+ },
+ ];
+ }
+
+ /**
+ * Validate input length
+ * @param {Object[]} sets
+ * @throws {Error} if not two sets
+ */
+ validateSampleNumbers(sets) {
+ if (!sets || (sets.length !== 2)) {
+ throw "Incorrect number of sets, perhaps you need to modify the sample delimiter or add more samples?";
+ }
+ }
+
+ /**
+ * Run the product operation
+ * @param input
+ * @param args
+ */
+ run(input, args) {
+ [this.sampleDelim, this.itemDelimiter] = args;
+ const sets = input.split(this.sampleDelim);
+
+ try {
+ this.validateSampleNumbers(sets);
+ } catch (e) {
+ return e;
+ }
+
+ return Utils.escapeHtml(this.runCartesianProduct(...sets.map(s => s.split(this.itemDelimiter))));
+ }
+
+ /**
+ * Return the cartesian product of the two inputted sets.
+ *
+ * @param {Object[]} a
+ * @param {Object[]} b
+ * @returns {String[]}
+ */
+ runCartesianProduct(a, b) {
+ return Array(Math.max(a.length, b.length))
+ .fill(null)
+ .map((item, index) => `(${a[index] || undefined},${b[index] || undefined})`)
+ .join(this.itemDelimiter);
+ }
+}
+
+export default CartesianProduct;
diff --git a/src/core/operations/PowerSet.mjs b/src/core/operations/PowerSet.mjs
new file mode 100644
index 0000000000..e40e3027ee
--- /dev/null
+++ b/src/core/operations/PowerSet.mjs
@@ -0,0 +1,91 @@
+/**
+ * @author d98762625 [d98762625@gmail.com]
+ * @copyright Crown Copyright 2018
+ * @license Apache-2.0
+ */
+
+import Utils from "../Utils";
+import Operation from "../Operation";
+
+/**
+ * Power Set operation
+ */
+class PowerSet extends Operation {
+
+ /**
+ * Power set constructor
+ */
+ constructor() {
+ super();
+
+ this.name = "Power Set";
+ this.module = "Default";
+ this.description = "Generate the power set of a set";
+ this.inputType = "string";
+ this.outputType = "string";
+ this.args = [
+ {
+ name: "Item delimiter",
+ type: "binaryString",
+ value: ","
+ },
+ ];
+ }
+
+ /**
+ * Generate the power set
+ * @param input
+ * @param args
+ */
+ run(input, args) {
+ [this.itemDelimiter] = args;
+ // Split and filter empty strings
+ const inputArray = input.split(this.itemDelimiter).filter(a => a);
+
+ if (inputArray.length) {
+ return Utils.escapeHtml(this.runPowerSet(inputArray));
+ }
+
+ return "";
+ }
+
+ /**
+ * Return the power set of the inputted set.
+ *
+ * @param {Object[]} a
+ * @returns {Object[]}
+ */
+ runPowerSet(a) {
+ // empty array items getting picked up
+ a = a.filter(i => i.length);
+ if (!a.length) {
+ return [];
+ }
+
+ /**
+ * Decimal to binary function
+ * @param {*} dec
+ */
+ const toBinary = (dec) => (dec >>> 0).toString(2);
+ const result = new Set();
+ // Get the decimal number to make a binary as long as the input
+ const maxBinaryValue = parseInt(Number(a.map(i => "1").reduce((p, c) => p + c)), 2);
+ // Make an array of each binary number from 0 to maximum
+ const binaries = [...Array(maxBinaryValue + 1).keys()]
+ .map(toBinary)
+ .map(i => i.padStart(toBinary(maxBinaryValue).length, "0"));
+
+ // XOR the input with each binary to get each unique permutation
+ binaries.forEach((binary) => {
+ const split = binary.split("");
+ result.add(a.filter((item, index) => split[index] === "1"));
+ });
+
+ // map for formatting & put in length order.
+ return [...result]
+ .map(r => r.join(this.itemDelimiter)).sort((a, b) => a.length - b.length)
+ .map(i => `${i}\n`).join("");
+ }
+}
+
+export default PowerSet;
diff --git a/src/core/operations/SetDifference.mjs b/src/core/operations/SetDifference.mjs
new file mode 100644
index 0000000000..653ef2d85f
--- /dev/null
+++ b/src/core/operations/SetDifference.mjs
@@ -0,0 +1,86 @@
+/**
+ * @author d98762625 [d98762625@gmail.com]
+ * @copyright Crown Copyright 2018
+ * @license Apache-2.0
+ */
+
+import Utils from "../Utils";
+import Operation from "../Operation";
+
+/**
+ * Set Difference operation
+ */
+class SetDifference extends Operation {
+
+ /**
+ * Set Difference constructor
+ */
+ constructor() {
+ super();
+
+ this.name = "Set Difference";
+ this.module = "Default";
+ this.description = "Get the Difference of two sets";
+ this.inputType = "string";
+ this.outputType = "string";
+ this.args = [
+ {
+ name: "Sample delimiter",
+ type: "binaryString",
+ value: Utils.escapeHtml("\\n\\n")
+ },
+ {
+ name: "Item delimiter",
+ type: "binaryString",
+ value: ","
+ },
+ ];
+ }
+
+ /**
+ * Validate input length
+ * @param {Object[]} sets
+ * @throws {Error} if not two sets
+ */
+ validateSampleNumbers(sets) {
+ if (!sets || (sets.length !== 2)) {
+ throw "Incorrect number of sets, perhaps you need to modify the sample delimiter or add more samples?";
+ }
+ }
+
+ /**
+ * Run the difference operation
+ * @param input
+ * @param args
+ */
+ run(input, args) {
+ [this.sampleDelim, this.itemDelimiter] = args;
+ const sets = input.split(this.sampleDelim);
+
+ try {
+ this.validateSampleNumbers(sets);
+ } catch (e) {
+ return e;
+ }
+
+ return Utils.escapeHtml(this.runSetDifference(...sets.map(s => s.split(this.itemDelimiter))));
+ }
+
+ /**
+ * Get elements in set a that are not in set b
+ *
+ * @param {Object[]} a
+ * @param {Object[]} b
+ * @returns {Object[]}
+ */
+ runSetDifference(a, b) {
+ return a
+ .filter((item) => {
+ return b.indexOf(item) === -1;
+ })
+ .join(this.itemDelimiter);
+ }
+
+}
+
+export default SetDifference;
diff --git a/src/core/operations/SetIntersection.mjs b/src/core/operations/SetIntersection.mjs
new file mode 100644
index 0000000000..fd91c55b59
--- /dev/null
+++ b/src/core/operations/SetIntersection.mjs
@@ -0,0 +1,80 @@
+import Utils from "../Utils";
+import Operation from "../Operation";
+
+/**
+ * Set Intersection operation
+ */
+class SetIntersection extends Operation {
+
+ /**
+ * Set Intersection constructor
+ */
+ constructor() {
+ super();
+
+ this.name = "Set Intersection";
+ this.module = "Default";
+ this.description = "Get the intersection of two sets";
+ this.inputType = "string";
+ this.outputType = "string";
+ this.args = [
+ {
+ name: "Sample delimiter",
+ type: "binaryString",
+ value: Utils.escapeHtml("\\n\\n")
+ },
+ {
+ name: "Item delimiter",
+ type: "binaryString",
+ value: ","
+ },
+ ];
+ }
+
+ /**
+ * Validate input length
+ * @param {Object[]} sets
+ * @throws {Error} if not two sets
+ */
+ validateSampleNumbers(sets) {
+ if (!sets || (sets.length !== 2)) {
+ throw "Incorrect number of sets, perhaps you need to modify the sample delimiter or add more samples?";
+ }
+ }
+
+ /**
+ * Run the intersection operation
+ * @param input
+ * @param args
+ */
+ run(input, args) {
+ [this.sampleDelim, this.itemDelimiter] = args;
+ const sets = input.split(this.sampleDelim);
+
+ try {
+ this.validateSampleNumbers(sets);
+ } catch (e) {
+ return e;
+ }
+
+ return Utils.escapeHtml(this.runIntersect(...sets.map(s => s.split(this.itemDelimiter))));
+ }
+
+ /**
+ * Get the intersection of the two sets.
+ *
+ * @param {Object[]} a
+ * @param {Object[]} b
+ * @returns {Object[]}
+ */
+ runIntersect(a, b) {
+ return a
+ .filter((item) => {
+ return b.indexOf(item) > -1;
+ })
+ .join(this.itemDelimiter);
+ }
+
+}
+
+export default SetIntersection;
diff --git a/src/core/operations/SetUnion.mjs b/src/core/operations/SetUnion.mjs
new file mode 100644
index 0000000000..bfe39d11a7
--- /dev/null
+++ b/src/core/operations/SetUnion.mjs
@@ -0,0 +1,96 @@
+/**
+ * @author d98762625 [d98762625@gmail.com]
+ * @copyright Crown Copyright 2018
+ * @license Apache-2.0
+ */
+
+import Utils from "../Utils";
+import Operation from "../Operation";
+
+/**
+ * Set Union operation
+ */
+class SetUnion extends Operation {
+
+ /**
+ * Set Union constructor
+ */
+ constructor() {
+ super();
+
+ this.name = "Set Union";
+ this.module = "Default";
+ this.description = "Get the union of two sets";
+ this.inputType = "string";
+ this.outputType = "string";
+ this.args = [
+ {
+ name: "Sample delimiter",
+ type: "binaryString",
+ value: Utils.escapeHtml("\\n\\n")
+ },
+ {
+ name: "Item delimiter",
+ type: "binaryString",
+ value: ","
+ },
+ ];
+ }
+
+ /**
+ * Validate input length
+ * @param {Object[]} sets
+ * @throws {Error} if not two sets
+ */
+ validateSampleNumbers(sets) {
+ if (!sets || (sets.length !== 2)) {
+ throw "Incorrect number of sets, perhaps you need to modify the sample delimiter or add more samples?";
+ }
+ }
+
+ /**
+ * Run the union operation
+ * @param input
+ * @param args
+ */
+ run(input, args) {
+ [this.sampleDelim, this.itemDelimiter] = args;
+ const sets = input.split(this.sampleDelim);
+
+ try {
+ this.validateSampleNumbers(sets);
+ } catch (e) {
+ return e;
+ }
+
+ return Utils.escapeHtml(this.runUnion(...sets.map(s => s.split(this.itemDelimiter))));
+ }
+
+ /**
+ * Get the union of the two sets.
+ *
+ * @param {Object[]} a
+ * @param {Object[]} b
+ * @returns {Object[]}
+ */
+ runUnion(a, b) {
+ const result = {};
+
+ /**
+ * Only add non-existing items
+ * @param {Object} hash
+ */
+ const addUnique = (hash) => (item) => {
+ if (!hash[item]) {
+ hash[item] = true;
+ }
+ };
+
+ a.map(addUnique(result));
+ b.map(addUnique(result));
+
+ return Object.keys(result).join(this.itemDelimiter);
+ }
+}
+
+export default SetUnion;
diff --git a/src/core/operations/SymmetricDifference.mjs b/src/core/operations/SymmetricDifference.mjs
new file mode 100644
index 0000000000..09132802e0
--- /dev/null
+++ b/src/core/operations/SymmetricDifference.mjs
@@ -0,0 +1,97 @@
+/**
+ * @author d98762625 [d98762625@gmail.com]
+ * @copyright Crown Copyright 2018
+ * @license Apache-2.0
+ */
+
+import Utils from "../Utils";
+import Operation from "../Operation";
+
+/**
+ * Set Symmetric Difference operation
+ */
+class SymmetricDifference extends Operation {
+
+ /**
+ * Symmetric Difference constructor
+ */
+ constructor() {
+ super();
+
+ this.name = "Symmetric Difference";
+ this.module = "Default";
+ this.description = "Get the symmetric difference of two sets";
+ this.inputType = "string";
+ this.outputType = "string";
+ this.args = [
+ {
+ name: "Sample delimiter",
+ type: "binaryString",
+ value: Utils.escapeHtml("\\n\\n")
+ },
+ {
+ name: "Item delimiter",
+ type: "binaryString",
+ value: ","
+ },
+ ];
+ }
+
+ /**
+ * Validate input length
+ * @param {Object[]} sets
+ * @throws {Error} if not two sets
+ */
+ validateSampleNumbers(sets) {
+ if (!sets || (sets.length !== 2)) {
+ throw "Incorrect number of sets, perhaps you need to modify the sample delimiter or add more samples?";
+ }
+ }
+
+ /**
+ * Run the difference operation
+ * @param input
+ * @param args
+ */
+ run(input, args) {
+ [this.sampleDelim, this.itemDelimiter] = args;
+ const sets = input.split(this.sampleDelim);
+
+ try {
+ this.validateSampleNumbers(sets);
+ } catch (e) {
+ return e;
+ }
+
+ return Utils.escapeHtml(this.runSymmetricDifference(...sets.map(s => s.split(this.itemDelimiter))));
+ }
+
+ /**
+ * Get elements in set a that are not in set b
+ *
+ * @param {Object[]} a
+ * @param {Object[]} b
+ * @returns {Object[]}
+ */
+ runSetDifference(a, b) {
+ return a.filter((item) => {
+ return b.indexOf(item) === -1;
+ });
+ }
+
+ /**
+ * Get elements of each set that aren't in the other set.
+ *
+ * @param {Object[]} a
+ * @param {Object[]} b
+ * @return {Object[]}
+ */
+ runSymmetricDifference(a, b) {
+ return this.runSetDifference(a, b)
+ .concat(this.runSetDifference(b, a))
+ .join(this.itemDelimiter);
+ }
+
+}
+
+export default SymmetricDifference;
diff --git a/src/core/operations/index.mjs b/src/core/operations/index.mjs
index 5b7359638b..fff714d2f2 100644
--- a/src/core/operations/index.mjs
+++ b/src/core/operations/index.mjs
@@ -5,18 +5,24 @@
* @copyright Crown Copyright 2018
* @license Apache-2.0
*/
+import CartesianProduct from "./CartesianProduct";
import FromBase32 from "./FromBase32";
import FromBase64 from "./FromBase64";
import FromHex from "./FromHex";
import Gunzip from "./Gunzip";
import Gzip from "./Gzip";
+import PowerSet from "./PowerSet";
import ROT13 from "./ROT13";
import ROT47 from "./ROT47";
import RawDeflate from "./RawDeflate";
import RawInflate from "./RawInflate";
import RotateLeft from "./RotateLeft";
import RotateRight from "./RotateRight";
+import SetDifference from "./SetDifference";
+import SetIntersection from "./SetIntersection";
+import SetUnion from "./SetUnion";
import ShowBase64Offsets from "./ShowBase64Offsets";
+import SymmetricDifference from "./SymmetricDifference";
import ToBase32 from "./ToBase32";
import ToBase64 from "./ToBase64";
import ToHex from "./ToHex";
@@ -26,18 +32,24 @@ import ZlibDeflate from "./ZlibDeflate";
import ZlibInflate from "./ZlibInflate";
export {
+ CartesianProduct,
FromBase32,
FromBase64,
FromHex,
Gunzip,
Gzip,
+ PowerSet,
ROT13,
ROT47,
RawDeflate,
RawInflate,
RotateLeft,
RotateRight,
+ SetDifference,
+ SetIntersection,
+ SetUnion,
ShowBase64Offsets,
+ SymmetricDifference,
ToBase32,
ToBase64,
ToHex,
diff --git a/test/index.mjs b/test/index.mjs
index 82b3c08161..dd592f6fc4 100644
--- a/test/index.mjs
+++ b/test/index.mjs
@@ -48,7 +48,12 @@ import "./tests/operations/Base64";
import "./tests/operations/Rotate.mjs";
// import "./tests/operations/StrUtils.js";
// import "./tests/operations/SeqUtils.js";
-
+import "./tests/operations/SetUnion";
+import "./tests/operations/SetIntersection";
+import "./tests/operations/SetDifference";
+import "./tests/operations/SymmetricDifference";
+import "./tests/operations/CartesianProduct";
+import "./tests/operations/PowerSet";
let allTestsPassing = true;
const testStatusCounts = {
diff --git a/test/tests/operations/CartesianProduct.mjs b/test/tests/operations/CartesianProduct.mjs
new file mode 100644
index 0000000000..d5505b2f30
--- /dev/null
+++ b/test/tests/operations/CartesianProduct.mjs
@@ -0,0 +1,78 @@
+/**
+ * Cartesian Product tests.
+ *
+ * @author d98762625
+ *
+ * @copyright Crown Copyright 2018
+ * @license Apache-2.0
+ */
+import TestRegister from "../../TestRegister";
+
+TestRegister.addTests([
+ {
+ name: "Cartesian Product",
+ input: "1 2 3 4 5\n\na b c d e",
+ expectedOutput: "(1,a) (2,b) (3,c) (4,d) (5,e)",
+ recipeConfig: [
+ {
+ op: "Cartesian Product",
+ args: ["\n\n", " "],
+ },
+ ],
+ },
+ {
+ name: "Cartesian Product: wrong sample count",
+ input: "1 2\n\n3 4 5\n\na b c d e",
+ expectedOutput: "Incorrect number of sets, perhaps you need to modify the sample delimiter or add more samples?",
+ recipeConfig: [
+ {
+ op: "Cartesian Product",
+ args: ["\n\n", " "],
+ },
+ ],
+ },
+ {
+ name: "Cartesian Product: too many on left",
+ input: "1 2 3 4 5 6\n\na b c d e",
+ expectedOutput: "(1,a) (2,b) (3,c) (4,d) (5,e) (6,undefined)",
+ recipeConfig: [
+ {
+ op: "Cartesian Product",
+ args: ["\n\n", " "],
+ },
+ ],
+ },
+ {
+ name: "Cartesian Product: too many on right",
+ input: "1 2 3 4 5\n\na b c d e f",
+ expectedOutput: "(1,a) (2,b) (3,c) (4,d) (5,e) (undefined,f)",
+ recipeConfig: [
+ {
+ op: "Cartesian Product",
+ args: ["\n\n", " "],
+ },
+ ],
+ },
+ {
+ name: "Cartesian Product: item delimiter",
+ input: "1-2-3-4-5\n\na-b-c-d-e",
+ expectedOutput: "(1,a)-(2,b)-(3,c)-(4,d)-(5,e)",
+ recipeConfig: [
+ {
+ op: "Cartesian Product",
+ args: ["\n\n", "-"],
+ },
+ ],
+ },
+ {
+ name: "Cartesian Product: sample delimiter",
+ input: "1 2 3 4 5_a b c d e",
+ expectedOutput: "(1,a) (2,b) (3,c) (4,d) (5,e)",
+ recipeConfig: [
+ {
+ op: "Cartesian Product",
+ args: ["_", " "],
+ },
+ ],
+ },
+]);
diff --git a/test/tests/operations/PowerSet.mjs b/test/tests/operations/PowerSet.mjs
new file mode 100644
index 0000000000..f3fffed487
--- /dev/null
+++ b/test/tests/operations/PowerSet.mjs
@@ -0,0 +1,34 @@
+/**
+ * Power Set tests.
+ *
+ * @author d98762625
+ *
+ * @copyright Crown Copyright 2018
+ * @license Apache-2.0
+ */
+import TestRegister from "../../TestRegister";
+
+TestRegister.addTests([
+ {
+ name: "Power set: nothing",
+ input: "",
+ expectedOutput: "",
+ recipeConfig: [
+ {
+ op: "Power Set",
+ args: [","],
+ },
+ ],
+ },
+ {
+ name: "Power set",
+ input: "1 2 4",
+ expectedOutput: "\n4\n2\n1\n2 4\n1 4\n1 2\n1 2 4\n",
+ recipeConfig: [
+ {
+ op: "Power Set",
+ args: [" "],
+ },
+ ],
+ },
+]);
diff --git a/test/tests/operations/SetDifference.mjs b/test/tests/operations/SetDifference.mjs
new file mode 100644
index 0000000000..3bc91d3f21
--- /dev/null
+++ b/test/tests/operations/SetDifference.mjs
@@ -0,0 +1,56 @@
+/**
+ * Set Difference tests.
+ *
+ * @author d98762625
+ *
+ * @copyright Crown Copyright 2018
+ * @license Apache-2.0
+ */
+import TestRegister from "../../TestRegister";
+
+TestRegister.addTests([
+ {
+ name: "Set Difference",
+ input: "1 2 3 4 5\n\n3 4 5 6 7",
+ expectedOutput: "1 2",
+ recipeConfig: [
+ {
+ op: "Set Difference",
+ args: ["\n\n", " "],
+ },
+ ],
+ },
+ {
+ name: "Set Difference: wrong sample count",
+ input: "1 2 3 4 5_3_4 5 6 7",
+ expectedOutput: "Incorrect number of sets, perhaps you need to modify the sample delimiter or add more samples?",
+ recipeConfig: [
+ {
+ op: "Set Difference",
+ args: [" ", "_"],
+ },
+ ],
+ },
+ {
+ name: "Set Difference: item delimiter",
+ input: "1;2;3;4;5\n\n3;4;5;6;7",
+ expectedOutput: "1;2",
+ recipeConfig: [
+ {
+ op: "Set Difference",
+ args: ["\n\n", ";"],
+ },
+ ],
+ },
+ {
+ name: "Set Difference: sample delimiter",
+ input: "1;2;3;4;5===3;4;5;6;7",
+ expectedOutput: "1;2",
+ recipeConfig: [
+ {
+ op: "Set Difference",
+ args: ["===", ";"],
+ },
+ ],
+ },
+]);
diff --git a/test/tests/operations/SetIntersection.mjs b/test/tests/operations/SetIntersection.mjs
new file mode 100644
index 0000000000..83809b6ea8
--- /dev/null
+++ b/test/tests/operations/SetIntersection.mjs
@@ -0,0 +1,56 @@
+/**
+ * Set Intersection tests.
+ *
+ * @author d98762625
+ *
+ * @copyright Crown Copyright 2018
+ * @license Apache-2.0
+ */
+import TestRegister from "../../TestRegister";
+
+TestRegister.addTests([
+ {
+ name: "Set Intersection",
+ input: "1 2 3 4 5\n\n3 4 5 6 7",
+ expectedOutput: "3 4 5",
+ recipeConfig: [
+ {
+ op: "Set Intersection",
+ args: ["\n\n", " "],
+ },
+ ],
+ },
+ {
+ name: "Set Intersection: only one set",
+ input: "1 2 3 4 5 6 7 8",
+ expectedOutput: "Incorrect number of sets, perhaps you need to modify the sample delimiter or add more samples?",
+ recipeConfig: [
+ {
+ op: "Set Intersection",
+ args: ["\n\n", " "],
+ },
+ ],
+ },
+ {
+ name: "Set Intersection: item delimiter",
+ input: "1-2-3-4-5\n\n3-4-5-6-7",
+ expectedOutput: "3-4-5",
+ recipeConfig: [
+ {
+ op: "Set Intersection",
+ args: ["\n\n", "-"],
+ },
+ ],
+ },
+ {
+ name: "Set Intersection: sample delimiter",
+ input: "1-2-3-4-5z3-4-5-6-7",
+ expectedOutput: "3-4-5",
+ recipeConfig: [
+ {
+ op: "Set Intersection",
+ args: ["z", "-"],
+ },
+ ],
+ }
+]);
diff --git a/test/tests/operations/SetUnion.mjs b/test/tests/operations/SetUnion.mjs
new file mode 100644
index 0000000000..a78bdc3619
--- /dev/null
+++ b/test/tests/operations/SetUnion.mjs
@@ -0,0 +1,298 @@
+/**
+ * Set Union tests.
+ *
+ * @author d98762625
+ *
+ * @copyright Crown Copyright 2018
+ * @license Apache-2.0
+ */
+import TestRegister from "../../TestRegister";
+
+TestRegister.addTests([
+ {
+ name: "Set Union: Nothing",
+ input: "\n\n",
+ expectedOutput: "",
+ recipeConfig: [
+ {
+ op: "Set Union",
+ args: ["\n\n", " "],
+ },
+ ],
+ },
+ {
+ name: "Set Union",
+ input: "1 2 3 4 5\n\n3 4 5 6 7",
+ expectedOutput: "1 2 3 4 5 6 7",
+ recipeConfig: [
+ {
+ op: "Set Union",
+ args: ["\n\n", " "],
+ },
+ ],
+ },
+ {
+ name: "Set Union: invalid sample number",
+ input: "1 2 3 4 5\n\n3 4 5 6 7\n\n1",
+ expectedOutput: "Incorrect number of sets, perhaps you need to modify the sample delimiter or add more samples?",
+ recipeConfig: [
+ {
+ op: "Set Union",
+ args: ["\n\n", " "],
+ },
+ ],
+ },
+ {
+ name: "Set Union: item delimiter",
+ input: "1,2,3,4,5\n\n3,4,5,6,7",
+ expectedOutput: "1,2,3,4,5,6,7",
+ recipeConfig: [
+ {
+ op: "Set Union",
+ args: ["\n\n", ","],
+ },
+ ],
+ },
+ {
+ name: "Set Union: sample delimiter",
+ input: "1 2 3 4 5whatever3 4 5 6 7",
+ expectedOutput: "1 2 3 4 5 6 7",
+ recipeConfig: [
+ {
+ op: "Set Union",
+ args: ["whatever", " "],
+ },
+ ],
+ },
+ // {
+ // name: "Set Operations: Intersection",
+ // input: "1 2 3 4 5\n\n3 4 5 6 7",
+ // expectedOutput: "3 4 5",
+ // recipeConfig: [
+ // {
+ // op: "Set Operations",
+ // args: ["\n\n", " ", "Intersection"],
+ // },
+ // ],
+ // },
+ // {
+ // name: "Set Operations: Intersection: only one set",
+ // input: "1 2 3 4 5 6 7 8",
+ // expectedOutput: "Incorrect number of sets, perhaps you need to modify the sample delimiter or add more samples?",
+ // recipeConfig: [
+ // {
+ // op: "Set Operations",
+ // args: ["\n\n", " ", "Intersection"],
+ // },
+ // ],
+ // },
+ // {
+ // name: "Set Operations: Intersection: item delimiter",
+ // input: "1-2-3-4-5\n\n3-4-5-6-7",
+ // expectedOutput: "3-4-5",
+ // recipeConfig: [
+ // {
+ // op: "Set Operations",
+ // args: ["\n\n", "-", "Intersection"],
+ // },
+ // ],
+ // },
+ // {
+ // name: "Set Operations: Intersection: sample delimiter",
+ // input: "1-2-3-4-5z3-4-5-6-7",
+ // expectedOutput: "3-4-5",
+ // recipeConfig: [
+ // {
+ // op: "Set Operations",
+ // args: ["z", "-", "Intersection"],
+ // },
+ // ],
+ // },
+ // {
+ // name: "Set Operations: Set Difference",
+ // input: "1 2 3 4 5\n\n3 4 5 6 7",
+ // expectedOutput: "1 2",
+ // recipeConfig: [
+ // {
+ // op: "Set Operations",
+ // args: ["\n\n", " ", "Set Difference"],
+ // },
+ // ],
+ // },
+ // {
+ // name: "Set Operations: Set Difference: wrong sample count",
+ // input: "1 2 3 4 5_3_4 5 6 7",
+ // expectedOutput: "Incorrect number of sets, perhaps you need to modify the sample delimiter or add more samples?",
+ // recipeConfig: [
+ // {
+ // op: "Set Operations",
+ // args: [" ", "_", "Set Difference"],
+ // },
+ // ],
+ // },
+ // {
+ // name: "Set Operations: Set Difference: item delimiter",
+ // input: "1;2;3;4;5\n\n3;4;5;6;7",
+ // expectedOutput: "1;2",
+ // recipeConfig: [
+ // {
+ // op: "Set Operations",
+ // args: ["\n\n", ";", "Set Difference"],
+ // },
+ // ],
+ // },
+ // {
+ // name: "Set Operations: Set Difference: sample delimiter",
+ // input: "1;2;3;4;5===3;4;5;6;7",
+ // expectedOutput: "1;2",
+ // recipeConfig: [
+ // {
+ // op: "Set Operations",
+ // args: ["===", ";", "Set Difference"],
+ // },
+ // ],
+ // },
+ // {
+ // name: "Set Operations: Symmetric Difference",
+ // input: "1 2 3 4 5\n\n3 4 5 6 7",
+ // expectedOutput: "1 2 6 7",
+ // recipeConfig: [
+ // {
+ // op: "Set Operations",
+ // args: ["\n\n", " ", "Symmetric Difference"],
+ // },
+ // ],
+ // },
+ // {
+ // name: "Set Operations: Symmetric Difference: wrong sample count",
+ // input: "1 2\n\n3 4 5\n\n3 4 5 6 7",
+ // expectedOutput: "Incorrect number of sets, perhaps you need to modify the sample delimiter or add more samples?",
+ // recipeConfig: [
+ // {
+ // op: "Set Operations",
+ // args: ["\n\n", " ", "Symmetric Difference"],
+ // },
+ // ],
+ // },
+ // {
+ // name: "Set Operations: Symmetric Difference: item delimiter",
+ // input: "a_b_c_d_e\n\nc_d_e_f_g",
+ // expectedOutput: "a_b_f_g",
+ // recipeConfig: [
+ // {
+ // op: "Set Operations",
+ // args: ["\n\n", "_", "Symmetric Difference"],
+ // },
+ // ],
+ // },
+ // {
+ // name: "Set Operations: Symmetric Difference: sample delimiter",
+ // input: "a_b_c_d_eAAAAAc_d_e_f_g",
+ // expectedOutput: "a_b_f_g",
+ // recipeConfig: [
+ // {
+ // op: "Set Operations",
+ // args: ["AAAAA", "_", "Symmetric Difference"],
+ // },
+ // ],
+ // },
+ // {
+ // name: "Set Operations: Cartesian Product",
+ // input: "1 2 3 4 5\n\na b c d e",
+ // expectedOutput: "(1,a) (2,b) (3,c) (4,d) (5,e)",
+ // recipeConfig: [
+ // {
+ // op: "Set Operations",
+ // args: ["\n\n", " ", "Cartesian Product"],
+ // },
+ // ],
+ // },
+ // {
+ // name: "Set Operations: Cartesian Product: wrong sample count",
+ // input: "1 2\n\n3 4 5\n\na b c d e",
+ // expectedOutput: "Incorrect number of sets, perhaps you need to modify the sample delimiter or add more samples?",
+ // recipeConfig: [
+ // {
+ // op: "Set Operations",
+ // args: ["\n\n", " ", "Cartesian Product"],
+ // },
+ // ],
+ // },
+ // {
+ // name: "Set Operations: Cartesian Product: too many on left",
+ // input: "1 2 3 4 5 6\n\na b c d e",
+ // expectedOutput: "(1,a) (2,b) (3,c) (4,d) (5,e) (6,undefined)",
+ // recipeConfig: [
+ // {
+ // op: "Set Operations",
+ // args: ["\n\n", " ", "Cartesian Product"],
+ // },
+ // ],
+ // },
+ // {
+ // name: "Set Operations: Cartesian Product: too many on right",
+ // input: "1 2 3 4 5\n\na b c d e f",
+ // expectedOutput: "(1,a) (2,b) (3,c) (4,d) (5,e) (undefined,f)",
+ // recipeConfig: [
+ // {
+ // op: "Set Operations",
+ // args: ["\n\n", " ", "Cartesian Product"],
+ // },
+ // ],
+ // },
+ // {
+ // name: "Set Operations: Cartesian Product: item delimiter",
+ // input: "1-2-3-4-5\n\na-b-c-d-e",
+ // expectedOutput: "(1,a)-(2,b)-(3,c)-(4,d)-(5,e)",
+ // recipeConfig: [
+ // {
+ // op: "Set Operations",
+ // args: ["\n\n", "-", "Cartesian Product"],
+ // },
+ // ],
+ // },
+ // {
+ // name: "Set Operations: Cartesian Product: sample delimiter",
+ // input: "1 2 3 4 5_a b c d e",
+ // expectedOutput: "(1,a) (2,b) (3,c) (4,d) (5,e)",
+ // recipeConfig: [
+ // {
+ // op: "Set Operations",
+ // args: ["_", " ", "Cartesian Product"],
+ // },
+ // ],
+ // },
+ // {
+ // name: "Set Operations: Power set: nothing",
+ // input: "",
+ // expectedOutput: "",
+ // recipeConfig: [
+ // {
+ // op: "Set Operations",
+ // args: ["\n\n", " ", "Power Set"],
+ // },
+ // ],
+ // },
+ // {
+ // name: "Set Operations: Power set: Too many samples",
+ // input: "1 2 3\n\n4",
+ // expectedOutput: "Incorrect number of sets, perhaps you need to modify the sample delimiter or add more samples?",
+ // recipeConfig: [
+ // {
+ // op: "Set Operations",
+ // args: ["\n\n", " ", "Power Set"],
+ // },
+ // ],
+ // },
+ // {
+ // name: "Set Operations: Power set",
+ // input: "1 2 4",
+ // expectedOutput: "\n4\n2\n1\n2 4\n1 4\n1 2\n1 2 4\n",
+ // recipeConfig: [
+ // {
+ // op: "Set Operations",
+ // args: ["\n\n", " ", "Power Set"],
+ // },
+ // ],
+ // },
+]);
diff --git a/test/tests/operations/SymmetricDifference.mjs b/test/tests/operations/SymmetricDifference.mjs
new file mode 100644
index 0000000000..a2ef1562a3
--- /dev/null
+++ b/test/tests/operations/SymmetricDifference.mjs
@@ -0,0 +1,56 @@
+/**
+ * Symmetric difference tests.
+ *
+ * @author d98762625
+ *
+ * @copyright Crown Copyright 2018
+ * @license Apache-2.0
+ */
+import TestRegister from "../../TestRegister";
+
+TestRegister.addTests([
+ {
+ name: "Symmetric Difference",
+ input: "1 2 3 4 5\n\n3 4 5 6 7",
+ expectedOutput: "1 2 6 7",
+ recipeConfig: [
+ {
+ op: "Symmetric Difference",
+ args: ["\n\n", " "],
+ },
+ ],
+ },
+ {
+ name: "Symmetric Difference: wrong sample count",
+ input: "1 2\n\n3 4 5\n\n3 4 5 6 7",
+ expectedOutput: "Incorrect number of sets, perhaps you need to modify the sample delimiter or add more samples?",
+ recipeConfig: [
+ {
+ op: "Symmetric Difference",
+ args: ["\n\n", " "],
+ },
+ ],
+ },
+ {
+ name: "Symmetric Difference: item delimiter",
+ input: "a_b_c_d_e\n\nc_d_e_f_g",
+ expectedOutput: "a_b_f_g",
+ recipeConfig: [
+ {
+ op: "Symmetric Difference",
+ args: ["\n\n", "_"],
+ },
+ ],
+ },
+ {
+ name: "Symmetric Difference: sample delimiter",
+ input: "a_b_c_d_eAAAAAc_d_e_f_g",
+ expectedOutput: "a_b_f_g",
+ recipeConfig: [
+ {
+ op: "Symmetric Difference",
+ args: ["AAAAA", "_"],
+ },
+ ],
+ },
+]);