PageRenderTime 55ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 1ms

/vendor/assets/components/handsontable-excel/jquery.handsontable-excel.js

https://gitlab.com/jgcallahan/sheets
JavaScript | 1262 lines | 1176 code | 38 blank | 48 comment | 186 complexity | b59303309c3f3c697614eb2a5251b179 MD5 | raw file
Possible License(s): MIT
  1. /**
  2. Extension to jquery.handsontable to implement formula evaluation
  3. as well as dynamic types depending on the value you input.
  4. To get the translation, add the following code to your 'cell' function:
  5. cells: function (row, col, prop) {
  6. var cellProperties = {};
  7. cellProperties.type = 'excel';
  8. return cellProperties;
  9. },
  10. */
  11. ;(function ( $ ) {
  12. var Token = function (t,c) {
  13. var _d = { "type": null, t: t, c: c };
  14. };
  15. Token.parse = function () {
  16. var c;
  17. if (self._d.type != null)
  18. return self._d; // Already parsed
  19. if (typeof self._d.t === 'undefined' || self._d.t == null || self._d.t == '')
  20. return self._d = { "type": '', token: null, next: null };
  21. if (typeof self._d.t == 'string') {
  22. c = self._d.t[0].toUpperCase();
  23. if (c == "'")
  24. return self._d = { "type": 'text', token: self._d.t.substring(1), next: self._d.c };
  25. if (c == '=')
  26. return self._d = { "type": 'formula', token: self._d.t.substring(1), next: self._d.c };
  27. if (c == '(' || c == ')')
  28. return self._d = { "type": 'paranthes', token: c, next: self._d.t.substring(1) };
  29. }
  30. else if (typeof self._d.t == 'number' && typeof self._d.c == 'number') {
  31. return self._d = { "type": 'coord', row: self._d.t, col: self._d.c };
  32. }
  33. };
  34. Token.get = function () {
  35. if (self._d.type != null)
  36. return self._d;
  37. else
  38. return self.parse();
  39. }
  40. Token.set = function(t,c) {
  41. self._d = { "type": null, t: t, c: c };
  42. }
  43. Token.error = function () {
  44. if (self._d.type == 'error')
  45. return self._d;
  46. if (self._d.type == null && typeof self._d.t == 'string')
  47. return self._d = { "type": 'error', error: self._d.t };
  48. return self._d = { "type": 'error', error: 'Bad error' };
  49. }
  50. Token.boolean = function () {
  51. if (self._d.type == 'boolean')
  52. return self._d;
  53. if (self._d.type == null && typeof self._d.t == 'number')
  54. return self._d = { "type": 'boolean', token: !!self._d.t, next: self._d.c };
  55. if (self._d.type == null && typeof self._d.t == 'string' &&
  56. self._d.t.length > 0 && self._d.t[0] >= '0' && self._d.t[0] <= '9')
  57. return self._d = { "type": 'boolean', token: !!(self._d.t * 1.0), next: self._d.c };
  58. return self._d = { "type": 'boolean', token: null, next: self._d.c };
  59. }
  60. Token.number = function () {
  61. if (self._d.type == 'number')
  62. return self._d;
  63. if (self._d.type == null && typeof self._d.t == 'number')
  64. return self._d = { "type": 'number', token: !!self._d.t, next: self._d.c };
  65. if (self._d.type == null && typeof self._d.t == 'string' && self._d.t.length > 0 &&
  66. (self._d.t[0] == '-' || (self._d.t[0] >= '0' && self._d.t[0] <= '9')))
  67. return self._d = { "type": 'number', token: !!(self._d.t * 1.0), next: self._d.c };
  68. return self._d = { "type": 'number', token: null, next: self._d.c };
  69. }
  70. //
  71. // Take the formula as a string
  72. // and make an array of tokens
  73. // Take the array of tokens and evaluate it
  74. // Return the result of that evaluation
  75. var evaluateFormula = function (instance,formula) {
  76. /*
  77. var evalFormula = function (instance, formula) {
  78. var instance = instance;
  79. var orgformula;
  80. var tokens = [];
  81. }
  82. */
  83. //
  84. // Give us a the coordinated from a string reference.
  85. // The coordinates are 0-based!!!
  86. // A1 gives the row=0 (from the '1') and col=0 (from the 'A')
  87. //
  88. var getRC = function (cellRef) {
  89. var i;
  90. var row = 0;
  91. var col = 0;
  92. var radix = "Z".charCodeAt(0) - "A".charCodeAt(0) + 1;
  93. var moff = "A".charCodeAt(0);
  94. // var res = {};
  95. var errres = { "type": 'error', error: 'Bad reference', next: null };
  96. var state = 0;
  97. for (i = 0; i < cellRef.length; i++) {
  98. var c = cellRef[i];
  99. if (c == '$')
  100. continue;
  101. if (state < 2 && c >= 'A' && c <= 'Z') {
  102. state = 1;
  103. col = col * radix + c.charCodeAt(0) - moff;
  104. }
  105. else if (state >= 1 && state <= 2 && c >= '0' && c <= '9') {
  106. state = 2;
  107. row = row * 10 + c.charCodeAt(0) - "0".charCodeAt(0);
  108. }
  109. else
  110. return errres;
  111. }
  112. if (state < 2 || row < 1 || col < 0)
  113. return errres;
  114. return { "type": 'coord', row: row-1, col: col };
  115. };
  116. //
  117. // Get the value from row and col variables
  118. // Row and col is 0-based
  119. //
  120. var getData = function (row, col) {
  121. var c;
  122. var val;
  123. var res = { "type": '', token: null, next: null };
  124. try {
  125. // val = container.handsontable('getData')[row][col];
  126. val = instance.getData()[row][col];
  127. }
  128. catch(err) {
  129. return { "type": 'error', error: 'Bad reference', next: null };
  130. }
  131. if (val == null || val == '')
  132. return res;
  133. c = val[0];
  134. if (c == "'") {
  135. res.type = 'text';
  136. res.token = val.substring(1) + '';
  137. return res;
  138. }
  139. if (c == "=") {
  140. return evaluateFormula(instance,val.substring(1));
  141. }
  142. if (c == '-' || c == '+' || c == '.' || (c >= '0' && c <= '9')) { // Numeric constant
  143. res.type = 'number';
  144. res.token = (val * 1.0);
  145. return res;
  146. }
  147. // Else, we treat it as text anyway
  148. res.type = 'text';
  149. res.token = val + '';
  150. return res;
  151. };
  152. //
  153. // Get the value of a cell, referenced by a string as A1 or ABD3092
  154. // Returns a token with the result, or an error if we cannot comply
  155. //
  156. var getCell = function (cellRef) {
  157. var thiserr;
  158. var coord = getRC(cellRef);
  159. if (coord.type == 'error') {
  160. thiserr = coord.error != null && coord.error != '' ? coord.error : 'Bad reference';
  161. return { "type": 'error', error: thiserr, next: null };
  162. }
  163. return getData(coord.row, coord.col);
  164. };
  165. //
  166. // Take the partial formula in the string
  167. // and give us the next token from it
  168. // swallowing the charcters of that token
  169. // in the process
  170. //
  171. var nextToken = function (formulaString) { // split the textstring into tokens one at a time
  172. var result = { "type": '', "token": null, "next": null};
  173. var i;
  174. var c;
  175. var l;
  176. if (formulaString == null || formulaString == '')
  177. return result;
  178. while (formulaString.length > 0 && formulaString[0] == ' ')
  179. formulaString = formulaString.substring(1);
  180. if (formulaString == null || formulaString == '')
  181. return result;
  182. l = formulaString.length;
  183. c = formulaString[0].toUpperCase();
  184. if (c == '-' || c == '+' || c == '*' || c == '/') {
  185. result = { "type": "operator", token: c, next: formulaString.substring(1) };
  186. }
  187. else if (c == '(' || c == ')') {
  188. result = { "type": "paranthes", token: c, next: formulaString.substring(1) };
  189. }
  190. else if (c == ':') {
  191. result = { "type": "range", token: c, next: formulaString.substring(1) };
  192. }
  193. else if (c == ';') {
  194. result = { "type": "param", token: c, next: formulaString.substring(1) };
  195. }
  196. else if ((c >= '0' && c <= '9') || c == '.') {
  197. var val = 0.0;
  198. var part = 0.0;
  199. var state = 0;
  200. var div = 1.0;
  201. for (i = 0; i < formulaString.length && ((formulaString[i] >= '0' && formulaString[i] <= '9') || formulaString[i] == '.'); i++) {
  202. c = formulaString[i];
  203. if (state == 0 && c == '.')
  204. state = 1;
  205. else if (state == 0)
  206. val = val * 10 + formulaString.charCodeAt(i) - "0".charCodeAt(0);
  207. else if (state == 1 && c == '.')
  208. return { "type": 'error', error: 'Bad munber', next: null };
  209. else if (state == 1) {
  210. div /= 10.0;
  211. part = part + (formulaString.charCodeAt(i) - "0".charCodeAt(0)) * div;
  212. }
  213. }
  214. result = { "type": "number", token: val + part, next: formulaString.substring(i) };
  215. }
  216. else if (c == '$' || (c >= 'A' && c <= 'Z')) { // See if it is a cell reference
  217. var ref = '';
  218. for (i = 0; i < formulaString.length && (formulaString[i] == '$' || (formulaString[i] >= '0' && formulaString[i] <= '9') || (formulaString[i].toUpperCase() >= 'A' && formulaString[i].toUpperCase() <= 'Z')); i++) {
  219. c = formulaString[i].toUpperCase();
  220. ref = ref + c;
  221. }
  222. if (i < formulaString.length && formulaString[i] == '(') {
  223. ref = ref.toLowerCase(); // Reformat the function names to lower case!!!!!
  224. result.type = 'func';
  225. }
  226. else
  227. result.type = 'cell';
  228. result.token = ref; // NOTE: Keep the upper case for cell references!!!!!
  229. result.next = formulaString.substring(i);
  230. }
  231. else if (c == '=' || c == '<' || c == '>') { // See if it is a relation that produces a boolean
  232. var c2 = null;
  233. if (l == 1) {
  234. result = { "type": "relation", token: c, next: formulaString.substring(1) };
  235. }
  236. else {
  237. c2 = formulaString[1];
  238. if (c2 == '=' || c2 == '<' || c2 == '>') {
  239. result = { "type": "relation", token: c+c2, next: formulaString.substring(2) };
  240. }
  241. else
  242. result = { "type": "relation", token: c, next: formulaString.substring(1) };
  243. }
  244. }
  245. else if (c == '"') {
  246. var str = ''
  247. var i;
  248. for (i = 1; i < formulaString.length && formulaString[i] != '"'; i++) {
  249. c = formulaString[i];
  250. if (c == '\\') {
  251. i++;
  252. c = formulaString[i];
  253. }
  254. str = str + c;
  255. }
  256. if (i == formulaString.length || formulaString[i] != '"')
  257. return { "type": 'error', error: 'Unbalanced string', next: null };
  258. result = { "type": "text", token: str, next: formulaString.substring(i+1) };
  259. }
  260. else { // Error or unknown token
  261. result.type = 'error';
  262. result.error = 'Bad token ' + c;
  263. }
  264. return result;
  265. };
  266. //
  267. // When all parameters that uses the cell name are done in a function,
  268. // We comes here and evaluates all the rest of the parameters,
  269. // Giving all the values after each other in the array.
  270. // This makes it possible to handle the array as a collection of values
  271. // as all cells, functions and operators have been dealt with.
  272. //
  273. var fixParams = function (tokens) {
  274. var i;
  275. var s = 0;
  276. var res = {};
  277. for (i = 0; i < tokens.length; i++) {
  278. if (tokens[i].type == 'param') {
  279. if (s == i) { // A null parameter.
  280. tokens[i].type = '';
  281. tokens[i].token = null;
  282. s++;
  283. continue;
  284. }
  285. res = evaluateTokens(tokens.slice(s,i));
  286. tokens.splice(s,i-s+1,res);
  287. s++;
  288. i = s - 1;
  289. }
  290. }
  291. if (s < tokens.length) {
  292. res = evaluateTokens(tokens.slice(s,i));
  293. tokens.splice(s,i-s+1,res);
  294. }
  295. return tokens;
  296. }
  297. //
  298. //
  299. // The function block
  300. // Here we have all the functions that can be used within the formulas.
  301. // They need to be in lowercase as the formula give us the names that way.
  302. //
  303. //
  304. // Sums all the parameters as well as all the ranges within
  305. //
  306. //
  307. var funcSum = function (tokens) {
  308. var i;
  309. var sum = 0.0;
  310. var res = {};
  311. for (i = 0; i < tokens.length; i++)
  312. if (tokens[i].type == 'range') {
  313. var topleft, bottomright;
  314. var r, c;
  315. if (i == 0 || i == tokens.length - 1) { // Range error
  316. return { "type": 'error', error: 'Bad range', next: null };
  317. }
  318. topleft = getRC(tokens[i-1].token);
  319. bottomright = getRC(tokens[i+1].token);
  320. sum = 0.0;
  321. for (r = topleft.row; r <= bottomright.row; r++)
  322. for (c = topleft.col; c <= bottomright.col; c++) {
  323. res = getData(r,c);
  324. if (res.type == 'error')
  325. return res;
  326. if (res.type == 'number' || res.type == 'boolean')
  327. sum += (res.token * 1.0);
  328. }
  329. tokens.splice(i-1,3,{ "type": 'number', token: sum, next: null});
  330. i = i - 2;
  331. continue;
  332. }
  333. tokens = fixParams(tokens); // evaluate all parameters
  334. sum = 0.0;
  335. for (i = 0; i < tokens.length; i++)
  336. if (tokens[i].type == 'number' || tokens[i].type == 'boolean')
  337. sum += (tokens[i].token * 1.0);
  338. return { "type": 'number', token: sum, next: null };
  339. };
  340. //
  341. // Count all parameters that have a value as well as all ranges
  342. //
  343. var funcCount = function (tokens) {
  344. var i;
  345. var count = null;
  346. var res = {};
  347. for (i = 0; i < tokens.length; i++)
  348. if (tokens[i].type == 'range') {
  349. var topleft, bottomright;
  350. var r, c;
  351. if (i == 0 || i == tokens.length - 1) { // Range error
  352. return { "type": 'error', error: 'Bad range', next: null };
  353. }
  354. topleft = getRC(tokens[i-1].token);
  355. bottomright = getRC(tokens[i+1].token);
  356. for (r = topleft.row; r <= bottomright.row; r++)
  357. for (c = topleft.col; c <= bottomright.col; c++) {
  358. res = getData(r,c);
  359. if (res.type == 'error')
  360. return res;
  361. if (res.type == 'number' || res.type == 'boolean') {
  362. if (count == null)
  363. count = 0;
  364. count++;
  365. }
  366. }
  367. tokens.splice(i-1,3); // Remove range itself as we have just counted all within
  368. i = i - 2;
  369. continue;
  370. }
  371. tokens = fixParams(tokens); // evaluate all parameters
  372. for (i = 0; i < tokens.length; i++)
  373. if (tokens[i].type == 'number' || tokens[i].type == 'boolean') {
  374. if (count == null)
  375. count = 0;
  376. count++;
  377. }
  378. return { "type": count == null ? '' : 'number', token: count, next: null };
  379. }
  380. //
  381. // Find the average of all parameters that have a value as well as all ranges
  382. // If we have no values, this function gives 'NULL', ie blank box
  383. //
  384. var funcAvg = function (tokens) {
  385. var i;
  386. var count = null;
  387. var sum = null;
  388. var myavg = null;
  389. var res = {};
  390. for (i = 0; i < tokens.length; i++)
  391. if (tokens[i].type == 'range') {
  392. var topleft, bottomright;
  393. var r, c;
  394. if (i == 0 || i == tokens.length - 1) { // Range error
  395. return { "type": 'error', error: 'Bad range', next: null };
  396. }
  397. topleft = getRC(tokens[i-1].token);
  398. bottomright = getRC(tokens[i+1].token);
  399. for (r = topleft.row; r <= bottomright.row; r++)
  400. for (c = topleft.col; c <= bottomright.col; c++) {
  401. res = getData(r,c);
  402. if (res.type == 'error')
  403. return res;
  404. if (res.type == 'number' || res.type == 'boolean') {
  405. if (count == null) {
  406. count = 0;
  407. sum = 0.0;
  408. }
  409. count++;
  410. sum += (res.token * 1.0);
  411. }
  412. }
  413. tokens.splice(i-1,3); // Remove range itself as we have just counted all within
  414. i = i - 2;
  415. continue;
  416. }
  417. tokens = fixParams(tokens); // evaluate all parameters
  418. for (i = 0; i < tokens.length; i++)
  419. if (tokens[i].type == 'number' || tokens[i].type == 'boolean') {
  420. if (count == null) {
  421. count = 0;
  422. sum = 0.0;
  423. }
  424. count++;
  425. sum += (tokens[i].token * 1.0);
  426. }
  427. if (count > 0)
  428. myavg = (sum * 1.0) / count;
  429. return { "type": myavg == null ? '' : 'number', token: myavg, next: null };
  430. }
  431. //
  432. // Find Minimum among all parameters that have a value as well as all ranges
  433. //
  434. var funcMin = function (tokens) {
  435. var i;
  436. var mymin = null;
  437. var res = {};
  438. for (i = 0; i < tokens.length; i++)
  439. if (tokens[i].type == 'range') {
  440. var topleft, bottomright;
  441. var r, c;
  442. if (i == 0 || i == tokens.length - 1) { // Range error
  443. return { "type": 'error', error: 'Bad range', next: null };
  444. }
  445. topleft = getRC(tokens[i-1].token);
  446. bottomright = getRC(tokens[i+1].token);
  447. for (r = topleft.row; r <= bottomright.row; r++)
  448. for (c = topleft.col; c <= bottomright.col; c++) {
  449. res = getData(r,c);
  450. if (res.type == 'error')
  451. return res;
  452. if (res.type == 'number' || res.type == 'boolean') {
  453. if (mymin == null)
  454. mymin = res.token;
  455. if (mymin > res.token)
  456. mymin = res.token;
  457. }
  458. }
  459. tokens.splice(i-1,3); // Remove range itself as we have just counted all within
  460. i = i - 2;
  461. continue;
  462. }
  463. tokens = fixParams(tokens); // evaluate all parameters
  464. for (i = 0; i < tokens.length; i++)
  465. if (tokens[i].type == 'number' || tokens[i].type == 'boolean') {
  466. if (mymin == null)
  467. mymin = tokens[i].token;
  468. if (mymin > tokens[i].token)
  469. mymin = tokens[i].token;
  470. }
  471. return { "type": mymin == null ? '' : 'number', token: mymin, next: null };
  472. }
  473. //
  474. // Find Maximum among all parameters that have a value as well as all ranges
  475. //
  476. var funcMax = function (tokens) {
  477. var i;
  478. var mymax = null;
  479. var res = {};
  480. for (i = 0; i < tokens.length; i++)
  481. if (tokens[i].type == 'range') {
  482. var topleft, bottomright;
  483. var r, c;
  484. if (i == 0 || i == tokens.length - 1) { // Range error
  485. return { "type": 'error', error: 'Bad range', next: null };
  486. }
  487. topleft = getRC(tokens[i-1].token);
  488. bottomright = getRC(tokens[i+1].token);
  489. for (r = topleft.row; r <= bottomright.row; r++)
  490. for (c = topleft.col; c <= bottomright.col; c++) {
  491. res = getData(r,c);
  492. if (res.type == 'error')
  493. return res;
  494. if (res.type == 'number' || res.type == 'boolean') {
  495. if (mymax == null)
  496. mymax = res.token;
  497. if (mymax < res.token)
  498. mymax = res.token;
  499. }
  500. }
  501. tokens.splice(i-1,3); // Remove range itself as we have just counted all within
  502. i = i - 2;
  503. continue;
  504. }
  505. tokens = fixParams(tokens); // evaluate all parameters
  506. for (i = 0; i < tokens.length; i++)
  507. if (tokens[i].type == 'number' || tokens[i].type == 'boolean') {
  508. if (mymax == null)
  509. mymax = tokens[i].token;
  510. if (mymax < tokens[i].token)
  511. mymax = tokens[i].token;
  512. }
  513. return { "type": mymax == null ? '' : 'number', token: mymax, next: null };
  514. }
  515. //
  516. // Logical AND among all parameters that have a value as well as all ranges
  517. //
  518. var funcAnd = function (tokens) {
  519. var i;
  520. var mycond = null;
  521. var res = {};
  522. for (i = 0; i < tokens.length; i++)
  523. if (tokens[i].type == 'range') {
  524. var topleft, bottomright;
  525. var r, c;
  526. if (i == 0 || i == tokens.length - 1) { // Range error
  527. return { "type": 'error', error: 'Bad range', next: null };
  528. }
  529. topleft = getRC(tokens[i-1].token);
  530. bottomright = getRC(tokens[i+1].token);
  531. for (r = topleft.row; r <= bottomright.row; r++)
  532. for (c = topleft.col; c <= bottomright.col; c++) {
  533. res = getData(r,c);
  534. if (res.type == 'error')
  535. return res;
  536. if (res.type == 'number' || res.type == 'boolean') {
  537. if (mycond == null)
  538. mycond = !!res.token;
  539. mycond = (mycond && !!res.token);
  540. }
  541. }
  542. tokens.splice(i-1,3); // Remove range itself as we have just counted all within
  543. i = i - 2;
  544. continue;
  545. }
  546. tokens = fixParams(tokens); // evaluate all parameters
  547. for (i = 0; i < tokens.length; i++)
  548. if (tokens[i].type == 'number' || tokens[i].type == 'boolean') {
  549. if (mycond == null)
  550. mycond = !!tokens[i].token;
  551. mycond = (mycond && !!tokens[i].token);
  552. }
  553. return { "type": mycond == null ? '' : 'boolean', token: mycond, next: null };
  554. }
  555. //
  556. // Logical OR among all parameters that have a value as well as all ranges
  557. //
  558. var funcOr = function (tokens) {
  559. var i;
  560. var mycond = null;
  561. var res = {};
  562. for (i = 0; i < tokens.length; i++)
  563. if (tokens[i].type == 'range') {
  564. var topleft, bottomright;
  565. var r, c;
  566. if (i == 0 || i == tokens.length - 1) { // Range error
  567. return { "type": 'error', error: 'Bad range', next: null };
  568. }
  569. topleft = getRC(tokens[i-1].token);
  570. bottomright = getRC(tokens[i+1].token);
  571. for (r = topleft.row; r <= bottomright.row; r++)
  572. for (c = topleft.col; c <= bottomright.col; c++) {
  573. res = getData(r,c);
  574. if (res.type == 'error')
  575. return res;
  576. if (res.type == 'number' || res.type == 'boolean') {
  577. if (mycond == null)
  578. mycond = !!res.token;
  579. mycond = (mycond || !!res.token);
  580. }
  581. }
  582. tokens.splice(i-1,3); // Remove range itself as we have just counted all within
  583. i = i - 2;
  584. continue;
  585. }
  586. tokens = fixParams(tokens); // evaluate all parameters
  587. for (i = 0; i < tokens.length; i++)
  588. if (tokens[i].type == 'number' || tokens[i].type == 'boolean') {
  589. if (mycond == null)
  590. mycond = !!tokens[i].token;
  591. mycond = (mycond || !!tokens[i].token);
  592. }
  593. return { "type": mycond == null ? '' : 'boolean', token: mycond, next: null };
  594. }
  595. //
  596. // Logical XOR among all parameters that have a value as well as all ranges
  597. //
  598. var funcXor = function (tokens) {
  599. var i;
  600. var mycond = null;
  601. var res = {};
  602. for (i = 0; i < tokens.length; i++)
  603. if (tokens[i].type == 'range') {
  604. var topleft, bottomright;
  605. var r, c;
  606. if (i == 0 || i == tokens.length - 1) { // Range error
  607. return { "type": 'error', error: 'Bad range', next: null };
  608. }
  609. topleft = getRC(tokens[i-1].token);
  610. bottomright = getRC(tokens[i+1].token);
  611. for (r = topleft.row; r <= bottomright.row; r++)
  612. for (c = topleft.col; c <= bottomright.col; c++) {
  613. res = getData(r,c);
  614. if (res.type == 'error')
  615. return res;
  616. if (res.type == 'number' || res.type == 'boolean') {
  617. if (mycond == null)
  618. mycond = 0;
  619. mycond = !!(mycond ^ !!res.token);
  620. }
  621. }
  622. tokens.splice(i-1,3); // Remove range itself as we have just counted all within
  623. i = i - 2;
  624. continue;
  625. }
  626. tokens = fixParams(tokens); // evaluate all parameters
  627. for (i = 0; i < tokens.length; i++)
  628. if (tokens[i].type == 'number' || tokens[i].type == 'boolean') {
  629. if (mycond == null)
  630. mycond = 0;
  631. mycond = !!(mycond ^ !!tokens[i].token);
  632. }
  633. return { "type": mycond == null ? '' : 'boolean', token: mycond, next: null };
  634. }
  635. //
  636. // Logical NOT among all parameters that have a value as well as all ranges
  637. //
  638. var funcNot = function (tokens) {
  639. var i;
  640. var mycond = null;
  641. var res = {};
  642. for (i = 0; i < tokens.length; i++)
  643. if (tokens[i].type == 'range') {
  644. var topleft, bottomright;
  645. var r, c;
  646. if (i == 0 || i == tokens.length - 1) { // Range error
  647. return { "type": 'error', error: 'Bad range', next: null };
  648. }
  649. topleft = getRC(tokens[i-1].token);
  650. bottomright = getRC(tokens[i+1].token);
  651. for (r = topleft.row; r <= bottomright.row; r++)
  652. for (c = topleft.col; c <= bottomright.col; c++) {
  653. res = getData(r,c);
  654. if (res.type == 'error')
  655. return res;
  656. if (res.type == 'number' || res.type == 'boolean') {
  657. mycond = !res.token;
  658. }
  659. }
  660. tokens.splice(i-1,3); // Remove range itself as we have just counted all within
  661. i = i - 2;
  662. continue;
  663. }
  664. tokens = fixParams(tokens); // evaluate all parameters
  665. for (i = 0; i < tokens.length; i++)
  666. if (tokens[i].type == 'number' || tokens[i].type == 'boolean') {
  667. mycond = !tokens[i].token;
  668. }
  669. return { "type": mycond == null ? '' : 'boolean', token: mycond, next: null };
  670. }
  671. //
  672. // IF have two or three parameters. First must evaluate to a boolean,
  673. // The second is evaluated if the first is true.
  674. // If the third exists, it is evaluated if the first is false.
  675. //
  676. var funcIf = function (tokens) {
  677. var res = {};
  678. tokens = fixParams(tokens); // evaluate all parameters
  679. if (tokens.length < 2)
  680. return { "type": 'error', error: 'To few parameters for IF', next: null };
  681. res = tokens[0];
  682. if (!(res.type == 'boolean' || res.type == 'number')) {
  683. return { "type": 'error', error: 'First parameter for IF must be boolean', next: null };
  684. }
  685. if (!!res.token)
  686. return tokens[1];
  687. else
  688. if (tokens.length < 3)
  689. return { "type": '', token: null, next: null };
  690. else
  691. return tokens[2];
  692. }
  693. //
  694. // Concatenate all parameters that have a value as well as all ranges
  695. // The result is a text or null.
  696. //
  697. var funcConcat = function (tokens) {
  698. var i;
  699. var tempstr = null;
  700. var res = {};
  701. for (i = 0; i < tokens.length; i++)
  702. if (tokens[i].type == 'range') {
  703. var topleft, bottomright;
  704. var r, c;
  705. var tempstr = null;
  706. if (i == 0 || i == tokens.length - 1) { // Range error
  707. return { "type": 'error', error: 'Bad range', next: null };
  708. }
  709. topleft = getRC(tokens[i-1].token);
  710. bottomright = getRC(tokens[i+1].token);
  711. for (r = topleft.row; r <= bottomright.row; r++)
  712. for (c = topleft.col; c <= bottomright.col; c++) {
  713. res = getData(r,c);
  714. if (res.type == 'error')
  715. return res;
  716. if (res.type == '' || res.token == null)
  717. continue;
  718. if (tempstr == null)
  719. tempstr = '';
  720. tempstr = tempstr + res.token + '';
  721. }
  722. res = { "type": tempstr == null ? '' : 'text', token: tempstr, next: null };
  723. tokens.splice(i-1,3,res); // Remove range itself and place the concated string there
  724. i = i - 2;
  725. continue;
  726. }
  727. tokens = fixParams(tokens); // evaluate all parameters
  728. for (i = 0; i < tokens.length; i++) {
  729. if (tokens[i].type == '' || tokens[i].token == null)
  730. continue;
  731. if (tempstr == null)
  732. tempstr = '';
  733. tempstr = tempstr + tokens[i].token + '';
  734. }
  735. return { "type": tempstr == null ? '' : 'text', token: tempstr, next: null };
  736. }
  737. //
  738. // NOTE: All function names are in lower case!!!!!
  739. //
  740. var funcs = [
  741. { func: 'sum', cb: funcSum }
  742. ,{ func: 'count', cb: funcCount }
  743. ,{ func: 'if', cb: funcIf }
  744. ,{ func: 'avg', cb: funcAvg }
  745. ,{ func: 'min', cb: funcMin }
  746. ,{ func: 'max', cb: funcMax }
  747. ,{ func: 'and', cb: funcAnd }
  748. ,{ func: 'or', cb: funcOr }
  749. ,{ func: 'xor', cb: funcXor }
  750. ,{ func: 'not', cb: funcNot }
  751. ,{ func: 'concat', cb: funcConcat }
  752. ];
  753. var evaluateFunc = function (tokens) {
  754. var i;
  755. for (i = 0; i < funcs.length; i++)
  756. if (funcs[i].func == tokens[0].token)
  757. return funcs[i].cb(tokens.slice(2, tokens.length));
  758. return { "type": 'error', error: 'No such function ' + tokens[0].token, next: null };
  759. };
  760. //
  761. // Some support functions to evalTokens:
  762. // fixBoolean: makes boolean value forcefully either numeric 0 or 1.
  763. // A null value is treated as a 0
  764. //
  765. // fixText: if the first token is a text and the other a number,
  766. // see if the text has digits as first chars. If so,
  767. // make the text a number, else make the number a text
  768. //
  769. var fixBoolean = function(tokens, col) {
  770. if (tokens[col].type == '' || tokens[col].type == 'boolean') {
  771. var term = tokens[col].token;
  772. tokens[col].type = 'number';
  773. if (typeof term === 'undefined' || term == null || term == '')
  774. tokens[col].token = 0.0;
  775. }
  776. };
  777. var fixOneCol = function (tokens, col) {
  778. var term = tokens[col].token;
  779. if (typeof term === 'undefined' || term == null || term == '') {
  780. tokens[col].type = 'number';
  781. tokens[col].token = 0.0;
  782. }
  783. else {
  784. var c = tokens[col].token[0];
  785. if (c == '-' || c == '+' || (c >= '0' && c <= '9')) {
  786. tokens[col].type = 'number';
  787. tokens[col].token = (1.0 * tokens[col].token);
  788. }
  789. }
  790. };
  791. var fixText = function (tokens, col1, col2) {
  792. if (tokens[col1].type == 'text' && tokens[col2].type == 'number')
  793. fixOneCol(tokens,col1);
  794. else if (tokens[col2].type == 'text' && tokens[col1].type == 'number')
  795. fixOneCol(tokens,col2);
  796. };
  797. //
  798. // Make a recursive loop through the array of tokens
  799. // to evaluate it by collapse the array when we find something to evaluate.
  800. // The result is at the first index of the collapsed array
  801. //
  802. // () and functions have the highest priority.
  803. // that mean that the parameters to the function isn't neseccary evaluated fully
  804. // we need to have it that way so that we can evaluate 'range' and functions
  805. // that use the cell references not the values they point to. For instance
  806. // COLUMN(A3) should give '3' as result and ROW(A3) should give '1'.
  807. // but SUM(A1:B2) should iterate all indicated cells to sum the values of them.
  808. // The above gives A1, A2, B1, B2 as cells to sum the values from.
  809. // If any of them contails a formula, it is reevaluated on the spot, returning
  810. // the result of that formula before the 'SUM' continues.
  811. //
  812. // A2 When all paranthesis and functions have been evaluated,
  813. // the rest of the cell references are evaluated and exchanged for their values.
  814. // If any value is a formula, it is reevaluated here to give the current value
  815. // of that formula.
  816. //
  817. // Note that we can have a deadlock!!!!!! formulas are not marked as evaluated
  818. // this sequence, so be careful.
  819. //
  820. // * / comes next in priority
  821. // - Unary minus is evaluated next. Also unary '+' goes here.
  822. // + - Have the lowest priority
  823. //
  824. var evaluateTokens = function (tokens) {
  825. var i, j;
  826. var found = 0;
  827. for (i = tokens.length - 1; i >= 0; i--)
  828. if (tokens[i].type == 'paranthes' && tokens[i].token == '(') {
  829. for (j = i; j < tokens.length; j++)
  830. if (tokens[j].type == 'paranthes' && tokens[j].token == ')') {
  831. var res;
  832. if (i > 0 && tokens[i-1].type == 'func') {
  833. i--;
  834. res = evaluateFunc(tokens.slice(i,j)); // Evaluate function
  835. }
  836. else
  837. res = evaluateTokens(tokens.slice(i+1,j)); // Evaluate formula within ()
  838. tokens.splice(i,j-i+1,res);
  839. found = 1;
  840. break;
  841. }
  842. if (!found) {
  843. return { "type": 'error', error: 'Unbalanced paranthesis', next: null };
  844. }
  845. i = tokens.length; // Restart search for paranthesis
  846. found = 0;
  847. continue;
  848. }
  849. for (i = 0; i < tokens.length; i++)
  850. if (tokens[i].type == 'cell') {
  851. var res = getCell(tokens[i].token); // Replace reference with the value
  852. tokens.splice(i,1,res);
  853. }
  854. for (i = 1; i < tokens.length - 1; i++) // Evaluate * /
  855. if (tokens[i].type == 'operator' && (tokens[i].token == '*' || tokens[i].token == '/')) {
  856. var res = { "type": 'number', "token": 0.0, "next": null };
  857. fixBoolean(tokens, i-1);
  858. fixBoolean(tokens, i+1);
  859. fixText(tokens, i-1, i+1);
  860. if (tokens[i-1].type != 'number' || tokens[i+1].type != 'number')
  861. return { "type": 'error', "error": 'Bad formula', "next": null };
  862. if (tokens[i].token == '*')
  863. res.token = tokens[i-1].token * tokens[i+1].token;
  864. else
  865. res.token = tokens[i-1].token / tokens[i+1].token;
  866. tokens.splice(i-1,3,res);
  867. i = i - 2;
  868. continue;
  869. }
  870. // Unary + and -
  871. if (tokens[0].type == 'operator' && (tokens[0].token == '-' || tokens[0].token == '+')) {
  872. var res = { "type": 'number', "token": 0.0, "next": null };
  873. if (tokens.length > 1) {
  874. fixBoolean(tokens, 1);
  875. if (tokens[1].type == 'text')
  876. fixOneCol(tokens,1);
  877. }
  878. if (tokens.length > 1 && tokens[1].type != 'number')
  879. return { "type": 'error', "error": 'Bad formula', "next": null };
  880. if (tokens[0].token == '-')
  881. res.token = -tokens[1].token;
  882. else
  883. res.token = tokens[1].token;
  884. tokens.splice(0,2,res);
  885. }
  886. for (i = 1; i < tokens.length - 1; i++) // almost lastly evaluate + -
  887. if (tokens[i].type == 'operator' && (tokens[i].token == '-' || tokens[i].token == '+')) {
  888. var res = { "type": 'number', "token": 0.0, "next": null };
  889. fixBoolean(tokens, i-1);
  890. fixBoolean(tokens, i+1);
  891. fixText(tokens, i-1, i+1);
  892. if (tokens[i-1].type != 'number' || tokens[i+1].type != 'number')
  893. return { "type": 'error', "error": 'Bad formula', "next": null };
  894. if (tokens[i].token == '+')
  895. res.token = tokens[i-1].token + tokens[i+1].token;
  896. else
  897. res.token = tokens[i-1].token - tokens[i+1].token;
  898. tokens.splice(i-1,3,res);
  899. i = i - 2;
  900. continue;
  901. }
  902. for (i = 1; i < tokens.length - 1; i++) // lastly evaluate relation = > < <> <= >=
  903. if (tokens[i].type == 'relation') {
  904. var res = { "type": 'boolean', "token": 0, "next": null };
  905. fixBoolean(tokens,i-1);
  906. fixBoolean(tokens,i+1);
  907. fixText(tokens,i-1,i+1);
  908. if (tokens[i-1].type != tokens[i+1].type || tokens[i].token == '==')
  909. return { "type": 'error', "error": 'Bad formula', "next": null };
  910. var term1 = tokens[i-1].token;
  911. var term2 = tokens[i+1].token;
  912. var relation = tokens[i].token;
  913. if (relation == '=')
  914. res.token = (term1 == term2);
  915. else if (relation == '<>' || relation == '><')
  916. res.token = (term1 != term2);
  917. else if (relation == '>=' || relation == '=>')
  918. res.token = (term1 >= term2);
  919. else if (relation == '<=' || relation == '=<')
  920. res.token = (term1 <= term2);
  921. else if (relation == '<')
  922. res.token = (term1 < term2);
  923. else if (relation == '>')
  924. res.token = (term1 > term2);
  925. tokens.splice(i-1,3,res);
  926. i = i - 2;
  927. continue;
  928. }
  929. if (tokens.length == 1) // If we just have one value, say it's ok and continue
  930. return tokens[0];
  931. return { "type": 'error', "error": 'Bad formula', "next": null };
  932. }
  933. //
  934. // Body of evaluateFormula
  935. //
  936. var theToken = {};
  937. var tokens = [];
  938. theToken = nextToken(formula);
  939. while (theToken.type != 'error' && theToken.next != '') {
  940. tokens.push(theToken);
  941. theToken = nextToken(theToken.next);
  942. }
  943. if (theToken.type == 'error')
  944. return theToken;
  945. tokens.push(theToken);
  946. return evaluateTokens(tokens);
  947. };
  948. function initExcelCell() {
  949. var instance = this
  950. ,settings = instance.getSettings()
  951. ,orgtextcellrender
  952. ,orgcelltypestxt;
  953. if (!settings.useFormula)
  954. return;
  955. orgcelltypestxt = Handsontable.cellTypes.text;
  956. Handsontable.cellTypes.text = Handsontable._TextCell;
  957. orgtextcellrender = Handsontable.TextCell.renderer;
  958. Handsontable.TextCell.renderer = Handsontable.ExcelRenderer;
  959. }
  960. /**
  961. * original TextCell
  962. Handsontable.TextCell = {
  963. renderer: Handsontable.TextRenderer,
  964. editor: Handsontable.TextEditor
  965. };
  966. */
  967. Handsontable._TextCell = {
  968. renderer: Handsontable.TextRenderer,
  969. editor: Handsontable.TextEditor
  970. };
  971. /**
  972. * original cellTypes
  973. //here setup the friendly aliases that are used by cellProperties.type
  974. Handsontable.cellTypes = {
  975. autocomplete: Handsontable.AutocompleteCell,
  976. checkbox: Handsontable.CheckboxCell,
  977. text: Handsontable.TextCell,
  978. numeric: Handsontable.NumericCell,
  979. date: Handsontable.DateCell
  980. }
  981. */
  982. /**
  983. * original NumericRender.
  984. * We needs to make it somewhat more complicated so that there are three formats to
  985. * choose from
  986. Handsontable.NumericRenderer = function (instance, td, row, col, prop, value, cellProperties) {
  987. if (typeof value === 'number') {
  988. if (typeof cellProperties.language !== 'undefined') {
  989. numeral.language(cellProperties.language)
  990. }
  991. td.innerHTML = numeral(value).format(cellProperties.format || '0'); //docs: http://numeraljs.com/
  992. td.className = 'htNumeric';
  993. }
  994. else {
  995. Handsontable.TextRenderer(instance, td, row, col, prop, value, cellProperties);
  996. }
  997. };
  998. */
  999. //
  1000. // rewrite old numericrenderer so that it takes account for more different formats
  1001. //
  1002. var numericRenderer = Handsontable.NumericRenderer;
  1003. Handsontable.NumericRenderer = function (instance, td, row, col, prop, value, cellProperties) {
  1004. var myformat;
  1005. if (typeof value === 'number') {
  1006. if (typeof cellProperties.language !== 'undefined') {
  1007. numeral.language(cellProperties.language)
  1008. }
  1009. if (value == 0)
  1010. myformat = cellProperties.zeroformat;
  1011. else if (value < 0)
  1012. myformat = cellProperties.negativeformat;
  1013. else
  1014. myformat = cellProperties.format;
  1015. td.innerHTML = numeral(value).format(myformat || '0'); //docs: http://numeraljs.com/
  1016. td.className = 'htNumeric';
  1017. }
  1018. else {
  1019. Handsontable.TextRenderer(instance, td, row, col, prop, value, cellProperties);
  1020. }
  1021. };
  1022. Handsontable.ExcelRenderer = function (instance, td, row, col, prop, value, cellProperties) {
  1023. var c;
  1024. if (typeof value === 'undefined' || value === null || value == '') {
  1025. Handsontable._TextCell.renderer.apply(this, arguments);
  1026. return;
  1027. }
  1028. c = value[0];
  1029. if (c == "'") { // force value to a string, even things like numeric or formula
  1030. var newValue = value.substring(1);
  1031. $(td).css({
  1032. "text-align": 'left',
  1033. "background": 'none'
  1034. });
  1035. Handsontable._TextCell.renderer.apply(this, [ instance, td, row, col, prop, newValue, cellProperties ]);
  1036. }
  1037. else if (c == "=") { // Hmm, now we are cooked, boiled and fried. Trying to evaluate a formula
  1038. var newValue = null;
  1039. var theToken = evaluateFormula(instance,value.substring(1));
  1040. if (theToken.type == 'error') {
  1041. newValue = theToken.error;
  1042. $(td).css({
  1043. "text-align": 'left',
  1044. "background": '#FF8888'
  1045. });
  1046. Handsontable._TextCell.renderer.apply(this, [ instance, td, row, col, prop, newValue, cellProperties ]);
  1047. }
  1048. else if (theToken.type == 'number') {
  1049. newValue = theToken.token;
  1050. $(td).css({
  1051. "text-align": 'right',
  1052. "background": 'none'
  1053. });
  1054. Handsontable.NumericCell.renderer.apply(this, [ instance, td, row, col, prop, newValue, cellProperties ]);
  1055. }
  1056. else if (theToken.type == 'text') {
  1057. newValue = theToken.token;
  1058. $(td).css({
  1059. "text-align": 'left',
  1060. "background": 'none'
  1061. });
  1062. Handsontable._TextCell.renderer.apply(this, [ instance, td, row, col, prop, newValue, cellProperties ]);
  1063. }
  1064. else if (theToken.type == 'boolean') {
  1065. newValue = theToken.token ? "True" : "False";
  1066. $(td).css({
  1067. "text-align": 'left',
  1068. "background": 'none'
  1069. });
  1070. Handsontable._TextCell.renderer.apply(this, [ instance, td, row, col, prop, newValue, cellProperties ]);
  1071. }
  1072. else {
  1073. newValue = theToken.token;
  1074. $(td).css({
  1075. "text-align": 'left',
  1076. "background": 'none'
  1077. });
  1078. Handsontable._TextCell.renderer.apply(this, [ instance, td, row, col, prop, newValue, cellProperties ]);
  1079. }
  1080. }
  1081. else if (c == '-' || c == '+' || c == '.' || (c >= '0' && c <= '9')) { // Numeric constant
  1082. $(td).css({
  1083. "text-align": 'right',
  1084. "background": 'none'
  1085. });
  1086. Handsontable.NumericCell.renderer.apply(this, arguments);
  1087. }
  1088. else { // else probably a string
  1089. $(td).css({
  1090. "text-align": 'left',
  1091. "background": 'none'
  1092. });
  1093. Handsontable._TextCell.renderer.apply(this, arguments);
  1094. }
  1095. };
  1096. Handsontable.ExcelCell = {
  1097. renderer: Handsontable.ExcelRenderer,
  1098. editor: Handsontable.TextEditor,
  1099. dataType: 'excel'
  1100. };
  1101. // Add mapping of our type
  1102. Handsontable.cellTypes['excel'] = Handsontable.ExcelCell;
  1103. Handsontable.PluginHooks.push('afterInit', initExcelCell);
  1104. })(jQuery);