defined-names.js 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. 'use strict';
  2. const _ = require('../utils/under-dash');
  3. const colCache = require('../utils/col-cache');
  4. const CellMatrix = require('../utils/cell-matrix');
  5. const Range = require('./range');
  6. const rangeRegexp = /[$](\w+)[$](\d+)(:[$](\w+)[$](\d+))?/;
  7. class DefinedNames {
  8. constructor() {
  9. this.matrixMap = {};
  10. }
  11. getMatrix(name) {
  12. const matrix = this.matrixMap[name] || (this.matrixMap[name] = new CellMatrix());
  13. return matrix;
  14. }
  15. // add a name to a cell. locStr in the form SheetName!$col$row or SheetName!$c1$r1:$c2:$r2
  16. add(locStr, name) {
  17. const location = colCache.decodeEx(locStr);
  18. this.addEx(location, name);
  19. }
  20. addEx(location, name) {
  21. const matrix = this.getMatrix(name);
  22. if (location.top) {
  23. for (let col = location.left; col <= location.right; col++) {
  24. for (let row = location.top; row <= location.bottom; row++) {
  25. const address = {
  26. sheetName: location.sheetName,
  27. address: colCache.n2l(col) + row,
  28. row,
  29. col,
  30. };
  31. matrix.addCellEx(address);
  32. }
  33. }
  34. } else {
  35. matrix.addCellEx(location);
  36. }
  37. }
  38. remove(locStr, name) {
  39. const location = colCache.decodeEx(locStr);
  40. this.removeEx(location, name);
  41. }
  42. removeEx(location, name) {
  43. const matrix = this.getMatrix(name);
  44. matrix.removeCellEx(location);
  45. }
  46. removeAllNames(location) {
  47. _.each(this.matrixMap, matrix => {
  48. matrix.removeCellEx(location);
  49. });
  50. }
  51. forEach(callback) {
  52. _.each(this.matrixMap, (matrix, name) => {
  53. matrix.forEach(cell => {
  54. callback(name, cell);
  55. });
  56. });
  57. }
  58. // get all the names of a cell
  59. getNames(addressStr) {
  60. return this.getNamesEx(colCache.decodeEx(addressStr));
  61. }
  62. getNamesEx(address) {
  63. return _.map(this.matrixMap, (matrix, name) => matrix.findCellEx(address) && name).filter(
  64. Boolean
  65. );
  66. }
  67. _explore(matrix, cell) {
  68. cell.mark = false;
  69. const {sheetName} = cell;
  70. const range = new Range(cell.row, cell.col, cell.row, cell.col, sheetName);
  71. let x;
  72. let y;
  73. // grow vertical - only one col to worry about
  74. function vGrow(yy, edge) {
  75. const c = matrix.findCellAt(sheetName, yy, cell.col);
  76. if (!c || !c.mark) {
  77. return false;
  78. }
  79. range[edge] = yy;
  80. c.mark = false;
  81. return true;
  82. }
  83. for (y = cell.row - 1; vGrow(y, 'top'); y--);
  84. for (y = cell.row + 1; vGrow(y, 'bottom'); y++);
  85. // grow horizontal - ensure all rows can grow
  86. function hGrow(xx, edge) {
  87. const cells = [];
  88. for (y = range.top; y <= range.bottom; y++) {
  89. const c = matrix.findCellAt(sheetName, y, xx);
  90. if (c && c.mark) {
  91. cells.push(c);
  92. } else {
  93. return false;
  94. }
  95. }
  96. range[edge] = xx;
  97. for (let i = 0; i < cells.length; i++) {
  98. cells[i].mark = false;
  99. }
  100. return true;
  101. }
  102. for (x = cell.col - 1; hGrow(x, 'left'); x--);
  103. for (x = cell.col + 1; hGrow(x, 'right'); x++);
  104. return range;
  105. }
  106. getRanges(name, matrix) {
  107. matrix = matrix || this.matrixMap[name];
  108. if (!matrix) {
  109. return {name, ranges: []};
  110. }
  111. // mark and sweep!
  112. matrix.forEach(cell => {
  113. cell.mark = true;
  114. });
  115. const ranges = matrix
  116. .map(cell => cell.mark && this._explore(matrix, cell))
  117. .filter(Boolean)
  118. .map(range => range.$shortRange);
  119. return {
  120. name,
  121. ranges,
  122. };
  123. }
  124. normaliseMatrix(matrix, sheetName) {
  125. // some of the cells might have shifted on specified sheet
  126. // need to reassign rows, cols
  127. matrix.forEachInSheet(sheetName, (cell, row, col) => {
  128. if (cell) {
  129. if (cell.row !== row || cell.col !== col) {
  130. cell.row = row;
  131. cell.col = col;
  132. cell.address = colCache.n2l(col) + row;
  133. }
  134. }
  135. });
  136. }
  137. spliceRows(sheetName, start, numDelete, numInsert) {
  138. _.each(this.matrixMap, matrix => {
  139. matrix.spliceRows(sheetName, start, numDelete, numInsert);
  140. this.normaliseMatrix(matrix, sheetName);
  141. });
  142. }
  143. spliceColumns(sheetName, start, numDelete, numInsert) {
  144. _.each(this.matrixMap, matrix => {
  145. matrix.spliceColumns(sheetName, start, numDelete, numInsert);
  146. this.normaliseMatrix(matrix, sheetName);
  147. });
  148. }
  149. get model() {
  150. // To get names per cell - just iterate over all names finding cells if they exist
  151. return _.map(this.matrixMap, (matrix, name) => this.getRanges(name, matrix)).filter(
  152. definedName => definedName.ranges.length
  153. );
  154. }
  155. set model(value) {
  156. // value is [ { name, ranges }, ... ]
  157. const matrixMap = (this.matrixMap = {});
  158. value.forEach(definedName => {
  159. const matrix = (matrixMap[definedName.name] = new CellMatrix());
  160. definedName.ranges.forEach(rangeStr => {
  161. if (rangeRegexp.test(rangeStr.split('!').pop() || '')) {
  162. matrix.addCell(rangeStr);
  163. }
  164. });
  165. });
  166. }
  167. }
  168. module.exports = DefinedNames;