Browse Source

Merge branch 'JustAnotherMark-totable-operation'

n1474335 7 years ago
parent
commit
addb2b4448

+ 16 - 10
src/core/Utils.js

@@ -737,37 +737,43 @@ const Utils = {
      * Parses CSV data and returns it as a two dimensional array or strings.
      *
      * @param {string} data
+     * @param {string[]} [cellDelims=[","]]
+     * @param {string[]} [lineDelims=["\n", "\r"]]
      * @returns {string[][]}
      *
      * @example
      * // returns [["head1", "head2"], ["data1", "data2"]]
      * Utils.parseCSV("head1,head2\ndata1,data2");
      */
-    parseCSV: function(data) {
-
+    parseCSV: function(data, cellDelims=[","], lineDelims=["\n", "\r"]) {
         let b,
-            ignoreNext = false,
+            next,
+            renderNext = false,
             inString = false,
             cell = "",
             line = [],
             lines = [];
 
+        // Remove BOM, often present in Excel CSV files
+        if (data.length && data[0] === "\uFEFF") data = data.substr(1);
+
         for (let i = 0; i < data.length; i++) {
             b = data[i];
-            if (ignoreNext) {
+            next = data[i+1] || "";
+            if (renderNext) {
                 cell += b;
-                ignoreNext = false;
+                renderNext = false;
             } else if (b === "\\") {
-                cell += b;
-                ignoreNext = true;
+                renderNext = true;
             } else if (b === "\"" && !inString) {
                 inString = true;
             } else if (b === "\"" && inString) {
-                inString = false;
-            } else if (b === "," && !inString) {
+                if (next === "\"") renderNext = true;
+                else inString = false;
+            } else if (!inString && cellDelims.indexOf(b) >= 0) {
                 line.push(cell);
                 cell = "";
-            } else if ((b === "\n" || b === "\r") && !inString) {
+            } else if (!inString && lineDelims.indexOf(b) >= 0) {
                 line.push(cell);
                 cell = "";
                 lines.push(line);

+ 1 - 0
src/core/config/Categories.js

@@ -182,6 +182,7 @@ const Categories = [
             "To Lower case",
             "Add line numbers",
             "Remove line numbers",
+            "To Table",
             "Reverse",
             "Sort",
             "Unique",

+ 29 - 0
src/core/config/OperationConfig.js

@@ -37,6 +37,7 @@ import SeqUtils from "../operations/SeqUtils.js";
 import Shellcode from "../operations/Shellcode.js";
 import StrUtils from "../operations/StrUtils.js";
 import Tidy from "../operations/Tidy.js";
+import ToTable from "../operations/ToTable.js";
 import Unicode from "../operations/Unicode.js";
 import URL_ from "../operations/URL.js";
 
@@ -613,6 +614,34 @@ const OperationConfig = {
             }
         ]
     },
+    "To Table": {
+        module: "Default",
+        description: "Data can be split on different characters and rendered as an HTML or ASCII table with an optional header row.<br><br>Supports the CSV (Comma Separated Values) file format by default. Change the cell delimiter argument to <code>\\t</code> to support TSV (Tab Separated Values) or <code>|</code> for PSV (Pipe Separated Values).<br><br>You can enter as many delimiters as you like. Each character will be treat as a separate possible delimiter.",
+        inputType: "string",
+        outputType: "html",
+        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: ToTable.FORMATS
+            }
+        ]
+    },
     "From Hex": {
         module: "Default",
         description: "Converts a hexadecimal byte string back into its raw value.<br><br>e.g. <code>ce 93 ce b5 ce b9 ce ac 20 cf 83 ce bf cf 85 0a</code> becomes the UTF-8 encoded string <code>Γειά σου</code>",

+ 2 - 0
src/core/config/modules/Default.js

@@ -27,6 +27,7 @@ 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 ToTable from "../../operations/ToTable.js";
 import Unicode from "../../operations/Unicode.js";
 import UUID from "../../operations/UUID.js";
 import XKCD from "../../operations/XKCD.js";
@@ -163,6 +164,7 @@ OpModules.Default = {
     "Mean":                 Arithmetic.runMean,
     "Median":               Arithmetic.runMedian,
     "Standard Deviation":   Arithmetic.runStdDev,
+    "To Table":             ToTable.runToTable,
     "Windows Filetime to UNIX Timestamp": Filetime.runFromFiletimeToUnix,
     "UNIX Timestamp to Windows Filetime": Filetime.runToFiletimeFromUnix,
     "XKCD Random Number":  XKCD.runRandomNumber,

+ 164 - 0
src/core/operations/ToTable.js

@@ -0,0 +1,164 @@
+/**
+ * ToTable operations.
+ *
+ * @author Mark Jones [github.com/justanothermark]
+ * @copyright Crown Copyright 2018
+ * @license Apache-2.0
+ *
+ * @namespace
+ */
+import Utils from "../Utils.js";
+
+const ToTable = {
+
+    /**
+     * @constant
+     * @default
+     */
+    FORMATS: [
+        "ASCII",
+        "HTML"
+    ],
+
+
+    /**
+     * To Table operation.
+     *
+     * @param {string} input
+     * @param {Object[]} args
+     * @returns {html}
+     */
+    runToTable: function (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 = "";
+            let 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) {
+                let 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 = "<table class='table table-hover table-condensed table-bordered table-nonfluid'>";
+
+            // If the first row is a header then put it in <thead> with <th> cells.
+            if (firstRowHeader) {
+                let row = tableData.shift();
+                output += "<thead>";
+                output += outputRow(row, "th");
+                output += "</thead>";
+            }
+
+            // Output the rest of the rows in the <tbody>.
+            output += "<tbody>";
+            tableData.forEach(function(row, index) {
+                output += outputRow(row, "td");
+            });
+
+            // Close the body and table elements.
+            output += "</tbody></table>";
+            return output;
+
+          /**
+           * Outputs a table row.
+           *
+           * @param {string[]} row
+           * @param {string} cellType
+           */
+            function outputRow(row, cellType) {
+                let output = "<tr>";
+                row.forEach(function(cell) {
+                    output += "<" + cellType + ">" + cell + "</" + cellType + ">";
+                });
+                output += "</tr>";
+                return output;
+            }
+        }
+    }
+};
+
+export default ToTable;