ToTable.mjs 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. /**
  2. * @author Mark Jones [github.com/justanothermark]
  3. * @copyright Crown Copyright 2018
  4. * @license Apache-2.0
  5. */
  6. import Operation from "../Operation";
  7. import Utils from "../Utils";
  8. /**
  9. * To Table operation
  10. */
  11. class ToTable extends Operation {
  12. /**
  13. * ToTable constructor
  14. */
  15. constructor() {
  16. super();
  17. this.name = "To Table";
  18. this.module = "Default";
  19. this.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.";
  20. this.inputType = "string";
  21. this.outputType = "html";
  22. this.args = [
  23. {
  24. "name": "Cell delimiters",
  25. "type": "binaryShortString",
  26. "value": ","
  27. },
  28. {
  29. "name": "Row delimiters",
  30. "type": "binaryShortString",
  31. "value": "\\n\\r"
  32. },
  33. {
  34. "name": "Make first row header",
  35. "type": "boolean",
  36. "value": false
  37. },
  38. {
  39. "name": "Format",
  40. "type": "option",
  41. "value": ["ASCII", "HTML"]
  42. }
  43. ];
  44. }
  45. /**
  46. * @param {string} input
  47. * @param {Object[]} args
  48. * @returns {html}
  49. */
  50. run(input, args) {
  51. const [cellDelims, rowDelims, firstRowHeader, format] = args;
  52. // Process the input into a nested array of elements.
  53. const tableData = Utils.parseCSV(input, cellDelims.split(""), rowDelims.split(""));
  54. if (!tableData.length) return "";
  55. // Render the data in the requested format.
  56. switch (format) {
  57. case "ASCII":
  58. return asciiOutput(tableData);
  59. case "HTML":
  60. default:
  61. return htmlOutput(tableData);
  62. }
  63. /**
  64. * Outputs an array of data as an ASCII table.
  65. *
  66. * @param {string[][]} tableData
  67. * @returns {string}
  68. */
  69. function asciiOutput(tableData) {
  70. const horizontalBorder = "-";
  71. const verticalBorder = "|";
  72. const crossBorder = "+";
  73. let output = "";
  74. const longestCells = [];
  75. // Find longestCells value per column to pad cells equally.
  76. tableData.forEach(function(row, index) {
  77. row.forEach(function(cell, cellIndex) {
  78. if (longestCells[cellIndex] === undefined || cell.length > longestCells[cellIndex]) {
  79. longestCells[cellIndex] = cell.length;
  80. }
  81. });
  82. });
  83. // Add the top border of the table to the output.
  84. output += outputHorizontalBorder(longestCells);
  85. // If the first row is a header, remove the row from the data and
  86. // add it to the output with another horizontal border.
  87. if (firstRowHeader) {
  88. const row = tableData.shift();
  89. output += outputRow(row, longestCells);
  90. output += outputHorizontalBorder(longestCells);
  91. }
  92. // Add the rest of the table rows.
  93. tableData.forEach(function(row, index) {
  94. output += outputRow(row, longestCells);
  95. });
  96. // Close the table with a final horizontal border.
  97. output += outputHorizontalBorder(longestCells);
  98. return output;
  99. /**
  100. * Outputs a row of correctly padded cells.
  101. */
  102. function outputRow(row, longestCells) {
  103. let rowOutput = verticalBorder;
  104. row.forEach(function(cell, index) {
  105. rowOutput += " " + cell + " ".repeat(longestCells[index] - cell.length) + " " + verticalBorder;
  106. });
  107. rowOutput += "\n";
  108. return rowOutput;
  109. }
  110. /**
  111. * Outputs a horizontal border with a different character where
  112. * the horizontal border meets a vertical border.
  113. */
  114. function outputHorizontalBorder(longestCells) {
  115. let rowOutput = crossBorder;
  116. longestCells.forEach(function(cellLength) {
  117. rowOutput += horizontalBorder.repeat(cellLength + 2) + crossBorder;
  118. });
  119. rowOutput += "\n";
  120. return rowOutput;
  121. }
  122. }
  123. /**
  124. * Outputs a table of data as a HTML table.
  125. *
  126. * @param {string[][]} tableData
  127. * @returns {string}
  128. */
  129. function htmlOutput(tableData) {
  130. // Start the HTML output with suitable classes for styling.
  131. let output = "<table class='table table-hover table-sm table-bordered table-nonfluid'>";
  132. // If the first row is a header then put it in <thead> with <th> cells.
  133. if (firstRowHeader) {
  134. const row = tableData.shift();
  135. output += "<thead class='thead-light'>";
  136. output += outputRow(row, "th");
  137. output += "</thead>";
  138. }
  139. // Output the rest of the rows in the <tbody>.
  140. output += "<tbody>";
  141. tableData.forEach(function(row, index) {
  142. output += outputRow(row, "td");
  143. });
  144. // Close the body and table elements.
  145. output += "</tbody></table>";
  146. return output;
  147. /**
  148. * Outputs a table row.
  149. *
  150. * @param {string[]} row
  151. * @param {string} cellType
  152. */
  153. function outputRow(row, cellType) {
  154. let output = "<tr>";
  155. row.forEach(function(cell) {
  156. output += "<" + cellType + ">" + cell + "</" + cellType + ">";
  157. });
  158. output += "</tr>";
  159. return output;
  160. }
  161. }
  162. }
  163. }
  164. export default ToTable;