diff --git a/src/core/operations/BSONDeserialise.mjs b/src/core/operations/BSONDeserialise.mjs
new file mode 100644
index 00000000..b0225569
--- /dev/null
+++ b/src/core/operations/BSONDeserialise.mjs
@@ -0,0 +1,50 @@
+/**
+ * @author n1474335 [n1474335@gmail.com]
+ * @copyright Crown Copyright 2018
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation";
+import bsonjs from "bson";
+import OperationError from "../errors/OperationError";
+
+/**
+ * BSON deserialise operation
+ */
+class BSONDeserialise extends Operation {
+
+ /**
+ * BSONDeserialise constructor
+ */
+ constructor() {
+ super();
+
+ this.name = "BSON deserialise";
+ this.module = "BSON";
+ this.description = "BSON is a computer data interchange format used mainly as a data storage and network transfer format in the MongoDB database. It is a binary form for representing simple data structures, associative arrays (called objects or documents in MongoDB), and various data types of specific interest to MongoDB. The name 'BSON' is based on the term JSON and stands for 'Binary JSON'.
Input data should be in a raw bytes format.";
+ this.inputType = "ArrayBuffer";
+ this.outputType = "string";
+ this.args = [];
+ }
+
+ /**
+ * @param {ArrayBuffer} input
+ * @param {Object[]} args
+ * @returns {string}
+ */
+ run(input, args) {
+ if (!input.byteLength) return "";
+
+ const bson = new bsonjs();
+
+ try {
+ const data = bson.deserialize(new Buffer(input));
+ return JSON.stringify(data, null, 2);
+ } catch (err) {
+ throw new OperationError(err.toString());
+ }
+ }
+
+}
+
+export default BSONDeserialise;
diff --git a/src/core/operations/BSONSerialise.mjs b/src/core/operations/BSONSerialise.mjs
new file mode 100644
index 00000000..a82296b3
--- /dev/null
+++ b/src/core/operations/BSONSerialise.mjs
@@ -0,0 +1,50 @@
+/**
+ * @author n1474335 [n1474335@gmail.com]
+ * @copyright Crown Copyright 2018
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation";
+import bsonjs from "bson";
+import OperationError from "../errors/OperationError";
+
+/**
+ * BSON serialise operation
+ */
+class BSONSerialise extends Operation {
+
+ /**
+ * BSONSerialise constructor
+ */
+ constructor() {
+ super();
+
+ this.name = "BSON serialise";
+ this.module = "BSON";
+ this.description = "BSON is a computer data interchange format used mainly as a data storage and network transfer format in the MongoDB database. It is a binary form for representing simple data structures, associative arrays (called objects or documents in MongoDB), and various data types of specific interest to MongoDB. The name 'BSON' is based on the term JSON and stands for 'Binary JSON'.
Input data should be valid JSON.";
+ this.inputType = "string";
+ this.outputType = "ArrayBuffer";
+ this.args = [];
+ }
+
+ /**
+ * @param {string} input
+ * @param {Object[]} args
+ * @returns {ArrayBuffer}
+ */
+ run(input, args) {
+ if (!input) return new ArrayBuffer();
+
+ const bson = new bsonjs();
+
+ try {
+ const data = JSON.parse(input);
+ return bson.serialize(data).buffer;
+ } catch (err) {
+ throw new OperationError(err.toString());
+ }
+ }
+
+}
+
+export default BSONSerialise;
diff --git a/src/core/operations/ToTable.mjs b/src/core/operations/ToTable.mjs
new file mode 100644
index 00000000..944904e6
--- /dev/null
+++ b/src/core/operations/ToTable.mjs
@@ -0,0 +1,189 @@
+/**
+ * @author Mark Jones [github.com/justanothermark]
+ * @copyright Crown Copyright 2018
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation";
+import Utils from "../Utils";
+
+/**
+ * To Table operation
+ */
+class ToTable extends Operation {
+
+ /**
+ * ToTable constructor
+ */
+ constructor() {
+ super();
+
+ this.name = "To Table";
+ this.module = "Default";
+ this.description = "Data can be split on different characters and rendered as an HTML or ASCII table with an optional header row.
Supports the CSV (Comma Separated Values) file format by default. Change the cell delimiter argument to \\t
to support TSV (Tab Separated Values) or |
for PSV (Pipe Separated Values).
You can enter as many delimiters as you like. Each character will be treat as a separate possible delimiter.";
+ this.inputType = "string";
+ this.outputType = "html";
+ this.args = [
+ {
+ "name": "Cell delimiters",
+ "type": "binaryShortString",
+ "value": ","
+ },
+ {
+ "name": "Row delimiters",
+ "type": "binaryShortString",
+ "value": "\\n\\r"
+ },
+ {
+ "name": "Make first row header",
+ "type": "boolean",
+ "value": false
+ },
+ {
+ "name": "Format",
+ "type": "option",
+ "value": ["ASCII", "HTML"]
+ }
+ ];
+ }
+
+ /**
+ * @param {string} input
+ * @param {Object[]} args
+ * @returns {html}
+ */
+ run(input, args) {
+ const [cellDelims, rowDelims, firstRowHeader, format] = args;
+
+ // Process the input into a nested array of elements.
+ const tableData = Utils.parseCSV(input, cellDelims.split(""), rowDelims.split(""));
+
+ if (!tableData.length) return "";
+
+ // Render the data in the requested format.
+ switch (format) {
+ case "ASCII":
+ return asciiOutput(tableData);
+ case "HTML":
+ default:
+ return htmlOutput(tableData);
+ }
+
+ /**
+ * Outputs an array of data as an ASCII table.
+ *
+ * @param {string[][]} tableData
+ * @returns {string}
+ */
+ function asciiOutput(tableData) {
+ const horizontalBorder = "-";
+ const verticalBorder = "|";
+ const crossBorder = "+";
+
+ let output = "";
+ const longestCells = [];
+
+ // Find longestCells value per column to pad cells equally.
+ tableData.forEach(function(row, index) {
+ row.forEach(function(cell, cellIndex) {
+ if (longestCells[cellIndex] === undefined || cell.length > longestCells[cellIndex]) {
+ longestCells[cellIndex] = cell.length;
+ }
+ });
+ });
+
+ // Add the top border of the table to the output.
+ output += outputHorizontalBorder(longestCells);
+
+ // If the first row is a header, remove the row from the data and
+ // add it to the output with another horizontal border.
+ if (firstRowHeader) {
+ const row = tableData.shift();
+ output += outputRow(row, longestCells);
+ output += outputHorizontalBorder(longestCells);
+ }
+
+ // Add the rest of the table rows.
+ tableData.forEach(function(row, index) {
+ output += outputRow(row, longestCells);
+ });
+
+ // Close the table with a final horizontal border.
+ output += outputHorizontalBorder(longestCells);
+
+ return output;
+
+ /**
+ * Outputs a row of correctly padded cells.
+ */
+ function outputRow(row, longestCells) {
+ let rowOutput = verticalBorder;
+ row.forEach(function(cell, index) {
+ rowOutput += " " + cell + " ".repeat(longestCells[index] - cell.length) + " " + verticalBorder;
+ });
+ rowOutput += "\n";
+ return rowOutput;
+ }
+
+ /**
+ * Outputs a horizontal border with a different character where
+ * the horizontal border meets a vertical border.
+ */
+ function outputHorizontalBorder(longestCells) {
+ let rowOutput = crossBorder;
+ longestCells.forEach(function(cellLength) {
+ rowOutput += horizontalBorder.repeat(cellLength + 2) + crossBorder;
+ });
+ rowOutput += "\n";
+ return rowOutput;
+ }
+ }
+
+ /**
+ * Outputs a table of data as a HTML table.
+ *
+ * @param {string[][]} tableData
+ * @returns {string}
+ */
+ function htmlOutput(tableData) {
+ // Start the HTML output with suitable classes for styling.
+ let output = "
cells. + if (firstRowHeader) { + const row = tableData.shift(); + output += ""; + output += outputRow(row, "th"); + output += ""; + } + + // Output the rest of the rows in the | . + output += ""; + tableData.forEach(function(row, index) { + output += outputRow(row, "td"); + }); + + // Close the body and table elements. + output += "
---|