col-cache.js 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. "use strict";
  2. const addressRegex = /^[A-Z]+\d+$/;
  3. // =========================================================================
  4. // Column Letter to Number conversion
  5. const colCache = {
  6. _dictionary: ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'],
  7. _l2nFill: 0,
  8. _l2n: {},
  9. _n2l: [],
  10. _level(n) {
  11. if (n <= 26) {
  12. return 1;
  13. }
  14. if (n <= 26 * 26) {
  15. return 2;
  16. }
  17. return 3;
  18. },
  19. _fill(level) {
  20. let c;
  21. let v;
  22. let l1;
  23. let l2;
  24. let l3;
  25. let n = 1;
  26. if (level >= 4) {
  27. throw new Error('Out of bounds. Excel supports columns from 1 to 16384');
  28. }
  29. if (this._l2nFill < 1 && level >= 1) {
  30. while (n <= 26) {
  31. c = this._dictionary[n - 1];
  32. this._n2l[n] = c;
  33. this._l2n[c] = n;
  34. n++;
  35. }
  36. this._l2nFill = 1;
  37. }
  38. if (this._l2nFill < 2 && level >= 2) {
  39. n = 27;
  40. while (n <= 26 + 26 * 26) {
  41. v = n - (26 + 1);
  42. l1 = v % 26;
  43. l2 = Math.floor(v / 26);
  44. c = this._dictionary[l2] + this._dictionary[l1];
  45. this._n2l[n] = c;
  46. this._l2n[c] = n;
  47. n++;
  48. }
  49. this._l2nFill = 2;
  50. }
  51. if (this._l2nFill < 3 && level >= 3) {
  52. n = 26 + 26 * 26 + 1;
  53. while (n <= 16384) {
  54. v = n - (26 * 26 + 26 + 1);
  55. l1 = v % 26;
  56. l2 = Math.floor(v / 26) % 26;
  57. l3 = Math.floor(v / (26 * 26));
  58. c = this._dictionary[l3] + this._dictionary[l2] + this._dictionary[l1];
  59. this._n2l[n] = c;
  60. this._l2n[c] = n;
  61. n++;
  62. }
  63. this._l2nFill = 3;
  64. }
  65. },
  66. l2n(l) {
  67. if (!this._l2n[l]) {
  68. this._fill(l.length);
  69. }
  70. if (!this._l2n[l]) {
  71. throw new Error(`Out of bounds. Invalid column letter: ${l}`);
  72. }
  73. return this._l2n[l];
  74. },
  75. n2l(n) {
  76. if (n < 1 || n > 16384) {
  77. throw new Error(`${n} is out of bounds. Excel supports columns from 1 to 16384`);
  78. }
  79. if (!this._n2l[n]) {
  80. this._fill(this._level(n));
  81. }
  82. return this._n2l[n];
  83. },
  84. // =========================================================================
  85. // Address processing
  86. _hash: {},
  87. // check if value looks like an address
  88. validateAddress(value) {
  89. if (!addressRegex.test(value)) {
  90. throw new Error(`Invalid Address: ${value}`);
  91. }
  92. return true;
  93. },
  94. // convert address string into structure
  95. decodeAddress(value) {
  96. const addr = value.length < 5 && this._hash[value];
  97. if (addr) {
  98. return addr;
  99. }
  100. let hasCol = false;
  101. let col = '';
  102. let colNumber = 0;
  103. let hasRow = false;
  104. let row = '';
  105. let rowNumber = 0;
  106. for (let i = 0, char; i < value.length; i++) {
  107. char = value.charCodeAt(i);
  108. // col should before row
  109. if (!hasRow && char >= 65 && char <= 90) {
  110. // 65 = 'A'.charCodeAt(0)
  111. // 90 = 'Z'.charCodeAt(0)
  112. hasCol = true;
  113. col += value[i];
  114. // colNumber starts from 1
  115. colNumber = colNumber * 26 + char - 64;
  116. } else if (char >= 48 && char <= 57) {
  117. // 48 = '0'.charCodeAt(0)
  118. // 57 = '9'.charCodeAt(0)
  119. hasRow = true;
  120. row += value[i];
  121. // rowNumber starts from 0
  122. rowNumber = rowNumber * 10 + char - 48;
  123. } else if (hasRow && hasCol && char !== 36) {
  124. // 36 = '$'.charCodeAt(0)
  125. break;
  126. }
  127. }
  128. if (!hasCol) {
  129. colNumber = undefined;
  130. } else if (colNumber > 16384) {
  131. throw new Error(`Out of bounds. Invalid column letter: ${col}`);
  132. }
  133. if (!hasRow) {
  134. rowNumber = undefined;
  135. }
  136. // in case $row$col
  137. value = col + row;
  138. const address = {
  139. address: value,
  140. col: colNumber,
  141. row: rowNumber,
  142. $col$row: `$${col}$${row}`
  143. };
  144. // mem fix - cache only the tl 100x100 square
  145. if (colNumber <= 100 && rowNumber <= 100) {
  146. this._hash[value] = address;
  147. this._hash[address.$col$row] = address;
  148. }
  149. return address;
  150. },
  151. // convert r,c into structure (if only 1 arg, assume r is address string)
  152. getAddress(r, c) {
  153. if (c) {
  154. const address = this.n2l(c) + r;
  155. return this.decodeAddress(address);
  156. }
  157. return this.decodeAddress(r);
  158. },
  159. // convert [address], [tl:br] into address structures
  160. decode(value) {
  161. const parts = value.split(':');
  162. if (parts.length === 2) {
  163. const tl = this.decodeAddress(parts[0]);
  164. const br = this.decodeAddress(parts[1]);
  165. const result = {
  166. top: Math.min(tl.row, br.row),
  167. left: Math.min(tl.col, br.col),
  168. bottom: Math.max(tl.row, br.row),
  169. right: Math.max(tl.col, br.col)
  170. };
  171. // reconstruct tl, br and dimensions
  172. result.tl = this.n2l(result.left) + result.top;
  173. result.br = this.n2l(result.right) + result.bottom;
  174. result.dimensions = `${result.tl}:${result.br}`;
  175. return result;
  176. }
  177. return this.decodeAddress(value);
  178. },
  179. // convert [sheetName!][$]col[$]row[[$]col[$]row] into address or range structures
  180. decodeEx(value) {
  181. const groups = value.match(/(?:(?:(?:'((?:[^']|'')*)')|([^'^ !]*))!)?(.*)/);
  182. const sheetName = groups[1] || groups[2]; // Qouted and unqouted groups
  183. const reference = groups[3]; // Remaining address
  184. const parts = reference.split(':');
  185. if (parts.length > 1) {
  186. let tl = this.decodeAddress(parts[0]);
  187. let br = this.decodeAddress(parts[1]);
  188. const top = Math.min(tl.row, br.row);
  189. const left = Math.min(tl.col, br.col);
  190. const bottom = Math.max(tl.row, br.row);
  191. const right = Math.max(tl.col, br.col);
  192. tl = this.n2l(left) + top;
  193. br = this.n2l(right) + bottom;
  194. return {
  195. top,
  196. left,
  197. bottom,
  198. right,
  199. sheetName,
  200. tl: {
  201. address: tl,
  202. col: left,
  203. row: top,
  204. $col$row: `$${this.n2l(left)}$${top}`,
  205. sheetName
  206. },
  207. br: {
  208. address: br,
  209. col: right,
  210. row: bottom,
  211. $col$row: `$${this.n2l(right)}$${bottom}`,
  212. sheetName
  213. },
  214. dimensions: `${tl}:${br}`
  215. };
  216. }
  217. if (reference.startsWith('#')) {
  218. return sheetName ? {
  219. sheetName,
  220. error: reference
  221. } : {
  222. error: reference
  223. };
  224. }
  225. const address = this.decodeAddress(reference);
  226. return sheetName ? {
  227. sheetName,
  228. ...address
  229. } : address;
  230. },
  231. // convert row,col into address string
  232. encodeAddress(row, col) {
  233. return colCache.n2l(col) + row;
  234. },
  235. // convert row,col into string address or t,l,b,r into range
  236. encode() {
  237. switch (arguments.length) {
  238. case 2:
  239. return colCache.encodeAddress(arguments[0], arguments[1]);
  240. case 4:
  241. return `${colCache.encodeAddress(arguments[0], arguments[1])}:${colCache.encodeAddress(arguments[2], arguments[3])}`;
  242. default:
  243. throw new Error('Can only encode with 2 or 4 arguments');
  244. }
  245. },
  246. // return true if address is contained within range
  247. inRange(range, address) {
  248. const [left, top,, right, bottom] = range;
  249. const [col, row] = address;
  250. return col >= left && col <= right && row >= top && row <= bottom;
  251. }
  252. };
  253. module.exports = colCache;
  254. //# sourceMappingURL=col-cache.js.map