asciiTable.js 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. /* eslint-disable import/prefer-default-export */
  2. const $ = (selector, context = document) => context.querySelector(selector);
  3. /**
  4. * Return ASCII symbol for headers depends on what the class name HTMLTableCellElement has.
  5. *
  6. * @param {HTMLTableCellElement} cell
  7. * @return {String} Returns ' ', ` * ` or ' - '.
  8. */
  9. function getSelectionSymbolForHeader(cell) {
  10. const hasActiveHeader = cell.classList.contains('ht__active_highlight');
  11. const hasHighlight = cell.classList.contains('ht__highlight');
  12. let symbol = ' ';
  13. if (hasActiveHeader) {
  14. symbol = ' * ';
  15. } else if (hasHighlight) {
  16. symbol = ' - ';
  17. }
  18. return symbol;
  19. }
  20. /**
  21. * Return ASCII symbol for cells depends on what the class name HTMLTableCellElement has.
  22. *
  23. * @param {HTMLTableCellElement} cell
  24. * @return {String} Returns valid symbol for the pariticaul cell.
  25. */
  26. function getSelectionSymbolForCell(cell) {
  27. const hasCurrent = cell.classList.contains('current');
  28. const hasArea = cell.classList.contains('area');
  29. let areaLevel = new Array(7)
  30. .fill()
  31. .map((_, i, arr) => `area-${arr.length - i}`)
  32. .find(className => cell.classList.contains(className));
  33. areaLevel = areaLevel ? parseInt(areaLevel.replace('area-', ''), 10) : areaLevel;
  34. let symbol = ' ';
  35. if (hasCurrent && hasArea && areaLevel) {
  36. symbol = ` ${String.fromCharCode(65 + areaLevel)} `;
  37. } else if (hasCurrent && hasArea && areaLevel === void 0) {
  38. symbol = ' A ';
  39. } else if (hasCurrent && !hasArea && areaLevel === void 0) {
  40. symbol = ' # ';
  41. } else if (!hasCurrent && hasArea && areaLevel === void 0) {
  42. symbol = ' 0 ';
  43. } else if (!hasCurrent && hasArea && areaLevel) {
  44. symbol = ` ${areaLevel} `;
  45. }
  46. return symbol;
  47. }
  48. /**
  49. * Generate ASCII symbol for passed cell element.
  50. *
  51. * @param {HTMLTableCellElement} cell
  52. * @return {String}
  53. */
  54. function getSelectionSymbol(cell) {
  55. if (isLeftHeader(cell) || isTopHeader(cell)) {
  56. return getSelectionSymbolForHeader(cell);
  57. }
  58. return getSelectionSymbolForCell(cell);
  59. }
  60. /**
  61. * Check if passed element belong to the left header.
  62. *
  63. * @param {HTMLTableCellElement} cell
  64. * @return {Boolean}
  65. */
  66. function isLeftHeader(cell) {
  67. return cell.tagName === 'TH' && cell.parentElement.parentElement.tagName === 'TBODY';
  68. }
  69. /**
  70. * Check if passed element belong to the rop header.
  71. *
  72. * @param {HTMLTableCellElement} cell
  73. * @return {Boolean}
  74. */
  75. function isTopHeader(cell) {
  76. return cell.tagName === 'TH' && cell.parentElement.parentElement.tagName === 'THEAD';
  77. }
  78. /**
  79. * @param {HTMLTableElement} overlay
  80. * @return {Function}
  81. */
  82. function cellFactory(overlay) {
  83. return (row, column) => overlay && overlay.rows[row] && overlay.rows[row].cells[column];
  84. }
  85. /**
  86. * Generates table based on Handsontable structure.
  87. *
  88. * @param {HTMLElement} context The root element of the Handsontable instance to be generated.
  89. * @return {String}
  90. */
  91. export function generateASCIITable(context) {
  92. const TABLE_EDGES_SYMBOL = '|';
  93. const COLUMN_SEPARATOR = ':';
  94. const ROW_HEADER_SEPARATOR = '\u2551';
  95. const COLUMN_HEADER_SEPARATOR = '===';
  96. const ROW_OVERLAY_SEPARATOR = '|';
  97. const COLUMN_OVERLAY_SEPARATOR = '---';
  98. const cornerOverlayTable = $('.ht_clone_top_left_corner .htCore', context);
  99. const leftOverlayTable = $('.ht_clone_left .htCore', context);
  100. const topOverlayTable = $('.ht_clone_top .htCore', context);
  101. const masterTable = $('.ht_master .htCore', context);
  102. const stringRows = [];
  103. const cornerOverlayCells = cellFactory(cornerOverlayTable);
  104. const leftOverlayCells = cellFactory(leftOverlayTable);
  105. const topOverlayCells = cellFactory(topOverlayTable);
  106. const masterCells = cellFactory(masterTable);
  107. const hasLeftHeader = leftOverlayCells(1, 0) ? isLeftHeader(leftOverlayCells(1, 0)) : false;
  108. const hasTopHeader = topOverlayCells(0, 1) ? isTopHeader(topOverlayCells(0, 1)) : false;
  109. const hasCornerHeader = hasLeftHeader && hasTopHeader;
  110. const hasFixedLeftCells = leftOverlayCells(1, 1) ? !isLeftHeader(leftOverlayCells(1, 1)) : false;
  111. const hasFixedTopCells = topOverlayCells(1, 1) ? !isTopHeader(topOverlayCells(1, 1)) : false;
  112. const consumedFlags = new Map([
  113. ['hasLeftHeader', hasLeftHeader],
  114. ['hasTopHeader', hasTopHeader],
  115. ['hasCornerHeader', hasCornerHeader],
  116. ['hasFixedLeftCells', hasFixedLeftCells],
  117. ['hasFixedTopCells', hasLeftHeader],
  118. ]);
  119. const rowsLength = masterTable.rows.length;
  120. for (let r = 0; r < rowsLength; r++) {
  121. const stringCells = [];
  122. const columnsLength = masterTable.rows[0].cells.length;
  123. let isLastColumn = false;
  124. let insertTopOverlayRowSeparator = false;
  125. for (let c = 0; c < columnsLength; c++) {
  126. let cellSymbol;
  127. let separatorSymbol = COLUMN_SEPARATOR;
  128. isLastColumn = c === columnsLength - 1;
  129. if (cornerOverlayCells(r, c)) {
  130. const cell = cornerOverlayCells(r, c);
  131. const nextCell = cornerOverlayCells(r, c + 1);
  132. cellSymbol = getSelectionSymbol(cell);
  133. if (isLeftHeader(cell) && (!nextCell || !isLeftHeader(nextCell))) {
  134. separatorSymbol = ROW_HEADER_SEPARATOR;
  135. }
  136. if (!isLeftHeader(cell) && !nextCell) {
  137. separatorSymbol = ROW_OVERLAY_SEPARATOR;
  138. }
  139. if (r === 0 && c === 0 && hasCornerHeader) { // Fix for header symbol
  140. separatorSymbol = ROW_HEADER_SEPARATOR;
  141. }
  142. } else if (leftOverlayCells(r, c)) {
  143. const cell = leftOverlayCells(r, c);
  144. const nextCell = leftOverlayCells(r, c + 1);
  145. cellSymbol = getSelectionSymbol(cell);
  146. if (isLeftHeader(cell) && (!nextCell || !isLeftHeader(nextCell))) {
  147. separatorSymbol = ROW_HEADER_SEPARATOR;
  148. }
  149. if (!isLeftHeader(cell) && !nextCell) {
  150. separatorSymbol = ROW_OVERLAY_SEPARATOR;
  151. }
  152. } else if (topOverlayCells(r, c)) {
  153. const cell = topOverlayCells(r, c);
  154. cellSymbol = getSelectionSymbol(cell);
  155. if (hasFixedTopCells && isLastColumn && !topOverlayCells(r + 1, c)) {
  156. insertTopOverlayRowSeparator = true;
  157. }
  158. } else if (masterCells(r, c)) {
  159. const cell = masterCells(r, c);
  160. cellSymbol = getSelectionSymbol(cell);
  161. }
  162. stringCells.push(cellSymbol);
  163. if (!isLastColumn) {
  164. stringCells.push(separatorSymbol);
  165. }
  166. }
  167. stringRows.push(TABLE_EDGES_SYMBOL + stringCells.join('') + TABLE_EDGES_SYMBOL);
  168. if (consumedFlags.get('hasTopHeader')) {
  169. consumedFlags.delete('hasTopHeader');
  170. stringRows.push(TABLE_EDGES_SYMBOL + new Array(columnsLength).fill(COLUMN_HEADER_SEPARATOR).join(COLUMN_SEPARATOR) + TABLE_EDGES_SYMBOL);
  171. }
  172. if (insertTopOverlayRowSeparator) {
  173. insertTopOverlayRowSeparator = false;
  174. stringRows.push(TABLE_EDGES_SYMBOL + new Array(columnsLength).fill(COLUMN_OVERLAY_SEPARATOR).join(COLUMN_SEPARATOR) + TABLE_EDGES_SYMBOL);
  175. }
  176. }
  177. return stringRows.join('\n');
  178. }