diff --git a/src/core/Utils.mjs b/src/core/Utils.mjs index 55acd3dd1..342d6d6c3 100755 --- a/src/core/Utils.mjs +++ b/src/core/Utils.mjs @@ -573,6 +573,10 @@ class Utils { cell = ""; lines.push(line); line = []; + // Skip next byte if it is also a line delim (e.g. \r\n) + if (lineDelims.indexOf(next) >= 0 && next !== b) { + i++; + } } else { cell += b; } diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json index 1891c4600..ee638893c 100755 --- a/src/core/config/Categories.json +++ b/src/core/config/Categories.json @@ -54,7 +54,9 @@ "From MessagePack", "To Braille", "From Braille", - "Parse TLV" + "Parse TLV", + "CSV to JSON", + "JSON to CSV" ] }, { diff --git a/src/core/operations/CSVToJSON.mjs b/src/core/operations/CSVToJSON.mjs new file mode 100644 index 000000000..d2cdb53b9 --- /dev/null +++ b/src/core/operations/CSVToJSON.mjs @@ -0,0 +1,80 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import OperationError from "../errors/OperationError"; +import Utils from "../Utils"; + +/** + * CSV to JSON operation + */ +class CSVToJSON extends Operation { + + /** + * CSVToJSON constructor + */ + constructor() { + super(); + + this.name = "CSV to JSON"; + this.module = "Default"; + this.description = "Converts a CSV file to JSON format."; + this.infoURL = "https://wikipedia.org/wiki/Comma-separated_values"; + this.inputType = "string"; + this.outputType = "JSON"; + this.args = [ + { + name: "Cell delimiters", + type: "binaryShortString", + value: "," + }, + { + name: "Row delimiters", + type: "binaryShortString", + value: "\\r\\n" + }, + { + name: "Format", + type: "option", + value: ["Array of dictionaries", "Array of arrays"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {JSON} + */ + run(input, args) { + const [cellDelims, rowDelims, format] = args; + let json, header; + + try { + json = Utils.parseCSV(input, cellDelims.split(""), rowDelims.split("")); + } catch (err) { + throw new OperationError("Unable to parse CSV: " + err); + } + + switch (format) { + case "Array of dictionaries": + header = json[0]; + return json.slice(1).map(row => { + const obj = {}; + header.forEach((h, i) => { + obj[h] = row[i]; + }); + return obj; + }); + case "Array of arrays": + default: + return json; + } + } + +} + +export default CSVToJSON; diff --git a/src/core/operations/JSONToCSV.mjs b/src/core/operations/JSONToCSV.mjs new file mode 100644 index 000000000..5dd25ffcb --- /dev/null +++ b/src/core/operations/JSONToCSV.mjs @@ -0,0 +1,72 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import OperationError from "../errors/OperationError"; + +/** + * JSON to CSV operation + */ +class JSONToCSV extends Operation { + + /** + * JSONToCSV constructor + */ + constructor() { + super(); + + this.name = "JSON to CSV"; + this.module = "Default"; + this.description = "Converts JSON data to a CSV."; + this.infoURL = "https://wikipedia.org/wiki/Comma-separated_values"; + this.inputType = "JSON"; + this.outputType = "string"; + this.args = [ + { + name: "Cell delimiter", + type: "binaryShortString", + value: "," + }, + { + name: "Row delimiter", + type: "binaryShortString", + value: "\\r\\n" + } + ]; + } + + /** + * @param {JSON} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [cellDelim, rowDelim] = args; + + try { + // If the JSON is an array of arrays, this is easy + if (input[0] instanceof Array) { + return input.map(row => row.join(cellDelim)).join(rowDelim) + rowDelim; + } + + // If it's an array of dictionaries... + const header = Object.keys(input[0]); + return header.join(cellDelim) + + rowDelim + + input.map( + row => header.map( + h => row[h] + ).join(cellDelim) + ).join(rowDelim) + + rowDelim; + } catch (err) { + throw new OperationError("Unable to parse JSON to CSV: " + err); + } + } + +} + +export default JSONToCSV; diff --git a/src/core/operations/ToTable.mjs b/src/core/operations/ToTable.mjs index dfade8c1b..28cd9bfe0 100644 --- a/src/core/operations/ToTable.mjs +++ b/src/core/operations/ToTable.mjs @@ -33,7 +33,7 @@ class ToTable extends Operation { { "name": "Row delimiters", "type": "binaryShortString", - "value": "\\n\\r" + "value": "\\r\\n" }, { "name": "Make first row header",