Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added ToTable operation to output data as ASCII or HTML tables. #294

Merged
merged 2 commits into from
May 4, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/core/config/Categories.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ const Categories = [
"Encode text",
"Decode text",
"Swap endianness",
"To Table",
]
},
{
Expand Down
33 changes: 33 additions & 0 deletions src/core/config/OperationConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down Expand Up @@ -613,6 +614,38 @@ const OperationConfig = {
}
]
},
"To Table": {
module: "Default",
description: "Renders data as a table. Data can be split on different characters and output as a HTML or ASCII table with optional header row.",
inputType: "string",
outputType: "html",
highlight: false,
highlightReverse: false,
manualBake: false,
args: [
{
name: "Select separator",
type: "populateOption",
value: ToTable.SEPARATORS,
target: 1
},
{
name: "Separator",
type: "string",
value: ","
},
{
name: "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>",
Expand Down
2 changes: 2 additions & 0 deletions src/core/config/modules/Default.js
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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,
Expand Down
181 changes: 181 additions & 0 deletions src/core/operations/ToTable.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
/**
* ToTable operations.
*
* @author Mark Jones [github.com/justanothermark]
* @namespace
*/
const ToTable = {
/**
* @constant
* @default
*/
SEPARATORS: [
{name: "Comma", value: ","},
{name: "Tab", value: "\\t"},
{name: "Pipe", value: "|"},
{name: "Custom", value: ""}
],

/**
* @constant
* @default
*/
FORMATS: [
"ASCII",
"HTML"
],

/**
* To Table operation.
*
* @param {string} input
* @param {Object[]} args
* @returns {html}
*/
runToTable: function (input, args) {
let separator = args[1];
let firstRowHeader = args[2];
let format = args[3];
let tableData = [];

// If the separator contains any tabs, convert them to tab characters.
separator = separator.replace("\\t", "\t");

// Process the input into a nested array of elements.
let rows = input.split("\n");
rows.forEach(function(element) {
if (separator === "") {
tableData.push([element]);
} else {
tableData.push(element.split(separator));
}
});

// Render the data in the requested format.
let output = "";
switch (format) {
case "ASCII":
output = asciiOutput(tableData);
break;

default:
output = htmlOutput(tableData);
break;
}

return output;

/**
* Outputs an array of data as an ASCII table.
*
* @param {Array[]} 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.
*/
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;