You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					197 lines
				
				5.1 KiB
			
		
		
			
		
	
	
					197 lines
				
				5.1 KiB
			| 
											2 years ago
										 | 'use strict'; | ||
|  | 
 | ||
|  | const _ = require('../utils/under-dash'); | ||
|  | const colCache = require('../utils/col-cache'); | ||
|  | const CellMatrix = require('../utils/cell-matrix'); | ||
|  | const Range = require('./range'); | ||
|  | 
 | ||
|  | const rangeRegexp = /[$](\w+)[$](\d+)(:[$](\w+)[$](\d+))?/; | ||
|  | 
 | ||
|  | class DefinedNames { | ||
|  |   constructor() { | ||
|  |     this.matrixMap = {}; | ||
|  |   } | ||
|  | 
 | ||
|  |   getMatrix(name) { | ||
|  |     const matrix = this.matrixMap[name] || (this.matrixMap[name] = new CellMatrix()); | ||
|  |     return matrix; | ||
|  |   } | ||
|  | 
 | ||
|  |   // add a name to a cell. locStr in the form SheetName!$col$row or SheetName!$c1$r1:$c2:$r2
 | ||
|  |   add(locStr, name) { | ||
|  |     const location = colCache.decodeEx(locStr); | ||
|  |     this.addEx(location, name); | ||
|  |   } | ||
|  | 
 | ||
|  |   addEx(location, name) { | ||
|  |     const matrix = this.getMatrix(name); | ||
|  |     if (location.top) { | ||
|  |       for (let col = location.left; col <= location.right; col++) { | ||
|  |         for (let row = location.top; row <= location.bottom; row++) { | ||
|  |           const address = { | ||
|  |             sheetName: location.sheetName, | ||
|  |             address: colCache.n2l(col) + row, | ||
|  |             row, | ||
|  |             col, | ||
|  |           }; | ||
|  | 
 | ||
|  |           matrix.addCellEx(address); | ||
|  |         } | ||
|  |       } | ||
|  |     } else { | ||
|  |       matrix.addCellEx(location); | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   remove(locStr, name) { | ||
|  |     const location = colCache.decodeEx(locStr); | ||
|  |     this.removeEx(location, name); | ||
|  |   } | ||
|  | 
 | ||
|  |   removeEx(location, name) { | ||
|  |     const matrix = this.getMatrix(name); | ||
|  |     matrix.removeCellEx(location); | ||
|  |   } | ||
|  | 
 | ||
|  |   removeAllNames(location) { | ||
|  |     _.each(this.matrixMap, matrix => { | ||
|  |       matrix.removeCellEx(location); | ||
|  |     }); | ||
|  |   } | ||
|  | 
 | ||
|  |   forEach(callback) { | ||
|  |     _.each(this.matrixMap, (matrix, name) => { | ||
|  |       matrix.forEach(cell => { | ||
|  |         callback(name, cell); | ||
|  |       }); | ||
|  |     }); | ||
|  |   } | ||
|  | 
 | ||
|  |   // get all the names of a cell
 | ||
|  |   getNames(addressStr) { | ||
|  |     return this.getNamesEx(colCache.decodeEx(addressStr)); | ||
|  |   } | ||
|  | 
 | ||
|  |   getNamesEx(address) { | ||
|  |     return _.map(this.matrixMap, (matrix, name) => matrix.findCellEx(address) && name).filter( | ||
|  |       Boolean | ||
|  |     ); | ||
|  |   } | ||
|  | 
 | ||
|  |   _explore(matrix, cell) { | ||
|  |     cell.mark = false; | ||
|  |     const {sheetName} = cell; | ||
|  | 
 | ||
|  |     const range = new Range(cell.row, cell.col, cell.row, cell.col, sheetName); | ||
|  |     let x; | ||
|  |     let y; | ||
|  | 
 | ||
|  |     // grow vertical - only one col to worry about
 | ||
|  |     function vGrow(yy, edge) { | ||
|  |       const c = matrix.findCellAt(sheetName, yy, cell.col); | ||
|  |       if (!c || !c.mark) { | ||
|  |         return false; | ||
|  |       } | ||
|  |       range[edge] = yy; | ||
|  |       c.mark = false; | ||
|  |       return true; | ||
|  |     } | ||
|  |     for (y = cell.row - 1; vGrow(y, 'top'); y--); | ||
|  |     for (y = cell.row + 1; vGrow(y, 'bottom'); y++); | ||
|  | 
 | ||
|  |     // grow horizontal - ensure all rows can grow
 | ||
|  |     function hGrow(xx, edge) { | ||
|  |       const cells = []; | ||
|  |       for (y = range.top; y <= range.bottom; y++) { | ||
|  |         const c = matrix.findCellAt(sheetName, y, xx); | ||
|  |         if (c && c.mark) { | ||
|  |           cells.push(c); | ||
|  |         } else { | ||
|  |           return false; | ||
|  |         } | ||
|  |       } | ||
|  |       range[edge] = xx; | ||
|  |       for (let i = 0; i < cells.length; i++) { | ||
|  |         cells[i].mark = false; | ||
|  |       } | ||
|  |       return true; | ||
|  |     } | ||
|  |     for (x = cell.col - 1; hGrow(x, 'left'); x--); | ||
|  |     for (x = cell.col + 1; hGrow(x, 'right'); x++); | ||
|  | 
 | ||
|  |     return range; | ||
|  |   } | ||
|  | 
 | ||
|  |   getRanges(name, matrix) { | ||
|  |     matrix = matrix || this.matrixMap[name]; | ||
|  | 
 | ||
|  |     if (!matrix) { | ||
|  |       return {name, ranges: []}; | ||
|  |     } | ||
|  | 
 | ||
|  |     // mark and sweep!
 | ||
|  |     matrix.forEach(cell => { | ||
|  |       cell.mark = true; | ||
|  |     }); | ||
|  |     const ranges = matrix | ||
|  |       .map(cell => cell.mark && this._explore(matrix, cell)) | ||
|  |       .filter(Boolean) | ||
|  |       .map(range => range.$shortRange); | ||
|  | 
 | ||
|  |     return { | ||
|  |       name, | ||
|  |       ranges, | ||
|  |     }; | ||
|  |   } | ||
|  | 
 | ||
|  |   normaliseMatrix(matrix, sheetName) { | ||
|  |     // some of the cells might have shifted on specified sheet
 | ||
|  |     // need to reassign rows, cols
 | ||
|  |     matrix.forEachInSheet(sheetName, (cell, row, col) => { | ||
|  |       if (cell) { | ||
|  |         if (cell.row !== row || cell.col !== col) { | ||
|  |           cell.row = row; | ||
|  |           cell.col = col; | ||
|  |           cell.address = colCache.n2l(col) + row; | ||
|  |         } | ||
|  |       } | ||
|  |     }); | ||
|  |   } | ||
|  | 
 | ||
|  |   spliceRows(sheetName, start, numDelete, numInsert) { | ||
|  |     _.each(this.matrixMap, matrix => { | ||
|  |       matrix.spliceRows(sheetName, start, numDelete, numInsert); | ||
|  |       this.normaliseMatrix(matrix, sheetName); | ||
|  |     }); | ||
|  |   } | ||
|  | 
 | ||
|  |   spliceColumns(sheetName, start, numDelete, numInsert) { | ||
|  |     _.each(this.matrixMap, matrix => { | ||
|  |       matrix.spliceColumns(sheetName, start, numDelete, numInsert); | ||
|  |       this.normaliseMatrix(matrix, sheetName); | ||
|  |     }); | ||
|  |   } | ||
|  | 
 | ||
|  |   get model() { | ||
|  |     // To get names per cell - just iterate over all names finding cells if they exist
 | ||
|  |     return _.map(this.matrixMap, (matrix, name) => this.getRanges(name, matrix)).filter( | ||
|  |       definedName => definedName.ranges.length | ||
|  |     ); | ||
|  |   } | ||
|  | 
 | ||
|  |   set model(value) { | ||
|  |     // value is [ { name, ranges }, ... ]
 | ||
|  |     const matrixMap = (this.matrixMap = {}); | ||
|  |     value.forEach(definedName => { | ||
|  |       const matrix = (matrixMap[definedName.name] = new CellMatrix()); | ||
|  |       definedName.ranges.forEach(rangeStr => { | ||
|  |         if (rangeRegexp.test(rangeStr.split('!').pop() || '')) { | ||
|  |           matrix.addCell(rangeStr); | ||
|  |         } | ||
|  |       }); | ||
|  |     }); | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | module.exports = DefinedNames; |