diff --git a/.npmignore b/.npmignore
index 90f7bb0..de46204 100644
--- a/.npmignore
+++ b/.npmignore
@@ -10,3 +10,4 @@ CNAME
index.html
robots.txt
testapp.js
+index.coffee
diff --git a/README.md b/README.md
index b293299..afefecb 100644
--- a/README.md
+++ b/README.md
@@ -10,12 +10,10 @@ Changes to the core js library live in the master branch.
npm install excel-formula
##Installation
- 1. Save excelFormulaUtilities-[version].js(For dev) or excelFormulaUtilities-[version].js.min (For prod).
- 2. Save excelFormulaUtilities.css if you'd like basic styling when outputting formulas to html.
- 3. Drop these files into your project.
-
+ 1. Grab excel-formula.js from dist/*
+
Check out the examples below or if you'd like to use the beautifier online click here http://excelformulabeautifier.com/
-
+
##Basic usage
This will return a formated formula in plain text.
@@ -32,17 +30,17 @@ To see this example check out ./examples/basic_example1/index.html
var formula_1 = 'SUM((A1:A10>=5)*(A1:A10<=10)*A1:A10)';
document.getElementById('fomatFormula_1').innerHTML = formula_1;
document.getElementById('fomatFormula_1_out').innerHTML = excelFormulaUtilities.formatFormula(formula_1);
-
+
//Beautify an Excel formula output to html.
var formula_2 = '=RIGHT(A2,LEN(A2)-FIND("*",SUBSTITUTE(A2," ","*",LEN(A2)-LEN(SUBSTITUTE(A2," ","")))))';
document.getElementById('fomatFormula_2').innerHTML = formula_2;
document.getElementById('fomatFormula_2_out').innerHTML = excelFormulaUtilities.formatFormulaHTML(formula_2);
-
+
//Convert an Excel formula to C#
var formula_3 = '=IF(A2:A3="YES","A2 equals yes", "A2 does not equal yes")';
document.getElementById('fomatFormula_3').innerHTML = formula_3;
document.getElementById('fomatFormula_3_out').innerHTML = excelFormulaUtilities.formula2CSharp(formula_3);
-
+
//Convert an Excel formula to JavaScript
var formula_4 = 'ADDRESS(ROW(DataRange2),COLUMN(DataRange2),4)&":"&ADDRESS(MAX((DataRange2<>"")*ROW(DataRange2)),COLUMN(DataRange2)+COLUMNS(DataRange2)-1,4)';
document.getElementById('fomatFormula_4').innerHTML = formula_4;
@@ -54,18 +52,18 @@ To see this example check out ./examples/basic_example1/index.html
excelFormulaUtilities.formatFormula( "" );
-
+
-
+
-
+
\ No newline at end of file
+
diff --git a/dist/excel-formula.js b/dist/excel-formula.js
new file mode 100644
index 0000000..e126233
--- /dev/null
+++ b/dist/excel-formula.js
@@ -0,0 +1,1366 @@
+/*
+ * excelFormulaUtilitiesJS
+ * https://github.com/joshatjben/excelFormulaUtilitiesJS/
+ *
+ * Copyright 2011, Josh Bennett
+ * licensed under the MIT license.
+ * https://github.com/joshatjben/excelFormulaUtilitiesJS/blob/master/LICENSE.txt
+ *
+ * Some functionality based off of the jquery core lib
+ * Copyright 2011, John Resig
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * Based on Ewbi's Go Calc Prototype Excel Formula Parser. [http://ewbi.blogs.com/develops/2004/12/excel_formula_p.html]
+ */
+(function (root) {
+ var excelFormulaUtilities = root.excelFormulaUtilities = root.excelFormulaUtilities || {},
+ core = root.excelFormulaUtilities.core,
+ formatStr = root.excelFormulaUtilities.string.formatStr,
+ trim = root.excelFormulaUtilities.string.trim,
+
+ types = {},
+ TOK_TYPE_NOOP = types.TOK_TYPE_NOOP = "noop",
+ TOK_TYPE_OPERAND = types.TOK_TYPE_OPERAND = "operand",
+ TOK_TYPE_FUNCTION = types.TOK_TYPE_FUNCTION = "function",
+ TOK_TYPE_SUBEXPR = types.TOK_TYPE_SUBEXPR = "subexpression",
+ TOK_TYPE_ARGUMENT = types.TOK_TYPE_ARGUMENT = "argument",
+ TOK_TYPE_OP_PRE = types.TOK_TYPE_OP_PRE = "operator-prefix",
+ TOK_TYPE_OP_IN = types.TOK_TYPE_OP_IN = "operator-infix",
+ TOK_TYPE_OP_POST = types.TOK_TYPE_OP_POST = "operator-postfix",
+ TOK_TYPE_WSPACE = types.TOK_TYPE_WSPACE = "white-space",
+ TOK_TYPE_UNKNOWN = types.TOK_TYPE_UNKNOWN = "unknown",
+
+ TOK_SUBTYPE_START = types.TOK_SUBTYPE_START = "start",
+ TOK_SUBTYPE_STOP = types.TOK_SUBTYPE_STOP = "stop",
+
+ TOK_SUBTYPE_TEXT = types.TOK_SUBTYPE_TEXT = "text",
+ TOK_SUBTYPE_NUMBER = types.TOK_SUBTYPE_NUMBER = "number",
+ TOK_SUBTYPE_LOGICAL = types.TOK_SUBTYPE_LOGICAL = "logical",
+ TOK_SUBTYPE_ERROR = types.TOK_SUBTYPE_ERROR = "error",
+ TOK_SUBTYPE_RANGE = types.TOK_SUBTYPE_RANGE = "range",
+
+ TOK_SUBTYPE_MATH = types.TOK_SUBTYPE_MATH = "math",
+ TOK_SUBTYPE_CONCAT = types.TOK_SUBTYPE_CONCAT = "concatenate",
+ TOK_SUBTYPE_INTERSECT = types.TOK_SUBTYPE_INTERSECT = "intersect",
+ TOK_SUBTYPE_UNION = types.TOK_SUBTYPE_UNION = "union";
+
+ root.excelFormulaUtilities.isEu = typeof root.excelFormulaUtilities.isEu === 'boolean' ? root.excelFormulaUtilities.isEu : false;
+
+
+ /**
+ * @class
+ */
+
+ function F_token(value, type, subtype) {
+ this.value = value;
+ this.type = type;
+ this.subtype = subtype;
+ }
+
+ /**
+ * @class
+ */
+
+ function F_tokens() {
+
+ this.items = [];
+
+ this.add = function (value, type, subtype) {
+ if (!subtype) {
+ subtype = "";
+ }
+ var token = new F_token(value, type, subtype);
+ this.addRef(token);
+ return token;
+ };
+ this.addRef = function (token) {
+ this.items.push(token);
+ };
+
+ this.index = -1;
+ this.reset = function () {
+ this.index = -1;
+ };
+ this.BOF = function () {
+ return (this.index <= 0);
+ };
+ this.EOF = function () {
+ return (this.index >= (this.items.length - 1));
+ };
+ this.moveNext = function () {
+ if (this.EOF()) {
+ return false;
+ }
+ this.index += 1;
+ return true;
+ };
+ this.current = function () {
+ if (this.index === -1) {
+ return null;
+ }
+ return (this.items[this.index]);
+ };
+ this.next = function () {
+ if (this.EOF()) {
+ return null;
+ }
+ return (this.items[this.index + 1]);
+ };
+ this.previous = function () {
+ if (this.index < 1) {
+ return null;
+ }
+ return (this.items[this.index - 1]);
+ };
+
+ }
+
+ function F_tokenStack() {
+
+ this.items = [];
+
+ this.push = function (token) {
+ this.items.push(token);
+ };
+ this.pop = function (name) {
+ var token = this.items.pop();
+ return (new F_token(name || "", token.type, TOK_SUBTYPE_STOP));
+ };
+
+ this.token = function () {
+ return ((this.items.length > 0) ? this.items[this.items.length - 1] : null);
+ };
+ this.value = function () {
+ return ((this.token()) ? this.token().value.toString() : "");
+ };
+ this.type = function () {
+ return ((this.token()) ? this.token().type.toString() : "");
+ };
+ this.subtype = function () {
+ return ((this.token()) ? this.token().subtype.toString() : "");
+ };
+
+ }
+
+ function getTokens(formula) {
+
+ var tokens = new F_tokens(),
+ tokenStack = new F_tokenStack(),
+
+ offset = 0,
+
+ currentChar = function () {
+ return formula.substr(offset, 1);
+ },
+ doubleChar = function () {
+ return formula.substr(offset, 2);
+ },
+ nextChar = function () {
+ return formula.substr(offset + 1, 1);
+ },
+ EOF = function () {
+ return (offset >= formula.length);
+ },
+
+ token = "",
+
+ inString = false,
+ inPath = false,
+ inRange = false,
+ inError = false,
+ regexSN = /^[1-9]{1}(\.[0-9]+)?E{1}$/;
+
+ while (formula.length > 0) {
+ if (formula.substr(0, 1) === " ") {
+ formula = formula.substr(1);
+ } else {
+ if (formula.substr(0, 1) === "=") {
+ formula = formula.substr(1);
+ }
+ break;
+ }
+ }
+
+
+
+ while (!EOF()) {
+
+ // state-dependent character evaluation (order is important)
+ // double-quoted strings
+ // embeds are doubled
+ // end marks token
+ if (inString) {
+ if (currentChar() === "\"") {
+ if (nextChar() === "\"") {
+ token += "\"";
+ offset += 1;
+ } else {
+ inString = false;
+ tokens.add(token, TOK_TYPE_OPERAND, TOK_SUBTYPE_TEXT);
+ token = "";
+ }
+ } else {
+ token += currentChar();
+ }
+ offset += 1;
+ continue;
+ }
+
+ // single-quoted strings (links)
+ // embeds are double
+ // end does not mark a token
+ if (inPath) {
+ if (currentChar() === "'") {
+
+ if (nextChar() === "'") {
+ token += "'";
+ offset += 1;
+ } else {
+ inPath = false;
+ token += "'";
+ }
+ } else {
+ token += currentChar();
+ }
+
+ offset += 1;
+ continue;
+ }
+
+ // bracked strings (range offset or linked workbook name)
+ // no embeds (changed to "()" by Excel)
+ // end does not mark a token
+ if (inRange) {
+ if (currentChar() === "]") {
+ inRange = false;
+ }
+ token += currentChar();
+ offset += 1;
+ continue;
+ }
+
+ // error values
+ // end marks a token, determined from absolute list of values
+ if (inError) {
+ token += currentChar();
+ offset += 1;
+ if ((",#NULL!,#DIV/0!,#VALUE!,#REF!,#NAME?,#NUM!,#N/A,").indexOf("," + token + ",") !== -1) {
+ inError = false;
+ tokens.add(token, TOK_TYPE_OPERAND, TOK_SUBTYPE_ERROR);
+ token = "";
+ }
+ continue;
+ }
+
+ // scientific notation check
+ if (("+-").indexOf(currentChar()) !== -1) {
+ if (token.length > 1) {
+ if (token.match(regexSN)) {
+ token += currentChar();
+ offset += 1;
+ continue;
+ }
+ }
+ }
+
+ // independent character evaulation (order not important)
+ // establish state-dependent character evaluations
+ if (currentChar() === "\"") {
+ if (token.length > 0) {
+ // not expected
+ tokens.add(token, TOK_TYPE_UNKNOWN);
+ token = "";
+ }
+ inString = true;
+ offset += 1;
+ continue;
+ }
+
+ if (currentChar() === "'") {
+ if (token.length > 0) {
+ // not expected
+ tokens.add(token, TOK_TYPE_UNKNOWN);
+ token = "";
+ }
+ token = "'"
+ inPath = true;
+ offset += 1;
+ continue;
+ }
+
+ if (currentChar() === "[") {
+ inRange = true;
+ token += currentChar();
+ offset += 1;
+ continue;
+ }
+
+ if (currentChar() === "#") {
+ if (token.length > 0) {
+ // not expected
+ tokens.add(token, TOK_TYPE_UNKNOWN);
+ token = "";
+ }
+ inError = true;
+ token += currentChar();
+ offset += 1;
+ continue;
+ }
+
+ // mark start and end of arrays and array rows
+ if (currentChar() === "{") {
+ if (token.length > 0) {
+ // not expected
+ tokens.add(token, TOK_TYPE_UNKNOWN);
+ token = "";
+ }
+ tokenStack.push(tokens.add("ARRAY", TOK_TYPE_FUNCTION, TOK_SUBTYPE_START));
+ tokenStack.push(tokens.add("ARRAYROW", TOK_TYPE_FUNCTION, TOK_SUBTYPE_START));
+ offset += 1;
+ continue;
+ }
+
+ if (currentChar() === ";" ) {
+ if(root.excelFormulaUtilities.isEu){
+ // If is EU then handle ; as list seperators
+ if (token.length > 0) {
+ tokens.add(token, TOK_TYPE_OPERAND);
+ token = "";
+ }
+ if (tokenStack.type() !== TOK_TYPE_FUNCTION) {
+ tokens.add(currentChar(), TOK_TYPE_OP_IN, TOK_SUBTYPE_UNION);
+ } else {
+ tokens.add(currentChar(), TOK_TYPE_ARGUMENT);
+ }
+ offset += 1;
+ continue;
+ } else {
+ // Else if not Eu handle ; as array row seperator
+ if (token.length > 0) {
+ tokens.add(token, TOK_TYPE_OPERAND);
+ token = "";
+ }
+ tokens.addRef(tokenStack.pop());
+ tokens.add(",", TOK_TYPE_ARGUMENT);
+ tokenStack.push(tokens.add("ARRAYROW", TOK_TYPE_FUNCTION, TOK_SUBTYPE_START));
+ offset += 1;
+ continue;
+ }
+ }
+
+ if (currentChar() === "}") {
+ if (token.length > 0) {
+ tokens.add(token, TOK_TYPE_OPERAND);
+ token = "";
+ }
+ tokens.addRef(tokenStack.pop("ARRAYROWSTOP"));
+ tokens.addRef(tokenStack.pop("ARRAYSTOP"));
+ offset += 1;
+ continue;
+ }
+
+ // trim white-space
+ if (currentChar() === " ") {
+ if (token.length > 0) {
+ tokens.add(token, TOK_TYPE_OPERAND);
+ token = "";
+ }
+ tokens.add("", TOK_TYPE_WSPACE);
+ offset += 1;
+ while ((currentChar() === " ") && (!EOF())) {
+ offset += 1;
+ }
+ continue;
+ }
+
+ // multi-character comparators
+ if ((",>=,<=,<>,").indexOf("," + doubleChar() + ",") !== -1) {
+ if (token.length > 0) {
+ tokens.add(token, TOK_TYPE_OPERAND);
+ token = "";
+ }
+ tokens.add(doubleChar(), TOK_TYPE_OP_IN, TOK_SUBTYPE_LOGICAL);
+ offset += 2;
+ continue;
+ }
+
+ // standard infix operators
+ if (("+-*/^&=><").indexOf(currentChar()) !== -1) {
+ if (token.length > 0) {
+ tokens.add(token, TOK_TYPE_OPERAND);
+ token = "";
+ }
+ tokens.add(currentChar(), TOK_TYPE_OP_IN);
+ offset += 1;
+ continue;
+ }
+
+ // standard postfix operators
+ if (("%").indexOf(currentChar()) !== -1) {
+ if (token.length > 0) {
+ tokens.add(token, TOK_TYPE_OPERAND);
+ token = "";
+ }
+ tokens.add(currentChar(), TOK_TYPE_OP_POST);
+ offset += 1;
+ continue;
+ }
+
+ // start subexpression or function
+ if (currentChar() === "(") {
+ if (token.length > 0) {
+ tokenStack.push(tokens.add(token, TOK_TYPE_FUNCTION, TOK_SUBTYPE_START));
+ token = "";
+ } else {
+ tokenStack.push(tokens.add("", TOK_TYPE_SUBEXPR, TOK_SUBTYPE_START));
+ }
+ offset += 1;
+ continue;
+ }
+
+ // function, subexpression, array parameters
+ if (currentChar() === "," && !root.excelFormulaUtilities.isEu) {
+ if (token.length > 0) {
+ tokens.add(token, TOK_TYPE_OPERAND);
+ token = "";
+ }
+ if (tokenStack.type() !== TOK_TYPE_FUNCTION) {
+ tokens.add(currentChar(), TOK_TYPE_OP_IN, TOK_SUBTYPE_UNION);
+ } else {
+ tokens.add(currentChar(), TOK_TYPE_ARGUMENT);
+ }
+ offset += 1;
+ continue;
+ }
+
+ // stop subexpression
+ if (currentChar() === ")") {
+ if (token.length > 0) {
+ tokens.add(token, TOK_TYPE_OPERAND);
+ token = "";
+ }
+ tokens.addRef(tokenStack.pop());
+ offset += 1;
+ continue;
+ }
+
+ // token accumulation
+ token += currentChar();
+ offset += 1;
+
+ }
+
+ // dump remaining accumulation
+ if (token.length > 0) {
+ tokens.add(token, TOK_TYPE_OPERAND);
+ }
+
+ // move all tokens to a new collection, excluding all unnecessary white-space tokens
+ var tokens2 = new F_tokens();
+
+ while (tokens.moveNext()) {
+
+ token = tokens.current();
+
+ if (token.type.toString() === TOK_TYPE_WSPACE) {
+ var doAddToken = (tokens.BOF()) || (tokens.EOF());
+ //if ((tokens.BOF()) || (tokens.EOF())) {}
+ doAddToken = doAddToken && (((tokens.previous().type.toString() === TOK_TYPE_FUNCTION) && (tokens.previous().subtype.toString() === TOK_SUBTYPE_STOP)) || ((tokens.previous().type.toString() === TOK_TYPE_SUBEXPR) && (tokens.previous().subtype.toString() === TOK_SUBTYPE_STOP)) || (tokens.previous().type.toString() === TOK_TYPE_OPERAND));
+ //else if (!(
+ // ((tokens.previous().type === TOK_TYPE_FUNCTION) && (tokens.previous().subtype == TOK_SUBTYPE_STOP))
+ // || ((tokens.previous().type == TOK_TYPE_SUBEXPR) && (tokens.previous().subtype == TOK_SUBTYPE_STOP))
+ // || (tokens.previous().type == TOK_TYPE_OPERAND)))
+ // {}
+ doAddToken = doAddToken && (((tokens.next().type.toString() === TOK_TYPE_FUNCTION) && (tokens.next().subtype.toString() === TOK_SUBTYPE_START)) || ((tokens.next().type.toString() === TOK_TYPE_SUBEXPR) && (tokens.next().subtype.toString() === TOK_SUBTYPE_START)) || (tokens.next().type.toString() === TOK_TYPE_OPERAND));
+ //else if (!(
+ // ((tokens.next().type == TOK_TYPE_FUNCTION) && (tokens.next().subtype == TOK_SUBTYPE_START))
+ // || ((tokens.next().type == TOK_TYPE_SUBEXPR) && (tokens.next().subtype == TOK_SUBTYPE_START))
+ // || (tokens.next().type == TOK_TYPE_OPERAND)))
+ // {}
+ //else { tokens2.add(token.value, TOK_TYPE_OP_IN, TOK_SUBTYPE_INTERSECT)};
+ if (doAddToken) {
+ tokens2.add(token.value.toString(), TOK_TYPE_OP_IN, TOK_SUBTYPE_INTERSECT);
+ }
+ continue;
+ }
+
+ tokens2.addRef(token);
+
+ }
+
+ // switch infix "-" operator to prefix when appropriate, switch infix "+" operator to noop when appropriate, identify operand
+ // and infix-operator subtypes, pull "@" from in front of function names
+ while (tokens2.moveNext()) {
+
+ token = tokens2.current();
+
+ if ((token.type.toString() === TOK_TYPE_OP_IN) && (token.value.toString() === "-")) {
+ if (tokens2.BOF()) {
+ token.type = TOK_TYPE_OP_PRE.toString();
+ } else if (((tokens2.previous().type.toString() === TOK_TYPE_FUNCTION) && (tokens2.previous().subtype.toString() === TOK_SUBTYPE_STOP)) || ((tokens2.previous().type.toString() === TOK_TYPE_SUBEXPR) && (tokens2.previous().subtype.toString() === TOK_SUBTYPE_STOP)) || (tokens2.previous().type.toString() === TOK_TYPE_OP_POST) || (tokens2.previous().type.toString() === TOK_TYPE_OPERAND)) {
+ token.subtype = TOK_SUBTYPE_MATH.toString();
+ } else {
+ token.type = TOK_TYPE_OP_PRE.toString();
+ }
+ continue;
+ }
+
+ if ((token.type.toString() === TOK_TYPE_OP_IN) && (token.value.toString() === "+")) {
+ if (tokens2.BOF()) {
+ token.type = TOK_TYPE_NOOP.toString();
+ } else if (((tokens2.previous().type.toString() === TOK_TYPE_FUNCTION) && (tokens2.previous().subtype.toString() === TOK_SUBTYPE_STOP)) || ((tokens2.previous().type.toString() === TOK_TYPE_SUBEXPR) && (tokens2.previous().subtype.toString() === TOK_SUBTYPE_STOP)) || (tokens2.previous().type.toString() === TOK_TYPE_OP_POST) || (tokens2.previous().type.toString() === TOK_TYPE_OPERAND)) {
+ token.subtype = TOK_SUBTYPE_MATH.toString();
+ } else {
+ token.type = TOK_TYPE_NOOP.toString();
+ }
+ continue;
+ }
+
+ if ((token.type.toString() === TOK_TYPE_OP_IN) && (token.subtype.length === 0)) {
+ if (("<>=").indexOf(token.value.substr(0, 1)) !== -1) {
+ token.subtype = TOK_SUBTYPE_LOGICAL.toString();
+ } else if (token.value.toString() === "&") {
+ token.subtype = TOK_SUBTYPE_CONCAT.toString();
+ } else {
+ token.subtype = TOK_SUBTYPE_MATH.toString();
+ }
+ continue;
+ }
+
+ if ((token.type.toString() === TOK_TYPE_OPERAND) && (token.subtype.length === 0)) {
+ if (isNaN(parseFloat(token.value))) {
+ if ((token.value.toString() === 'TRUE') || (token.value.toString() === 'FALSE')) {
+ token.subtype = TOK_SUBTYPE_LOGICAL.toString();
+ } else {
+ token.subtype = TOK_SUBTYPE_RANGE.toString();
+ }
+ } else {
+ token.subtype = TOK_SUBTYPE_NUMBER.toString();
+ }
+
+ continue;
+ }
+
+ if (token.type.toString() === TOK_TYPE_FUNCTION) {
+ if (token.value.substr(0, 1) === "@") {
+ token.value = token.value.substr(1).toString();
+ }
+ continue;
+ }
+
+ }
+
+ tokens2.reset();
+
+ // move all tokens to a new collection, excluding all noops
+ tokens = new F_tokens();
+
+ while (tokens2.moveNext()) {
+ if (tokens2.current().type.toString() !== TOK_TYPE_NOOP) {
+ tokens.addRef(tokens2.current());
+ }
+ }
+
+ tokens.reset();
+
+ return tokens;
+ }
+
+
+ var parseFormula = excelFormulaUtilities.parseFormula = function (inputID, outputID) {
+
+
+ var indentCount = 0;
+
+ var indent = function () {
+ var s = "|",
+ i = 0;
+ for (; i < indentCount; i += 1) {
+ s += " |";
+ }
+ return s;
+ };
+
+ var formulaControl = document.getElementById(inputID);
+ var formula = formulaControl.value;
+
+ var tokens = getTokens(formula);
+
+ var tokensHtml = "";
+
+ tokensHtml += "";
+ tokensHtml += "";
+ tokensHtml += "index | ";
+ tokensHtml += "type | ";
+ tokensHtml += "subtype | ";
+ tokensHtml += "token | ";
+ tokensHtml += "token tree |
";
+
+ while (tokens.moveNext()) {
+
+ var token = tokens.current();
+
+ if (token.subtype === TOK_SUBTYPE_STOP) {
+ indentCount -= ((indentCount > 0) ? 1 : 0);
+ }
+
+ tokensHtml += "";
+
+ tokensHtml += "" + (tokens.index + 1) + " | ";
+
+ tokensHtml += "" + token.type + " | ";
+ tokensHtml += "" + ((token.subtype.length === 0) ? " " : token.subtype.toString()) + " | ";
+ tokensHtml += "" + ((token.value.length === 0) ? " " : token.value).split(" ").join(" ") + " | ";
+ tokensHtml += "" + indent() + ((token.value.length === 0) ? " " : token.value).split(" ").join(" ") + " | ";
+
+ tokensHtml += "
";
+
+ if (token.subtype === TOK_SUBTYPE_START) {
+ indentCount += 1;
+ }
+
+ }
+
+ tokensHtml += "
";
+
+ document.getElementById(outputID).innerHTML = tokensHtml;
+
+ formulaControl.select();
+ formulaControl.focus();
+
+ };
+
+ // Pass a range such as A1:B2 along with a
+ // delimiter to get back a full list of ranges.
+ //
+ // Example:
+ // breakOutRanges("A1:B2", "+"); //Returns A1+A2+B1+B2
+ function breakOutRanges(rangeStr, delimStr){
+
+ //Quick Check to see if if rangeStr is a valid range
+ if ( !RegExp("[a-z]+[0-9]+:[a-z]+[0-9]+","gi").test(rangeStr) ){
+ throw "This is not a valid range: " + rangeStr;
+ }
+
+ //Make the rangeStr lowercase to deal with looping.
+ var range = rangeStr.split(":"),
+
+ startRow = parseInt(range[0].match(/[0-9]+/gi)[0]),
+ startCol = range[0].match(/[A-Z]+/gi)[0],
+ startColDec = fromBase26(startCol)
+
+ endRow = parseInt(range[1].match(/[0-9]+/gi)[0]),
+ endCol = range[1].match(/[A-Z]+/gi)[0],
+ endColDec = fromBase26(endCol),
+
+ // Total rows and cols
+ totalRows = endRow - startRow + 1,
+ totalCols = fromBase26(endCol) - fromBase26(startCol) + 1,
+
+ // Loop vars
+ curCol = 0,
+ curRow = 1 ,
+ curCell = "",
+
+ //Return String
+ retStr = "";
+
+ for(; curRow <= totalRows; curRow+=1){
+ for(; curCol < totalCols; curCol+=1){
+ // Get the current cell id
+ curCell = toBase26(startColDec + curCol) + "" + (startRow+curRow-1) ;
+ retStr += curCell + (curRow===totalRows && curCol===totalCols-1 ? "" : delimStr);
+ }
+ curCol=0;
+ }
+
+ return retStr;
+
+ }
+
+ //Modified from function at http://en.wikipedia.org/wiki/Hexavigesimal
+ var toBase26 = excelFormulaUtilities.toBase26 = function( value ) {
+
+ value = Math.abs(value);
+
+ var converted = ""
+ ,iteration = false
+ ,remainder;
+
+ // Repeatedly divide the numerb by 26 and convert the
+ // remainder into the appropriate letter.
+ do {
+ remainder = value % 26;
+
+ // Compensate for the last letter of the series being corrected on 2 or more iterations.
+ if (iteration && value < 25) {
+ remainder--;
+ }
+
+ converted = String.fromCharCode((remainder + 'A'.charCodeAt(0))) + converted;
+ value = Math.floor((value - remainder) / 26);
+
+ iteration = true;
+ } while (value > 0);
+
+ return converted;
+ }
+
+ // This was Modified from a function at http://en.wikipedia.org/wiki/Hexavigesimal
+ // Pass in the base 26 string, get back integer
+ var fromBase26 = excelFormulaUtilities.fromBase26 = function (number) {
+ number = number.toUpperCase();
+
+ var s = 0
+ ,i = 0
+ ,dec = 0;
+
+ if (
+ number !== null
+ && typeof number !== "undefined"
+ && number.length > 0
+ ) {
+ for (; i < number.length; i++) {
+ s = number.charCodeAt(number.length - i - 1) - "A".charCodeAt(0);
+ dec += (Math.pow(26, i)) * (s+1);
+ }
+ }
+
+ return dec - 1;
+ }
+
+ function applyTokenTemplate(token, options, indent, lineBreak, override) {
+
+ var indt = indent;
+
+ var lastToken = typeof arguments[5] === undefined || arguments[5] === null ? null : arguments[5];
+
+ var replaceTokenTmpl = function (inStr) {
+ return inStr.replace(/\{\{token\}\}/gi, "{0}").replace(/\{\{autoindent\}\}/gi, "{1}").replace(/\{\{autolinebreak\}\}/gi, "{2}");
+ };
+
+ var tokenString = "";
+
+ if (token.subtype === "text" || token.type === "text") {
+ tokenString = token.value.toString();
+ } else if ( token.type === 'operand' && token.subtype === 'range') {
+ tokenString = token.value.toString() ;
+ } else {
+ tokenString = ((token.value.length === 0) ? " " : token.value.toString()).split(" ").join("").toString();
+ }
+
+ if (typeof override === 'function') {
+ var returnVal = override(tokenString, token, indent, lineBreak);
+
+ tokenString = returnVal.tokenString;
+
+ if (!returnVal.useTemplate) {
+ return tokenString;
+ }
+ }
+
+ switch (token.type) {
+
+ case "function":
+ //-----------------FUNCTION------------------
+ switch (token.value) {
+ case "ARRAY":
+ tokenString = formatStr(replaceTokenTmpl(options.tmplFunctionStartArray), tokenString, indt, lineBreak);
+ break;
+ case "ARRAYROW":
+ tokenString = formatStr(replaceTokenTmpl(options.tmplFunctionStartArrayRow), tokenString, indt, lineBreak);
+ break;
+ default:
+ if (token.subtype.toString() === "start") {
+ tokenString = formatStr(replaceTokenTmpl(options.tmplFunctionStart), tokenString, indt, lineBreak);
+ } else {
+ tokenString = formatStr(replaceTokenTmpl(options.tmplFunctionStop), tokenString, indt, lineBreak);
+ }
+ break;
+ }
+ break;
+ case "operand":
+ //-----------------OPERAND------------------
+ switch (token.subtype.toString()) {
+ case "error":
+ tokenString = formatStr(replaceTokenTmpl(options.tmplOperandError), tokenString, indt, lineBreak);
+ break;
+ case "range":
+ tokenString = formatStr(replaceTokenTmpl(options.tmplOperandRange), tokenString, indt, lineBreak);
+ break;
+ case "logical":
+ tokenString = formatStr(replaceTokenTmpl(options.tmplOperandLogical), tokenString, indt, lineBreak);
+ break;
+ case "number":
+ tokenString = formatStr(replaceTokenTmpl(options.tmplOperandNumber), tokenString, indt, lineBreak);
+ break;
+ case "text":
+ tokenString = formatStr(replaceTokenTmpl(options.tmplOperandText), tokenString, indt, lineBreak);
+ break;
+ case "argument":
+ tokenString = formatStr(replaceTokenTmpl(options.tmplArgument), tokenString, indt, lineBreak);
+ break;
+ default:
+ break;
+ }
+ break;
+ case "operator-infix":
+ tokenString = formatStr(replaceTokenTmpl(options.tmplOperandOperatorInfix), tokenString, indt, lineBreak);
+ break;
+ case "logical":
+ tokenString = formatStr(replaceTokenTmpl(options.tmplLogical), tokenString, indt, lineBreak);
+ break;
+ case "argument":
+ if(lastToken.type !== "argument"){
+ tokenString = formatStr(replaceTokenTmpl(options.tmplArgument), tokenString, indt, lineBreak);
+ } else {
+ tokenString = formatStr(replaceTokenTmpl("{{autoindent}}"+options.tmplArgument), tokenString, indt, lineBreak);
+ }
+ break;
+ case "subexpression":
+ if (token.subtype.toString() === "start") {
+ tokenString = formatStr(replaceTokenTmpl(options.tmplSubexpressionStart), tokenString, indt, lineBreak);
+ } else {
+ tokenString = formatStr(replaceTokenTmpl(options.tmplSubexpressionStop), tokenString, indt, lineBreak);
+ }
+ break;
+ default:
+
+ break;
+ }
+ return tokenString;
+ };
+
+ /**
+ *
+ * @memberof excelFormulaUtilities.parser
+ * @function
+ * @param {string} formula
+ * @param {object} options optional param
+ *
+ * TEMPLATE VALUES
+ * {{autoindent}} - apply auto indent based on current tree level
+ * {{token}} - the named token such as FUNCTION_NAME or "string"
+ * {{autolinebreak}} - apply linbreak automaticly. tests for next element only at this point
+ *
+ * Options include:
+ * tmplFunctionStart - template for the start of a function, the {{token}} will contain the name of the function.
+ * tmplFunctionStop - template for when the end of a function has been reached.
+ * tmplOperandError - template for errors.
+ * tmplOperandRange - template for ranges and variable names.
+ * tmplOperandLogical - template for logical operators such as + - = ...
+ * tmplOperandNumber - template for numbers.
+ * tmplOperandText - template for text/strings.
+ * tmplArgument - template for argument seperators such as ,.
+ * tmplFunctionStartArray - template for the start of an array.
+ * tmplFunctionStartArrayRow - template for the start of an array row.
+ * tmplFunctionStopArrayRow - template for the end of an array row.
+ * tmplFunctionStopArray - template for the end of an array.
+ * tmplSubexpressionStart - template for the sub expresson start
+ * tmplSubexpressionStop - template for the sub expresson stop
+ * tmplIndentTab - template for the tab char.
+ * tmplIndentSpace - template for space char.
+ * autoLineBreak - when rendering line breaks automaticly which types should it break on. "TOK_SUBTYPE_STOP | TOK_SUBTYPE_START | TOK_TYPE_ARGUMENT"
+ * newLine - used for the {{autolinebreak}} replacement as well as some string parsing. if this is not set correctly you may get undesired results. usually \n for text or
for html
+ * trim: true - trim the output.
+ * customTokenRender: null - this is a call back to a custom token function. your call back should look like
+ * EXAMPLE:
+ *
+ * customTokenRender: function(tokenString, token, indent, linbreak){
+ * var outstr = token,
+ * useTemplate = true;
+ * // In the return object "useTemplate" tells formatFormula()
+ * // weather or not to apply the template to what your return from the "tokenString".
+ * return {tokenString: outstr, useTemplate: useTemplate};
+ * }
+ *
+ *
+ * @returns {string}
+ */
+ var formatFormula = excelFormulaUtilities.formatFormula = function (formula, options) {
+ //Quick fix for trailing space after = sign
+ formula = formula.replace(/^\s*=\s+/, "=");
+
+ var isFirstToken = true,
+ defaultOptions = {
+ tmplFunctionStart: '{{autoindent}}{{token}}(\n',
+ tmplFunctionStop: '\n{{autoindent}}{{token}})',
+ tmplOperandError: ' {{token}}',
+ tmplOperandRange: '{{autoindent}}{{token}}',
+ tmplLogical: '{{token}}{{autolinebreak}}',
+ tmplOperandLogical: '{{autoindent}}{{token}}',
+ tmplOperandNumber: '{{autoindent}}{{token}}',
+ tmplOperandText: '{{autoindent}}"{{token}}"',
+ tmplArgument: '{{token}}\n',
+ tmplOperandOperatorInfix: ' {{token}}{{autolinebreak}}',
+ tmplFunctionStartArray: '',
+ tmplFunctionStartArrayRow: '{',
+ tmplFunctionStopArrayRow: '}',
+ tmplFunctionStopArray: '',
+ tmplSubexpressionStart: '{{autoindent}}(\n',
+ tmplSubexpressionStop: '\n)',
+ tmplIndentTab: '\t',
+ tmplIndentSpace: ' ',
+ autoLineBreak: 'TOK_TYPE_FUNCTION | TOK_TYPE_ARGUMENT | TOK_SUBTYPE_LOGICAL | TOK_TYPE_OP_IN ',
+ newLine: '\n',
+ //trim: true,
+ customTokenRender: null,
+ prefix: "",
+ postfix: ""
+ };
+
+ if (options) {
+ options = core.extend(true, defaultOptions, options);
+ } else {
+ options = defaultOptions;
+ }
+
+ var indentCount = 0;
+
+ var indent = function () {
+ var s = "",
+ i = 0;
+
+ for (; i < indentCount; i += 1) {
+ s += options.tmplIndentTab;
+ }
+ return s;
+ };
+
+ var tokens = getTokens(formula);
+
+ var outputFormula = "";
+
+ var autoBreakArray = options.autoLineBreak.replace(/\s/gi, "").split("|");
+
+ //Tokens
+ var isNewLine = true;
+
+ var testAutoBreak = function (nextToken) {
+ var i = 0;
+ for (; i < autoBreakArray.length; i += 1) {
+ if (nextToken !== null && typeof nextToken !== 'undefined' && (types[autoBreakArray[i]] === nextToken.type.toString() || types[autoBreakArray[i]] === nextToken.subtype.toString())) {
+ return true;
+ }
+ }
+ return false;
+ };
+
+ var lastToken = null;
+
+ while (tokens.moveNext()) {
+
+ var token = tokens.current();
+ var nextToken = tokens.next();
+
+ if (token.subtype.toString() === TOK_SUBTYPE_STOP) {
+ indentCount -= ((indentCount > 0) ? 1 : 0);
+ }
+
+ var matchBeginNewline = new RegExp('^' + options.newLine, ''),
+ matchEndNewLine = new RegExp(options.newLine + '$', ''),
+ autoBreak = testAutoBreak(nextToken),
+ autoIndent = isNewLine,
+ indt = autoIndent ? indent() : options.tmplIndentSpace,
+ lineBreak = autoBreak ? options.newLine : "";
+
+ // TODO this strips out spaces which breaks part of issue 28. 'Data Sheet' gets changed to DataSheet
+ outputFormula += applyTokenTemplate(token, options, indt, lineBreak, options.customTokenRender, lastToken);
+
+ if (token.subtype.toString() === TOK_SUBTYPE_START) {
+ indentCount += 1;
+
+ }
+
+ isNewLine = autoBreak || matchEndNewLine.test(outputFormula);
+ isFirstToken = false;
+
+ lastToken = token;
+ }
+
+ outputFormula = options.prefix + trim(outputFormula) + options.postfix;
+
+ return outputFormula;
+ };
+ /**
+ * This function calls {@link excelFormulaUtilities.parser.formatFormula}
+ *
+ * @memberof excelFormulaUtilities.parser
+ * @function
+ * @param {string} formula
+ * @param {object} options optional param
+ */
+ var formatFormulaHTML = excelFormulaUtilities.formatFormulaHTML = function (formula) {
+ var options = {
+ tmplFunctionStart: '{{autoindent}}{{token}}(
',
+ tmplFunctionStop: '
{{autoindent}}{{token}})',
+ tmplOperandText: '{{autoindent}}"{{token}}"',
+ tmplArgument: '{{token}}
',
+ tmplSubexpressionStart: '{{autoindent}}(',
+ tmplSubexpressionStop: ' )',
+ tmplIndentTab: ' ',
+ tmplIndentSpace: ' ',
+ newLine: '
',
+ autoLineBreak: 'TOK_TYPE_FUNCTION | TOK_TYPE_ARGUMENT | TOK_SUBTYPE_LOGICAL | TOK_TYPE_OP_IN ',
+ trim: true,
+ prefix: "=",
+ customTokenRender: null
+ };
+
+ return formatFormula(formula, options);
+ }
+
+ /**
+ *
+ * @memberof excelFormulaUtilities.convert
+ * @function
+ * @param {string} formula
+ * @returns {string}
+ */
+ var formula2CSharp = excelFormulaUtilities.formula2CSharp = function (formula) {
+
+ //Custom callback to format as c#
+ var functionStack = [];
+
+ var tokRender = function (tokenStr, token, indent, linbreak) {
+ var outstr = "",
+ /*tokenString = (token.value.length === 0) ? "" : token.value.toString(),*/
+ tokenString = tokenStr,
+ directConversionMap = {
+ "=": "==",
+ "<>": "!=",
+ "MIN": "Math.Min",
+ "MAX": "Math.Max",
+ "ABS": "Math.ABS",
+ "SUM": "",
+ "IF": "",
+ "&": "+"
+ },
+ currentFunctionOnStack = functionStack[functionStack.length - 1],
+ useTemplate = false;
+
+ switch (token.type.toString()) {
+
+ case TOK_TYPE_FUNCTION:
+
+ switch (token.subtype) {
+
+ case TOK_SUBTYPE_START:
+
+ functionStack.push({
+ name: tokenString,
+ argumentNumber: 0
+ });
+ outstr = typeof directConversionMap[tokenString.toUpperCase()] === "string" ? directConversionMap[tokenString.toUpperCase()] : tokenString;
+ useTemplate = true;
+
+ break;
+
+ case TOK_SUBTYPE_STOP:
+
+ useTemplate = true;
+ switch (currentFunctionOnStack.name.toLowerCase()) {
+ case "if":
+ outstr = ")";
+ useTemplate = false;
+ break;
+ default:
+ outstr = typeof directConversionMap[tokenString.toUpperCase()] === "string" ? directConversionMap[tokenString.toUpperCase()] : tokenString;
+ break
+ }
+ functionStack.pop();
+ break;
+ }
+
+ break;
+
+ case TOK_TYPE_ARGUMENT:
+ switch (currentFunctionOnStack.name.toLowerCase()) {
+ case "if":
+ switch (currentFunctionOnStack.argumentNumber) {
+ case 0:
+ outstr = "?";
+ break;
+ case 1:
+ outstr = ":";
+ break;
+ }
+ break;
+ case "sum":
+ outstr = "+";
+ break;
+ default:
+ outstr = typeof directConversionMap[tokenString.toUpperCase()] === "string" ? directConversionMap[tokenString.toUpperCase()] : tokenString;
+ useTemplate = true;
+ break;
+ }
+
+ currentFunctionOnStack.argumentNumber += 1;
+
+ break;
+
+ case TOK_TYPE_OPERAND:
+
+ switch (token.subtype) {
+
+ case TOK_SUBTYPE_RANGE:
+
+ switch (currentFunctionOnStack.name.toLowerCase()) {
+ // If in the sum function break aout cell names and add
+ case "sum":
+ //TODO make sure this is working
+ if(RegExp(":","gi").test(tokenString)){
+ outstr = breakOutRanges(tokenString, "+");
+ } else {
+ outStr = tokenString;
+ }
+
+ break;
+ // By Default return an array containing all cell names in array
+ default:
+ // Create array for ranges
+ if(RegExp(":","gi").test(tokenString)){
+ outstr = "[" + breakOutRanges(tokenString, ",") +"]";
+ } else {
+ outstr = tokenString;
+ }
+ //debugger;
+ break;
+ }
+
+ break;
+
+ default:
+ break;
+ }
+
+ default:
+ if( outstr === "" ){
+ outstr = typeof directConversionMap[tokenString.toUpperCase()] === "string" ? directConversionMap[tokenString.toUpperCase()] : tokenString;
+ }
+ useTemplate = true;
+ break;
+ }
+
+ return {
+ tokenString: outstr,
+ useTemplate: useTemplate
+ };
+ };
+
+ var cSharpOutput = formatFormula(
+ formula, {
+ tmplFunctionStart: '{{token}}(',
+ tmplFunctionStop: '{{token}})',
+ tmplOperandError: '{{token}}',
+ tmplOperandRange: '{{token}}',
+ tmplOperandLogical: '{{token}}',
+ tmplOperandNumber: '{{token}}',
+ tmplOperandText: '"{{token}}"',
+ tmplArgument: '{{token}}',
+ tmplOperandOperatorInfix: '{{token}}',
+ tmplFunctionStartArray: "",
+ tmplFunctionStartArrayRow: "{",
+ tmplFunctionStopArrayRow: "}",
+ tmplFunctionStopArray: "",
+ tmplSubexpressionStart: "(",
+ tmplSubexpressionStop: ")",
+ tmplIndentTab: "\t",
+ tmplIndentSpace: " ",
+ autoLineBreak: "TOK_SUBTYPE_STOP | TOK_SUBTYPE_START | TOK_TYPE_ARGUMENT",
+ trim: true,
+ customTokenRender: tokRender
+ });
+ return cSharpOutput;
+ };
+
+ /**
+ * Both the csharp and javascript are the same when converted, this is just an alias for convert2CSharp. there are some subtle differences such as == vrs ===, this will be addressed in a later version.
+ * @memberof excelFormulaUtilities.convert
+ * @function
+ * @param {string} formula
+ * @returns {string}
+ */
+ var formula2JavaScript = excelFormulaUtilities.formula2JavaScript = function (formula) {
+ return formula2CSharp(formula).replace('==', '===');
+ }
+
+ excelFormulaUtilities.getTokens = getTokens;
+
+}(window|| module.exports || {}));
+
+/*
+ * excelFormulaUtilitiesJS
+ * https://github.com/joshatjben/excelFormulaUtilitiesJS/
+ *
+ * Copyright 2011, Josh Bennett
+ * licensed under the MIT license.
+ * https://github.com/joshatjben/excelFormulaUtilitiesJS/blob/master/LICENSE.txt
+ *
+ * Some functionality based off of the jquery core lib
+ * Copyright 2011, John Resig
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * Based on Ewbi's Go Calc Prototype Excel Formula Parser. [http://ewbi.blogs.com/develops/2004/12/excel_formula_p.html]
+ */
+
+(function () {
+
+ if (typeof window === 'undefined') {
+ window = root;
+ }
+ var excelFormulaUtilities = window.excelFormulaUtilities = window.excelFormulaUtilities || {};
+ var core = window.excelFormulaUtilities.core = {};
+ window.excelFormulaUtilities.string = window.excelFormulaUtilities.string || {};
+
+ /**
+ * Simple/quick string formater. This will take an input string and apply n number of arguments to it.
+ *
+ * example:
+ *
+ *
+ * var foo = excelFormulaUtilities.core.formatStr("{0}", "foo"); // foo will be set to "foo"
+ * var fooBar = excelFormulaUtilities.core.formatStr("{0} {1}", "foo", "bar"); // fooBar will be set to "fooBar"
+ * var error = excelFormulaUtilities.core.formatStr("{1}", "error"); // will throw an index out of range error since only 1 extra argument was passed, which would be index 0.
+ *
+ *
+ *
+ * @memberOf window.excelFormulaUtilities.core
+ * @function
+ * @param {String} inStr
+ **/
+ var formatStr = window.excelFormulaUtilities.string.formatStr = function(inStr) {
+ var formattedStr = inStr;
+ var argIndex = 1;
+ for (; argIndex < arguments.length; argIndex++) {
+ var replaceIndex = (argIndex - 1);
+ var replaceRegex = new RegExp("\\{{1}" + replaceIndex.toString() + "{1}\\}{1}", "g");
+ formattedStr = formattedStr.replace(replaceRegex, arguments[argIndex]);
+ }
+ return formattedStr;
+ };
+
+ var trim = window.excelFormulaUtilities.string.trim = function(inStr){
+ return inStr.replace(/^\s|\s$/, "");
+ };
+
+ var trimHTML = window.excelFormulaUtilities.string.trim = function(inStr){
+ return inStr.replace(/^(?:\s| |<\s*br\s*\/*\s*>)*|(?:\s| |<\s*br\s*\/*\s*>)*$/, "");
+ };
+
+ //Quick and dirty type checks
+ /**
+ * @param {object} obj
+ * @returns {boolean}
+ * @memberOf window.excelFormulaUtilities.core
+ */
+ var isFunction = core.isFunction = function (obj) {
+ return (typeof obj) === "function";
+ };
+
+ /**
+ * @param {object} obj
+ * @returns {boolean}
+ * @memberOf window.excelFormulaUtilities.core
+ */
+ var isArray = core.isArray = function (obj) {
+ return (typeof obj) === "object" && obj.length;
+ };
+
+ /**
+ * @param {object} obj
+ * @returns {boolean}
+ * @memberOf window.excelFormulaUtilities.core
+ */
+ var isWindow = core.isWindow = function () {
+ return obj && typeof obj === "object" && "setInterval" in obj;
+ }; /*----The functionality below has based off of the jQuery core library----*/
+
+ /**
+ * Check if the object is a plain object or not. This has been pulled from the jQuery core and modified slightly.
+ * @param {object} obj
+ * @returns {boolean} returns weather the object is a plain object or not.
+ * @memberOf window.excelFormulaUtilities.core
+ */
+ var isPlainObject = core.isPlainObject = function (obj) {
+ // Must be an Object.
+ // Because of IE, we also have to check the presence of the constructor property.
+ // Make sure that DOM nodes and window objects don't pass through, as well
+ if (!obj || typeof obj !== "object" || obj.nodeType || isWindow(obj)) {
+ return false;
+ }
+ // Not own constructor property must be Object
+ if (obj.constructor && !hasOwnProperty.call(obj, "constructor") && !hasOwnProperty.call(obj.constructor.prototype, "isPrototypeOf")) {
+ return false;
+ }
+ // Own properties are enumerated firstly, so to speed up,
+ // if last one is own, then all properties are own.
+ var key;
+ for (key in obj) { }
+ return key === undefined || hasOwnProperty.call(obj, key);
+ };
+
+ /**
+ * This has been pulled from the jQuery core and modified slightly. see http://api.jquery.com/jQuery.extend/
+ * @param {object} target
+ * @param {object} object add one or more object to extend the target.
+ * @returns {object} returns the extended object.
+ * @memberOf window.excelFormulaUtilities.core
+ */
+ var extend = core.extend = function () {
+ var options, name, src, copy, copyIsArray, clone, target = arguments[0] || {},
+ i = 1,
+ length = arguments.length,
+ deep = false;
+ // Handle a deep copy situation
+ if (typeof target === "boolean") {
+ deep = target;
+ target = arguments[1] || {};
+ // skip the boolean and the target
+ i = 2;
+ }
+ // Handle case when target is a string or something (possible in deep copy)
+ if (typeof target !== "object" && !isFunction(target)) {
+ target = {};
+ }
+ // extend jQuery itself if only one argument is passed
+ if (length === i) {
+ target = this;
+ --i;
+ }
+ for (; i < length; i++) {
+ // Only deal with non-null/undefined values
+ if ((options = arguments[i]) != null) {
+ // Extend the base object
+ for (name in options) {
+ src = target[name];
+ copy = options[name];
+ // Prevent never-ending loop
+ if (target === copy) {
+ continue;
+ }
+ // Recurse if we're merging plain objects or arrays
+ if (deep && copy && (isPlainObject(copy) || (copyIsArray = isArray(copy)))) {
+ if (copyIsArray) {
+ copyIsArray = false;
+ clone = src && isArray(src) ? src : [];
+ } else {
+ clone = src && isPlainObject(src) ? src : {};
+ }
+ // Never move original objects, clone them
+ target[name] = core.extend(deep, clone, copy);
+ // Don't bring in undefined values
+ } else if (copy !== undefined) {
+ target[name] = copy;
+ }
+ }
+ }
+ }
+ // Return the modified object
+ return target;
+ }; /*----end of jquery functionality----*/
+
+
+}());
diff --git a/dist/excel-formula.min.js b/dist/excel-formula.min.js
new file mode 100644
index 0000000..1982352
--- /dev/null
+++ b/dist/excel-formula.min.js
@@ -0,0 +1 @@
+!function(t){function e(t,e,n){this.value=t,this.type=e,this.subtype=n}function n(){this.items=[],this.add=function(t,n,r){r||(r="");var o=new e(t,n,r);return this.addRef(o),o},this.addRef=function(t){this.items.push(t)},this.index=-1,this.reset=function(){this.index=-1},this.BOF=function(){return this.index<=0},this.EOF=function(){return this.index>=this.items.length-1},this.moveNext=function(){return this.EOF()?!1:(this.index+=1,!0)},this.current=function(){return-1===this.index?null:this.items[this.index]},this.next=function(){return this.EOF()?null:this.items[this.index+1]},this.previous=function(){return this.index<1?null:this.items[this.index-1]}}function r(){this.items=[],this.push=function(t){this.items.push(t)},this.pop=function(t){var n=this.items.pop();return new e(t||"",n.type,v)},this.token=function(){return this.items.length>0?this.items[this.items.length-1]:null},this.value=function(){return this.token()?this.token().value.toString():""},this.type=function(){return this.token()?this.token().type.toString():""},this.subtype=function(){return this.token()?this.token().subtype.toString():""}}function o(e){for(var o=new n,i=new r,s=0,a=function(){return e.substr(s,1)},u=function(){return e.substr(s,2)},p=function(){return e.substr(s+1,1)},l=function(){return s>=e.length},c="",C=!1,N=!1,Y=!1,K=!1,B=/^[1-9]{1}(\.[0-9]+)?E{1}$/;e.length>0;){if(" "!==e.substr(0,1)){"="===e.substr(0,1)&&(e=e.substr(1));break}e=e.substr(1)}for(;!l();)if(C)'"'===a()?'"'===p()?(c+='"',s+=1):(C=!1,o.add(c,g,w),c=""):c+=a(),s+=1;else if(N)"'"===a()?"'"===p()?(c+="'",s+=1):(N=!1,c+="'"):c+=a(),s+=1;else if(Y)"]"===a()&&(Y=!1),c+=a(),s+=1;else if(K)c+=a(),s+=1,-1!==",#NULL!,#DIV/0!,#VALUE!,#REF!,#NAME?,#NUM!,#N/A,".indexOf(","+c+",")&&(K=!1,o.add(c,g,E),c="");else if(-1!=="+-".indexOf(a())&&c.length>1&&c.match(B))c+=a(),s+=1;else if('"'!==a())if("'"!==a())if("["!==a())if("#"!==a())if("{"!==a())if(";"!==a())if("}"!==a())if(" "!==a())-1===",>=,<=,<>,".indexOf(","+u()+",")?-1==="+-*/^&=><".indexOf(a())?-1==="%".indexOf(a())?"("!==a()?","!==a()||t.excelFormulaUtilities.isEu?")"!==a()?(c+=a(),s+=1):(c.length>0&&(o.add(c,g),c=""),o.addRef(i.pop()),s+=1):(c.length>0&&(o.add(c,g),c=""),i.type()!==m?o.add(a(),b,P):o.add(a(),S),s+=1):(c.length>0?(i.push(o.add(c,m,x)),c=""):i.push(o.add("",f,x)),s+=1):(c.length>0&&(o.add(c,g),c=""),o.add(a(),y),s+=1):(c.length>0&&(o.add(c,g),c=""),o.add(a(),b),s+=1):(c.length>0&&(o.add(c,g),c=""),o.add(u(),b,_),s+=2);else for(c.length>0&&(o.add(c,g),c=""),o.add("",T),s+=1;" "===a()&&!l();)s+=1;else c.length>0&&(o.add(c,g),c=""),o.addRef(i.pop("ARRAYROWSTOP")),o.addRef(i.pop("ARRAYSTOP")),s+=1;else{if(t.excelFormulaUtilities.isEu){c.length>0&&(o.add(c,g),c=""),i.type()!==m?o.add(a(),b,P):o.add(a(),S),s+=1;continue}c.length>0&&(o.add(c,g),c=""),o.addRef(i.pop()),o.add(",",S),i.push(o.add("ARRAYROW",m,x)),s+=1}else c.length>0&&(o.add(c,O),c=""),i.push(o.add("ARRAY",m,x)),i.push(o.add("ARRAYROW",m,x)),s+=1;else c.length>0&&(o.add(c,O),c=""),K=!0,c+=a(),s+=1;else Y=!0,c+=a(),s+=1;else c.length>0&&(o.add(c,O),c=""),c="'",N=!0,s+=1;else c.length>0&&(o.add(c,O),c=""),C=!0,s+=1;c.length>0&&o.add(c,g);for(var I=new n;o.moveNext();)if(c=o.current(),c.type.toString()!==T)I.addRef(c);else{var L=o.BOF()||o.EOF();L=L&&(o.previous().type.toString()===m&&o.previous().subtype.toString()===v||o.previous().type.toString()===f&&o.previous().subtype.toString()===v||o.previous().type.toString()===g),L=L&&(o.next().type.toString()===m&&o.next().subtype.toString()===x||o.next().type.toString()===f&&o.next().subtype.toString()===x||o.next().type.toString()===g),L&&I.add(c.value.toString(),b,F)}for(;I.moveNext();)c=I.current(),c.type.toString()!==b||"-"!==c.value.toString()?c.type.toString()!==b||"+"!==c.value.toString()?c.type.toString()!==b||0!==c.subtype.length?c.type.toString()!==g||0!==c.subtype.length?c.type.toString()!==m||"@"===c.value.substr(0,1)&&(c.value=c.value.substr(1).toString()):isNaN(parseFloat(c.value))?"TRUE"===c.value.toString()||"FALSE"===c.value.toString()?c.subtype=_.toString():c.subtype=R.toString():c.subtype=k.toString():-1!=="<>=".indexOf(c.value.substr(0,1))?c.subtype=_.toString():"&"===c.value.toString()?c.subtype=U.toString():c.subtype=A.toString():I.BOF()?c.type=d.toString():I.previous().type.toString()===m&&I.previous().subtype.toString()===v||I.previous().type.toString()===f&&I.previous().subtype.toString()===v||I.previous().type.toString()===y||I.previous().type.toString()===g?c.subtype=A.toString():c.type=d.toString():I.BOF()?c.type=h.toString():I.previous().type.toString()===m&&I.previous().subtype.toString()===v||I.previous().type.toString()===f&&I.previous().subtype.toString()===v||I.previous().type.toString()===y||I.previous().type.toString()===g?c.subtype=A.toString():c.type=h.toString();for(I.reset(),o=new n;I.moveNext();)I.current().type.toString()!==d&&o.addRef(I.current());return o.reset(),o}function i(t,e){if(!RegExp("[a-z]+[0-9]+:[a-z]+[0-9]+","gi").test(t))throw"This is not a valid range: "+t;var n=t.split(":"),r=parseInt(n[0].match(/[0-9]+/gi)[0]),o=n[0].match(/[A-Z]+/gi)[0],i=N(o);for(endRow=parseInt(n[1].match(/[0-9]+/gi)[0]),endCol=n[1].match(/[A-Z]+/gi)[0],endColDec=N(endCol),totalRows=endRow-r+1,totalCols=N(endCol)-N(o)+1,curCol=0,curRow=1,curCell="",retStr="";curRow<=totalRows;curRow+=1){for(;curCole;e+=1)t+=" |";return t},i=document.getElementById(t),s=i.value,a=o(s),u="";for(u+="",u+="",u+="index | ",u+="type | ",u+="subtype | ",u+="token | ",u+="token tree |
";a.moveNext();){var p=a.current();p.subtype===v&&(n-=n>0?1:0),u+="",u+=""+(a.index+1)+" | ",u+=""+p.type+" | ",u+=""+(0===p.subtype.length?" ":p.subtype.toString())+" | ",u+=""+(0===p.value.length?" ":p.value).split(" ").join(" ")+" | ",u+=""+r()+(0===p.value.length?" ":p.value).split(" ").join(" ")+" | ",u+="
",p.subtype===x&&(n+=1)}u+="
",document.getElementById(e).innerHTML=u,i.select(),i.focus()},a.toBase26=function(t){t=Math.abs(t);var e,n="",r=!1;do e=t%26,r&&25>t&&e--,n=String.fromCharCode(e+"A".charCodeAt(0))+n,t=Math.floor((t-e)/26),r=!0;while(t>0);return n}),N=a.fromBase26=function(t){t=t.toUpperCase();var e=0,n=0,r=0;if(null!==t&&"undefined"!=typeof t&&t.length>0)for(;nn;n+=1)t+=e.tmplIndentTab;return t},p=o(t),d="",g=e.autoLineBreak.replace(/\s/gi,"").split("|"),m=!0,f=function(t){for(var e=0;e0?1:0);var y=(new RegExp("^"+e.newLine,""),new RegExp(e.newLine+"$","")),T=f(b),O=m,w=O?a():e.tmplIndentSpace,k=T?e.newLine:"";d+=s(h,e,w,k,e.customTokenRender,S),h.subtype.toString()===x&&(i+=1),m=T||y.test(d),n=!1,S=h}return d=e.prefix+l(d)+e.postfix},K=(a.formatFormulaHTML=function(t){var e={tmplFunctionStart:'{{autoindent}}{{token}}(
',tmplFunctionStop:'
{{autoindent}}{{token}})',tmplOperandText:'{{autoindent}}"{{token}}"',tmplArgument:"{{token}}
",tmplSubexpressionStart:"{{autoindent}}(",tmplSubexpressionStop:" )",tmplIndentTab:' ',tmplIndentSpace:" ",newLine:"
",autoLineBreak:"TOK_TYPE_FUNCTION | TOK_TYPE_ARGUMENT | TOK_SUBTYPE_LOGICAL | TOK_TYPE_OP_IN ",trim:!0,prefix:"=",customTokenRender:null};return Y(t,e)},a.formula2CSharp=function(t){var e=[],n=function(t,n,r,o){var s="",a=t,u={"=":"==","<>":"!=",MIN:"Math.Min",MAX:"Math.Max",ABS:"Math.ABS",SUM:"",IF:"","&":"+"},p=e[e.length-1],l=!1;switch(n.type.toString()){case m:switch(n.subtype){case x:e.push({name:a,argumentNumber:0}),s="string"==typeof u[a.toUpperCase()]?u[a.toUpperCase()]:a,l=!0;break;case v:switch(l=!0,p.name.toLowerCase()){case"if":s=")",l=!1;break;default:s="string"==typeof u[a.toUpperCase()]?u[a.toUpperCase()]:a}e.pop()}break;case S:switch(p.name.toLowerCase()){case"if":switch(p.argumentNumber){case 0:s="?";break;case 1:s=":"}break;case"sum":s="+";break;default:s="string"==typeof u[a.toUpperCase()]?u[a.toUpperCase()]:a,l=!0}p.argumentNumber+=1;break;case g:switch(n.subtype){case R:switch(p.name.toLowerCase()){case"sum":RegExp(":","gi").test(a)?s=i(a,"+"):outStr=a;break;default:s=RegExp(":","gi").test(a)?"["+i(a,",")+"]":a}}default:""===s&&(s="string"==typeof u[a.toUpperCase()]?u[a.toUpperCase()]:a),l=!0}return{tokenString:s,useTemplate:l}},r=Y(t,{tmplFunctionStart:"{{token}}(",tmplFunctionStop:"{{token}})",tmplOperandError:"{{token}}",tmplOperandRange:"{{token}}",tmplOperandLogical:"{{token}}",tmplOperandNumber:"{{token}}",tmplOperandText:'"{{token}}"',tmplArgument:"{{token}}",tmplOperandOperatorInfix:"{{token}}",tmplFunctionStartArray:"",tmplFunctionStartArrayRow:"{",tmplFunctionStopArrayRow:"}",tmplFunctionStopArray:"",tmplSubexpressionStart:"(",tmplSubexpressionStop:")",tmplIndentTab:" ",tmplIndentSpace:" ",autoLineBreak:"TOK_SUBTYPE_STOP | TOK_SUBTYPE_START | TOK_TYPE_ARGUMENT",trim:!0,customTokenRender:n});return r});a.formula2JavaScript=function(t){return K(t).replace("==","===")};a.getTokens=o}(window||module.exports||{}),function(){"undefined"==typeof window&&(window=root);var t=(window.excelFormulaUtilities=window.excelFormulaUtilities||{},window.excelFormulaUtilities.core={});window.excelFormulaUtilities.string=window.excelFormulaUtilities.string||{};var e=(window.excelFormulaUtilities.string.formatStr=function(t){for(var e=t,n=1;n)*|(?:\s| |<\s*br\s*\/*\s*>)*$/,"")},t.isFunction=function(t){return"function"==typeof t}),n=t.isArray=function(t){return"object"==typeof t&&t.length},r=t.isWindow=function(){return obj&&"object"==typeof obj&&"setInterval"in obj},o=t.isPlainObject=function(t){if(!t||"object"!=typeof t||t.nodeType||r(t))return!1;if(t.constructor&&!hasOwnProperty.call(t,"constructor")&&!hasOwnProperty.call(t.constructor.prototype,"isPrototypeOf"))return!1;var e;for(e in t);return void 0===e||hasOwnProperty.call(t,e)};t.extend=function(){var r,i,s,a,u,p,l=arguments[0]||{},c=1,d=arguments.length,g=!1;for("boolean"==typeof l&&(g=l,l=arguments[1]||{},c=2),"object"==typeof l||e(l)||(l={}),d===c&&(l=this,--c);d>c;c++)if(null!=(r=arguments[c]))for(i in r)s=l[i],a=r[i],l!==a&&(g&&a&&(o(a)||(u=n(a)))?(u?(u=!1,p=s&&n(s)?s:[]):p=s&&o(s)?s:{},l[i]=t.extend(g,p,a)):void 0!==a&&(l[i]=a));return l}}();
\ No newline at end of file
diff --git a/excel-formula.js b/excel-formula.js
new file mode 100644
index 0000000..e126233
--- /dev/null
+++ b/excel-formula.js
@@ -0,0 +1,1366 @@
+/*
+ * excelFormulaUtilitiesJS
+ * https://github.com/joshatjben/excelFormulaUtilitiesJS/
+ *
+ * Copyright 2011, Josh Bennett
+ * licensed under the MIT license.
+ * https://github.com/joshatjben/excelFormulaUtilitiesJS/blob/master/LICENSE.txt
+ *
+ * Some functionality based off of the jquery core lib
+ * Copyright 2011, John Resig
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * Based on Ewbi's Go Calc Prototype Excel Formula Parser. [http://ewbi.blogs.com/develops/2004/12/excel_formula_p.html]
+ */
+(function (root) {
+ var excelFormulaUtilities = root.excelFormulaUtilities = root.excelFormulaUtilities || {},
+ core = root.excelFormulaUtilities.core,
+ formatStr = root.excelFormulaUtilities.string.formatStr,
+ trim = root.excelFormulaUtilities.string.trim,
+
+ types = {},
+ TOK_TYPE_NOOP = types.TOK_TYPE_NOOP = "noop",
+ TOK_TYPE_OPERAND = types.TOK_TYPE_OPERAND = "operand",
+ TOK_TYPE_FUNCTION = types.TOK_TYPE_FUNCTION = "function",
+ TOK_TYPE_SUBEXPR = types.TOK_TYPE_SUBEXPR = "subexpression",
+ TOK_TYPE_ARGUMENT = types.TOK_TYPE_ARGUMENT = "argument",
+ TOK_TYPE_OP_PRE = types.TOK_TYPE_OP_PRE = "operator-prefix",
+ TOK_TYPE_OP_IN = types.TOK_TYPE_OP_IN = "operator-infix",
+ TOK_TYPE_OP_POST = types.TOK_TYPE_OP_POST = "operator-postfix",
+ TOK_TYPE_WSPACE = types.TOK_TYPE_WSPACE = "white-space",
+ TOK_TYPE_UNKNOWN = types.TOK_TYPE_UNKNOWN = "unknown",
+
+ TOK_SUBTYPE_START = types.TOK_SUBTYPE_START = "start",
+ TOK_SUBTYPE_STOP = types.TOK_SUBTYPE_STOP = "stop",
+
+ TOK_SUBTYPE_TEXT = types.TOK_SUBTYPE_TEXT = "text",
+ TOK_SUBTYPE_NUMBER = types.TOK_SUBTYPE_NUMBER = "number",
+ TOK_SUBTYPE_LOGICAL = types.TOK_SUBTYPE_LOGICAL = "logical",
+ TOK_SUBTYPE_ERROR = types.TOK_SUBTYPE_ERROR = "error",
+ TOK_SUBTYPE_RANGE = types.TOK_SUBTYPE_RANGE = "range",
+
+ TOK_SUBTYPE_MATH = types.TOK_SUBTYPE_MATH = "math",
+ TOK_SUBTYPE_CONCAT = types.TOK_SUBTYPE_CONCAT = "concatenate",
+ TOK_SUBTYPE_INTERSECT = types.TOK_SUBTYPE_INTERSECT = "intersect",
+ TOK_SUBTYPE_UNION = types.TOK_SUBTYPE_UNION = "union";
+
+ root.excelFormulaUtilities.isEu = typeof root.excelFormulaUtilities.isEu === 'boolean' ? root.excelFormulaUtilities.isEu : false;
+
+
+ /**
+ * @class
+ */
+
+ function F_token(value, type, subtype) {
+ this.value = value;
+ this.type = type;
+ this.subtype = subtype;
+ }
+
+ /**
+ * @class
+ */
+
+ function F_tokens() {
+
+ this.items = [];
+
+ this.add = function (value, type, subtype) {
+ if (!subtype) {
+ subtype = "";
+ }
+ var token = new F_token(value, type, subtype);
+ this.addRef(token);
+ return token;
+ };
+ this.addRef = function (token) {
+ this.items.push(token);
+ };
+
+ this.index = -1;
+ this.reset = function () {
+ this.index = -1;
+ };
+ this.BOF = function () {
+ return (this.index <= 0);
+ };
+ this.EOF = function () {
+ return (this.index >= (this.items.length - 1));
+ };
+ this.moveNext = function () {
+ if (this.EOF()) {
+ return false;
+ }
+ this.index += 1;
+ return true;
+ };
+ this.current = function () {
+ if (this.index === -1) {
+ return null;
+ }
+ return (this.items[this.index]);
+ };
+ this.next = function () {
+ if (this.EOF()) {
+ return null;
+ }
+ return (this.items[this.index + 1]);
+ };
+ this.previous = function () {
+ if (this.index < 1) {
+ return null;
+ }
+ return (this.items[this.index - 1]);
+ };
+
+ }
+
+ function F_tokenStack() {
+
+ this.items = [];
+
+ this.push = function (token) {
+ this.items.push(token);
+ };
+ this.pop = function (name) {
+ var token = this.items.pop();
+ return (new F_token(name || "", token.type, TOK_SUBTYPE_STOP));
+ };
+
+ this.token = function () {
+ return ((this.items.length > 0) ? this.items[this.items.length - 1] : null);
+ };
+ this.value = function () {
+ return ((this.token()) ? this.token().value.toString() : "");
+ };
+ this.type = function () {
+ return ((this.token()) ? this.token().type.toString() : "");
+ };
+ this.subtype = function () {
+ return ((this.token()) ? this.token().subtype.toString() : "");
+ };
+
+ }
+
+ function getTokens(formula) {
+
+ var tokens = new F_tokens(),
+ tokenStack = new F_tokenStack(),
+
+ offset = 0,
+
+ currentChar = function () {
+ return formula.substr(offset, 1);
+ },
+ doubleChar = function () {
+ return formula.substr(offset, 2);
+ },
+ nextChar = function () {
+ return formula.substr(offset + 1, 1);
+ },
+ EOF = function () {
+ return (offset >= formula.length);
+ },
+
+ token = "",
+
+ inString = false,
+ inPath = false,
+ inRange = false,
+ inError = false,
+ regexSN = /^[1-9]{1}(\.[0-9]+)?E{1}$/;
+
+ while (formula.length > 0) {
+ if (formula.substr(0, 1) === " ") {
+ formula = formula.substr(1);
+ } else {
+ if (formula.substr(0, 1) === "=") {
+ formula = formula.substr(1);
+ }
+ break;
+ }
+ }
+
+
+
+ while (!EOF()) {
+
+ // state-dependent character evaluation (order is important)
+ // double-quoted strings
+ // embeds are doubled
+ // end marks token
+ if (inString) {
+ if (currentChar() === "\"") {
+ if (nextChar() === "\"") {
+ token += "\"";
+ offset += 1;
+ } else {
+ inString = false;
+ tokens.add(token, TOK_TYPE_OPERAND, TOK_SUBTYPE_TEXT);
+ token = "";
+ }
+ } else {
+ token += currentChar();
+ }
+ offset += 1;
+ continue;
+ }
+
+ // single-quoted strings (links)
+ // embeds are double
+ // end does not mark a token
+ if (inPath) {
+ if (currentChar() === "'") {
+
+ if (nextChar() === "'") {
+ token += "'";
+ offset += 1;
+ } else {
+ inPath = false;
+ token += "'";
+ }
+ } else {
+ token += currentChar();
+ }
+
+ offset += 1;
+ continue;
+ }
+
+ // bracked strings (range offset or linked workbook name)
+ // no embeds (changed to "()" by Excel)
+ // end does not mark a token
+ if (inRange) {
+ if (currentChar() === "]") {
+ inRange = false;
+ }
+ token += currentChar();
+ offset += 1;
+ continue;
+ }
+
+ // error values
+ // end marks a token, determined from absolute list of values
+ if (inError) {
+ token += currentChar();
+ offset += 1;
+ if ((",#NULL!,#DIV/0!,#VALUE!,#REF!,#NAME?,#NUM!,#N/A,").indexOf("," + token + ",") !== -1) {
+ inError = false;
+ tokens.add(token, TOK_TYPE_OPERAND, TOK_SUBTYPE_ERROR);
+ token = "";
+ }
+ continue;
+ }
+
+ // scientific notation check
+ if (("+-").indexOf(currentChar()) !== -1) {
+ if (token.length > 1) {
+ if (token.match(regexSN)) {
+ token += currentChar();
+ offset += 1;
+ continue;
+ }
+ }
+ }
+
+ // independent character evaulation (order not important)
+ // establish state-dependent character evaluations
+ if (currentChar() === "\"") {
+ if (token.length > 0) {
+ // not expected
+ tokens.add(token, TOK_TYPE_UNKNOWN);
+ token = "";
+ }
+ inString = true;
+ offset += 1;
+ continue;
+ }
+
+ if (currentChar() === "'") {
+ if (token.length > 0) {
+ // not expected
+ tokens.add(token, TOK_TYPE_UNKNOWN);
+ token = "";
+ }
+ token = "'"
+ inPath = true;
+ offset += 1;
+ continue;
+ }
+
+ if (currentChar() === "[") {
+ inRange = true;
+ token += currentChar();
+ offset += 1;
+ continue;
+ }
+
+ if (currentChar() === "#") {
+ if (token.length > 0) {
+ // not expected
+ tokens.add(token, TOK_TYPE_UNKNOWN);
+ token = "";
+ }
+ inError = true;
+ token += currentChar();
+ offset += 1;
+ continue;
+ }
+
+ // mark start and end of arrays and array rows
+ if (currentChar() === "{") {
+ if (token.length > 0) {
+ // not expected
+ tokens.add(token, TOK_TYPE_UNKNOWN);
+ token = "";
+ }
+ tokenStack.push(tokens.add("ARRAY", TOK_TYPE_FUNCTION, TOK_SUBTYPE_START));
+ tokenStack.push(tokens.add("ARRAYROW", TOK_TYPE_FUNCTION, TOK_SUBTYPE_START));
+ offset += 1;
+ continue;
+ }
+
+ if (currentChar() === ";" ) {
+ if(root.excelFormulaUtilities.isEu){
+ // If is EU then handle ; as list seperators
+ if (token.length > 0) {
+ tokens.add(token, TOK_TYPE_OPERAND);
+ token = "";
+ }
+ if (tokenStack.type() !== TOK_TYPE_FUNCTION) {
+ tokens.add(currentChar(), TOK_TYPE_OP_IN, TOK_SUBTYPE_UNION);
+ } else {
+ tokens.add(currentChar(), TOK_TYPE_ARGUMENT);
+ }
+ offset += 1;
+ continue;
+ } else {
+ // Else if not Eu handle ; as array row seperator
+ if (token.length > 0) {
+ tokens.add(token, TOK_TYPE_OPERAND);
+ token = "";
+ }
+ tokens.addRef(tokenStack.pop());
+ tokens.add(",", TOK_TYPE_ARGUMENT);
+ tokenStack.push(tokens.add("ARRAYROW", TOK_TYPE_FUNCTION, TOK_SUBTYPE_START));
+ offset += 1;
+ continue;
+ }
+ }
+
+ if (currentChar() === "}") {
+ if (token.length > 0) {
+ tokens.add(token, TOK_TYPE_OPERAND);
+ token = "";
+ }
+ tokens.addRef(tokenStack.pop("ARRAYROWSTOP"));
+ tokens.addRef(tokenStack.pop("ARRAYSTOP"));
+ offset += 1;
+ continue;
+ }
+
+ // trim white-space
+ if (currentChar() === " ") {
+ if (token.length > 0) {
+ tokens.add(token, TOK_TYPE_OPERAND);
+ token = "";
+ }
+ tokens.add("", TOK_TYPE_WSPACE);
+ offset += 1;
+ while ((currentChar() === " ") && (!EOF())) {
+ offset += 1;
+ }
+ continue;
+ }
+
+ // multi-character comparators
+ if ((",>=,<=,<>,").indexOf("," + doubleChar() + ",") !== -1) {
+ if (token.length > 0) {
+ tokens.add(token, TOK_TYPE_OPERAND);
+ token = "";
+ }
+ tokens.add(doubleChar(), TOK_TYPE_OP_IN, TOK_SUBTYPE_LOGICAL);
+ offset += 2;
+ continue;
+ }
+
+ // standard infix operators
+ if (("+-*/^&=><").indexOf(currentChar()) !== -1) {
+ if (token.length > 0) {
+ tokens.add(token, TOK_TYPE_OPERAND);
+ token = "";
+ }
+ tokens.add(currentChar(), TOK_TYPE_OP_IN);
+ offset += 1;
+ continue;
+ }
+
+ // standard postfix operators
+ if (("%").indexOf(currentChar()) !== -1) {
+ if (token.length > 0) {
+ tokens.add(token, TOK_TYPE_OPERAND);
+ token = "";
+ }
+ tokens.add(currentChar(), TOK_TYPE_OP_POST);
+ offset += 1;
+ continue;
+ }
+
+ // start subexpression or function
+ if (currentChar() === "(") {
+ if (token.length > 0) {
+ tokenStack.push(tokens.add(token, TOK_TYPE_FUNCTION, TOK_SUBTYPE_START));
+ token = "";
+ } else {
+ tokenStack.push(tokens.add("", TOK_TYPE_SUBEXPR, TOK_SUBTYPE_START));
+ }
+ offset += 1;
+ continue;
+ }
+
+ // function, subexpression, array parameters
+ if (currentChar() === "," && !root.excelFormulaUtilities.isEu) {
+ if (token.length > 0) {
+ tokens.add(token, TOK_TYPE_OPERAND);
+ token = "";
+ }
+ if (tokenStack.type() !== TOK_TYPE_FUNCTION) {
+ tokens.add(currentChar(), TOK_TYPE_OP_IN, TOK_SUBTYPE_UNION);
+ } else {
+ tokens.add(currentChar(), TOK_TYPE_ARGUMENT);
+ }
+ offset += 1;
+ continue;
+ }
+
+ // stop subexpression
+ if (currentChar() === ")") {
+ if (token.length > 0) {
+ tokens.add(token, TOK_TYPE_OPERAND);
+ token = "";
+ }
+ tokens.addRef(tokenStack.pop());
+ offset += 1;
+ continue;
+ }
+
+ // token accumulation
+ token += currentChar();
+ offset += 1;
+
+ }
+
+ // dump remaining accumulation
+ if (token.length > 0) {
+ tokens.add(token, TOK_TYPE_OPERAND);
+ }
+
+ // move all tokens to a new collection, excluding all unnecessary white-space tokens
+ var tokens2 = new F_tokens();
+
+ while (tokens.moveNext()) {
+
+ token = tokens.current();
+
+ if (token.type.toString() === TOK_TYPE_WSPACE) {
+ var doAddToken = (tokens.BOF()) || (tokens.EOF());
+ //if ((tokens.BOF()) || (tokens.EOF())) {}
+ doAddToken = doAddToken && (((tokens.previous().type.toString() === TOK_TYPE_FUNCTION) && (tokens.previous().subtype.toString() === TOK_SUBTYPE_STOP)) || ((tokens.previous().type.toString() === TOK_TYPE_SUBEXPR) && (tokens.previous().subtype.toString() === TOK_SUBTYPE_STOP)) || (tokens.previous().type.toString() === TOK_TYPE_OPERAND));
+ //else if (!(
+ // ((tokens.previous().type === TOK_TYPE_FUNCTION) && (tokens.previous().subtype == TOK_SUBTYPE_STOP))
+ // || ((tokens.previous().type == TOK_TYPE_SUBEXPR) && (tokens.previous().subtype == TOK_SUBTYPE_STOP))
+ // || (tokens.previous().type == TOK_TYPE_OPERAND)))
+ // {}
+ doAddToken = doAddToken && (((tokens.next().type.toString() === TOK_TYPE_FUNCTION) && (tokens.next().subtype.toString() === TOK_SUBTYPE_START)) || ((tokens.next().type.toString() === TOK_TYPE_SUBEXPR) && (tokens.next().subtype.toString() === TOK_SUBTYPE_START)) || (tokens.next().type.toString() === TOK_TYPE_OPERAND));
+ //else if (!(
+ // ((tokens.next().type == TOK_TYPE_FUNCTION) && (tokens.next().subtype == TOK_SUBTYPE_START))
+ // || ((tokens.next().type == TOK_TYPE_SUBEXPR) && (tokens.next().subtype == TOK_SUBTYPE_START))
+ // || (tokens.next().type == TOK_TYPE_OPERAND)))
+ // {}
+ //else { tokens2.add(token.value, TOK_TYPE_OP_IN, TOK_SUBTYPE_INTERSECT)};
+ if (doAddToken) {
+ tokens2.add(token.value.toString(), TOK_TYPE_OP_IN, TOK_SUBTYPE_INTERSECT);
+ }
+ continue;
+ }
+
+ tokens2.addRef(token);
+
+ }
+
+ // switch infix "-" operator to prefix when appropriate, switch infix "+" operator to noop when appropriate, identify operand
+ // and infix-operator subtypes, pull "@" from in front of function names
+ while (tokens2.moveNext()) {
+
+ token = tokens2.current();
+
+ if ((token.type.toString() === TOK_TYPE_OP_IN) && (token.value.toString() === "-")) {
+ if (tokens2.BOF()) {
+ token.type = TOK_TYPE_OP_PRE.toString();
+ } else if (((tokens2.previous().type.toString() === TOK_TYPE_FUNCTION) && (tokens2.previous().subtype.toString() === TOK_SUBTYPE_STOP)) || ((tokens2.previous().type.toString() === TOK_TYPE_SUBEXPR) && (tokens2.previous().subtype.toString() === TOK_SUBTYPE_STOP)) || (tokens2.previous().type.toString() === TOK_TYPE_OP_POST) || (tokens2.previous().type.toString() === TOK_TYPE_OPERAND)) {
+ token.subtype = TOK_SUBTYPE_MATH.toString();
+ } else {
+ token.type = TOK_TYPE_OP_PRE.toString();
+ }
+ continue;
+ }
+
+ if ((token.type.toString() === TOK_TYPE_OP_IN) && (token.value.toString() === "+")) {
+ if (tokens2.BOF()) {
+ token.type = TOK_TYPE_NOOP.toString();
+ } else if (((tokens2.previous().type.toString() === TOK_TYPE_FUNCTION) && (tokens2.previous().subtype.toString() === TOK_SUBTYPE_STOP)) || ((tokens2.previous().type.toString() === TOK_TYPE_SUBEXPR) && (tokens2.previous().subtype.toString() === TOK_SUBTYPE_STOP)) || (tokens2.previous().type.toString() === TOK_TYPE_OP_POST) || (tokens2.previous().type.toString() === TOK_TYPE_OPERAND)) {
+ token.subtype = TOK_SUBTYPE_MATH.toString();
+ } else {
+ token.type = TOK_TYPE_NOOP.toString();
+ }
+ continue;
+ }
+
+ if ((token.type.toString() === TOK_TYPE_OP_IN) && (token.subtype.length === 0)) {
+ if (("<>=").indexOf(token.value.substr(0, 1)) !== -1) {
+ token.subtype = TOK_SUBTYPE_LOGICAL.toString();
+ } else if (token.value.toString() === "&") {
+ token.subtype = TOK_SUBTYPE_CONCAT.toString();
+ } else {
+ token.subtype = TOK_SUBTYPE_MATH.toString();
+ }
+ continue;
+ }
+
+ if ((token.type.toString() === TOK_TYPE_OPERAND) && (token.subtype.length === 0)) {
+ if (isNaN(parseFloat(token.value))) {
+ if ((token.value.toString() === 'TRUE') || (token.value.toString() === 'FALSE')) {
+ token.subtype = TOK_SUBTYPE_LOGICAL.toString();
+ } else {
+ token.subtype = TOK_SUBTYPE_RANGE.toString();
+ }
+ } else {
+ token.subtype = TOK_SUBTYPE_NUMBER.toString();
+ }
+
+ continue;
+ }
+
+ if (token.type.toString() === TOK_TYPE_FUNCTION) {
+ if (token.value.substr(0, 1) === "@") {
+ token.value = token.value.substr(1).toString();
+ }
+ continue;
+ }
+
+ }
+
+ tokens2.reset();
+
+ // move all tokens to a new collection, excluding all noops
+ tokens = new F_tokens();
+
+ while (tokens2.moveNext()) {
+ if (tokens2.current().type.toString() !== TOK_TYPE_NOOP) {
+ tokens.addRef(tokens2.current());
+ }
+ }
+
+ tokens.reset();
+
+ return tokens;
+ }
+
+
+ var parseFormula = excelFormulaUtilities.parseFormula = function (inputID, outputID) {
+
+
+ var indentCount = 0;
+
+ var indent = function () {
+ var s = "|",
+ i = 0;
+ for (; i < indentCount; i += 1) {
+ s += " |";
+ }
+ return s;
+ };
+
+ var formulaControl = document.getElementById(inputID);
+ var formula = formulaControl.value;
+
+ var tokens = getTokens(formula);
+
+ var tokensHtml = "";
+
+ tokensHtml += "";
+ tokensHtml += "";
+ tokensHtml += "index | ";
+ tokensHtml += "type | ";
+ tokensHtml += "subtype | ";
+ tokensHtml += "token | ";
+ tokensHtml += "token tree |
";
+
+ while (tokens.moveNext()) {
+
+ var token = tokens.current();
+
+ if (token.subtype === TOK_SUBTYPE_STOP) {
+ indentCount -= ((indentCount > 0) ? 1 : 0);
+ }
+
+ tokensHtml += "";
+
+ tokensHtml += "" + (tokens.index + 1) + " | ";
+
+ tokensHtml += "" + token.type + " | ";
+ tokensHtml += "" + ((token.subtype.length === 0) ? " " : token.subtype.toString()) + " | ";
+ tokensHtml += "" + ((token.value.length === 0) ? " " : token.value).split(" ").join(" ") + " | ";
+ tokensHtml += "" + indent() + ((token.value.length === 0) ? " " : token.value).split(" ").join(" ") + " | ";
+
+ tokensHtml += "
";
+
+ if (token.subtype === TOK_SUBTYPE_START) {
+ indentCount += 1;
+ }
+
+ }
+
+ tokensHtml += "
";
+
+ document.getElementById(outputID).innerHTML = tokensHtml;
+
+ formulaControl.select();
+ formulaControl.focus();
+
+ };
+
+ // Pass a range such as A1:B2 along with a
+ // delimiter to get back a full list of ranges.
+ //
+ // Example:
+ // breakOutRanges("A1:B2", "+"); //Returns A1+A2+B1+B2
+ function breakOutRanges(rangeStr, delimStr){
+
+ //Quick Check to see if if rangeStr is a valid range
+ if ( !RegExp("[a-z]+[0-9]+:[a-z]+[0-9]+","gi").test(rangeStr) ){
+ throw "This is not a valid range: " + rangeStr;
+ }
+
+ //Make the rangeStr lowercase to deal with looping.
+ var range = rangeStr.split(":"),
+
+ startRow = parseInt(range[0].match(/[0-9]+/gi)[0]),
+ startCol = range[0].match(/[A-Z]+/gi)[0],
+ startColDec = fromBase26(startCol)
+
+ endRow = parseInt(range[1].match(/[0-9]+/gi)[0]),
+ endCol = range[1].match(/[A-Z]+/gi)[0],
+ endColDec = fromBase26(endCol),
+
+ // Total rows and cols
+ totalRows = endRow - startRow + 1,
+ totalCols = fromBase26(endCol) - fromBase26(startCol) + 1,
+
+ // Loop vars
+ curCol = 0,
+ curRow = 1 ,
+ curCell = "",
+
+ //Return String
+ retStr = "";
+
+ for(; curRow <= totalRows; curRow+=1){
+ for(; curCol < totalCols; curCol+=1){
+ // Get the current cell id
+ curCell = toBase26(startColDec + curCol) + "" + (startRow+curRow-1) ;
+ retStr += curCell + (curRow===totalRows && curCol===totalCols-1 ? "" : delimStr);
+ }
+ curCol=0;
+ }
+
+ return retStr;
+
+ }
+
+ //Modified from function at http://en.wikipedia.org/wiki/Hexavigesimal
+ var toBase26 = excelFormulaUtilities.toBase26 = function( value ) {
+
+ value = Math.abs(value);
+
+ var converted = ""
+ ,iteration = false
+ ,remainder;
+
+ // Repeatedly divide the numerb by 26 and convert the
+ // remainder into the appropriate letter.
+ do {
+ remainder = value % 26;
+
+ // Compensate for the last letter of the series being corrected on 2 or more iterations.
+ if (iteration && value < 25) {
+ remainder--;
+ }
+
+ converted = String.fromCharCode((remainder + 'A'.charCodeAt(0))) + converted;
+ value = Math.floor((value - remainder) / 26);
+
+ iteration = true;
+ } while (value > 0);
+
+ return converted;
+ }
+
+ // This was Modified from a function at http://en.wikipedia.org/wiki/Hexavigesimal
+ // Pass in the base 26 string, get back integer
+ var fromBase26 = excelFormulaUtilities.fromBase26 = function (number) {
+ number = number.toUpperCase();
+
+ var s = 0
+ ,i = 0
+ ,dec = 0;
+
+ if (
+ number !== null
+ && typeof number !== "undefined"
+ && number.length > 0
+ ) {
+ for (; i < number.length; i++) {
+ s = number.charCodeAt(number.length - i - 1) - "A".charCodeAt(0);
+ dec += (Math.pow(26, i)) * (s+1);
+ }
+ }
+
+ return dec - 1;
+ }
+
+ function applyTokenTemplate(token, options, indent, lineBreak, override) {
+
+ var indt = indent;
+
+ var lastToken = typeof arguments[5] === undefined || arguments[5] === null ? null : arguments[5];
+
+ var replaceTokenTmpl = function (inStr) {
+ return inStr.replace(/\{\{token\}\}/gi, "{0}").replace(/\{\{autoindent\}\}/gi, "{1}").replace(/\{\{autolinebreak\}\}/gi, "{2}");
+ };
+
+ var tokenString = "";
+
+ if (token.subtype === "text" || token.type === "text") {
+ tokenString = token.value.toString();
+ } else if ( token.type === 'operand' && token.subtype === 'range') {
+ tokenString = token.value.toString() ;
+ } else {
+ tokenString = ((token.value.length === 0) ? " " : token.value.toString()).split(" ").join("").toString();
+ }
+
+ if (typeof override === 'function') {
+ var returnVal = override(tokenString, token, indent, lineBreak);
+
+ tokenString = returnVal.tokenString;
+
+ if (!returnVal.useTemplate) {
+ return tokenString;
+ }
+ }
+
+ switch (token.type) {
+
+ case "function":
+ //-----------------FUNCTION------------------
+ switch (token.value) {
+ case "ARRAY":
+ tokenString = formatStr(replaceTokenTmpl(options.tmplFunctionStartArray), tokenString, indt, lineBreak);
+ break;
+ case "ARRAYROW":
+ tokenString = formatStr(replaceTokenTmpl(options.tmplFunctionStartArrayRow), tokenString, indt, lineBreak);
+ break;
+ default:
+ if (token.subtype.toString() === "start") {
+ tokenString = formatStr(replaceTokenTmpl(options.tmplFunctionStart), tokenString, indt, lineBreak);
+ } else {
+ tokenString = formatStr(replaceTokenTmpl(options.tmplFunctionStop), tokenString, indt, lineBreak);
+ }
+ break;
+ }
+ break;
+ case "operand":
+ //-----------------OPERAND------------------
+ switch (token.subtype.toString()) {
+ case "error":
+ tokenString = formatStr(replaceTokenTmpl(options.tmplOperandError), tokenString, indt, lineBreak);
+ break;
+ case "range":
+ tokenString = formatStr(replaceTokenTmpl(options.tmplOperandRange), tokenString, indt, lineBreak);
+ break;
+ case "logical":
+ tokenString = formatStr(replaceTokenTmpl(options.tmplOperandLogical), tokenString, indt, lineBreak);
+ break;
+ case "number":
+ tokenString = formatStr(replaceTokenTmpl(options.tmplOperandNumber), tokenString, indt, lineBreak);
+ break;
+ case "text":
+ tokenString = formatStr(replaceTokenTmpl(options.tmplOperandText), tokenString, indt, lineBreak);
+ break;
+ case "argument":
+ tokenString = formatStr(replaceTokenTmpl(options.tmplArgument), tokenString, indt, lineBreak);
+ break;
+ default:
+ break;
+ }
+ break;
+ case "operator-infix":
+ tokenString = formatStr(replaceTokenTmpl(options.tmplOperandOperatorInfix), tokenString, indt, lineBreak);
+ break;
+ case "logical":
+ tokenString = formatStr(replaceTokenTmpl(options.tmplLogical), tokenString, indt, lineBreak);
+ break;
+ case "argument":
+ if(lastToken.type !== "argument"){
+ tokenString = formatStr(replaceTokenTmpl(options.tmplArgument), tokenString, indt, lineBreak);
+ } else {
+ tokenString = formatStr(replaceTokenTmpl("{{autoindent}}"+options.tmplArgument), tokenString, indt, lineBreak);
+ }
+ break;
+ case "subexpression":
+ if (token.subtype.toString() === "start") {
+ tokenString = formatStr(replaceTokenTmpl(options.tmplSubexpressionStart), tokenString, indt, lineBreak);
+ } else {
+ tokenString = formatStr(replaceTokenTmpl(options.tmplSubexpressionStop), tokenString, indt, lineBreak);
+ }
+ break;
+ default:
+
+ break;
+ }
+ return tokenString;
+ };
+
+ /**
+ *
+ * @memberof excelFormulaUtilities.parser
+ * @function
+ * @param {string} formula
+ * @param {object} options optional param
+ *
+ * TEMPLATE VALUES
+ * {{autoindent}} - apply auto indent based on current tree level
+ * {{token}} - the named token such as FUNCTION_NAME or "string"
+ * {{autolinebreak}} - apply linbreak automaticly. tests for next element only at this point
+ *
+ * Options include:
+ * tmplFunctionStart - template for the start of a function, the {{token}} will contain the name of the function.
+ * tmplFunctionStop - template for when the end of a function has been reached.
+ * tmplOperandError - template for errors.
+ * tmplOperandRange - template for ranges and variable names.
+ * tmplOperandLogical - template for logical operators such as + - = ...
+ * tmplOperandNumber - template for numbers.
+ * tmplOperandText - template for text/strings.
+ * tmplArgument - template for argument seperators such as ,.
+ * tmplFunctionStartArray - template for the start of an array.
+ * tmplFunctionStartArrayRow - template for the start of an array row.
+ * tmplFunctionStopArrayRow - template for the end of an array row.
+ * tmplFunctionStopArray - template for the end of an array.
+ * tmplSubexpressionStart - template for the sub expresson start
+ * tmplSubexpressionStop - template for the sub expresson stop
+ * tmplIndentTab - template for the tab char.
+ * tmplIndentSpace - template for space char.
+ * autoLineBreak - when rendering line breaks automaticly which types should it break on. "TOK_SUBTYPE_STOP | TOK_SUBTYPE_START | TOK_TYPE_ARGUMENT"
+ * newLine - used for the {{autolinebreak}} replacement as well as some string parsing. if this is not set correctly you may get undesired results. usually \n for text or
for html
+ * trim: true - trim the output.
+ * customTokenRender: null - this is a call back to a custom token function. your call back should look like
+ * EXAMPLE:
+ *
+ * customTokenRender: function(tokenString, token, indent, linbreak){
+ * var outstr = token,
+ * useTemplate = true;
+ * // In the return object "useTemplate" tells formatFormula()
+ * // weather or not to apply the template to what your return from the "tokenString".
+ * return {tokenString: outstr, useTemplate: useTemplate};
+ * }
+ *
+ *
+ * @returns {string}
+ */
+ var formatFormula = excelFormulaUtilities.formatFormula = function (formula, options) {
+ //Quick fix for trailing space after = sign
+ formula = formula.replace(/^\s*=\s+/, "=");
+
+ var isFirstToken = true,
+ defaultOptions = {
+ tmplFunctionStart: '{{autoindent}}{{token}}(\n',
+ tmplFunctionStop: '\n{{autoindent}}{{token}})',
+ tmplOperandError: ' {{token}}',
+ tmplOperandRange: '{{autoindent}}{{token}}',
+ tmplLogical: '{{token}}{{autolinebreak}}',
+ tmplOperandLogical: '{{autoindent}}{{token}}',
+ tmplOperandNumber: '{{autoindent}}{{token}}',
+ tmplOperandText: '{{autoindent}}"{{token}}"',
+ tmplArgument: '{{token}}\n',
+ tmplOperandOperatorInfix: ' {{token}}{{autolinebreak}}',
+ tmplFunctionStartArray: '',
+ tmplFunctionStartArrayRow: '{',
+ tmplFunctionStopArrayRow: '}',
+ tmplFunctionStopArray: '',
+ tmplSubexpressionStart: '{{autoindent}}(\n',
+ tmplSubexpressionStop: '\n)',
+ tmplIndentTab: '\t',
+ tmplIndentSpace: ' ',
+ autoLineBreak: 'TOK_TYPE_FUNCTION | TOK_TYPE_ARGUMENT | TOK_SUBTYPE_LOGICAL | TOK_TYPE_OP_IN ',
+ newLine: '\n',
+ //trim: true,
+ customTokenRender: null,
+ prefix: "",
+ postfix: ""
+ };
+
+ if (options) {
+ options = core.extend(true, defaultOptions, options);
+ } else {
+ options = defaultOptions;
+ }
+
+ var indentCount = 0;
+
+ var indent = function () {
+ var s = "",
+ i = 0;
+
+ for (; i < indentCount; i += 1) {
+ s += options.tmplIndentTab;
+ }
+ return s;
+ };
+
+ var tokens = getTokens(formula);
+
+ var outputFormula = "";
+
+ var autoBreakArray = options.autoLineBreak.replace(/\s/gi, "").split("|");
+
+ //Tokens
+ var isNewLine = true;
+
+ var testAutoBreak = function (nextToken) {
+ var i = 0;
+ for (; i < autoBreakArray.length; i += 1) {
+ if (nextToken !== null && typeof nextToken !== 'undefined' && (types[autoBreakArray[i]] === nextToken.type.toString() || types[autoBreakArray[i]] === nextToken.subtype.toString())) {
+ return true;
+ }
+ }
+ return false;
+ };
+
+ var lastToken = null;
+
+ while (tokens.moveNext()) {
+
+ var token = tokens.current();
+ var nextToken = tokens.next();
+
+ if (token.subtype.toString() === TOK_SUBTYPE_STOP) {
+ indentCount -= ((indentCount > 0) ? 1 : 0);
+ }
+
+ var matchBeginNewline = new RegExp('^' + options.newLine, ''),
+ matchEndNewLine = new RegExp(options.newLine + '$', ''),
+ autoBreak = testAutoBreak(nextToken),
+ autoIndent = isNewLine,
+ indt = autoIndent ? indent() : options.tmplIndentSpace,
+ lineBreak = autoBreak ? options.newLine : "";
+
+ // TODO this strips out spaces which breaks part of issue 28. 'Data Sheet' gets changed to DataSheet
+ outputFormula += applyTokenTemplate(token, options, indt, lineBreak, options.customTokenRender, lastToken);
+
+ if (token.subtype.toString() === TOK_SUBTYPE_START) {
+ indentCount += 1;
+
+ }
+
+ isNewLine = autoBreak || matchEndNewLine.test(outputFormula);
+ isFirstToken = false;
+
+ lastToken = token;
+ }
+
+ outputFormula = options.prefix + trim(outputFormula) + options.postfix;
+
+ return outputFormula;
+ };
+ /**
+ * This function calls {@link excelFormulaUtilities.parser.formatFormula}
+ *
+ * @memberof excelFormulaUtilities.parser
+ * @function
+ * @param {string} formula
+ * @param {object} options optional param
+ */
+ var formatFormulaHTML = excelFormulaUtilities.formatFormulaHTML = function (formula) {
+ var options = {
+ tmplFunctionStart: '{{autoindent}}{{token}}(
',
+ tmplFunctionStop: '
{{autoindent}}{{token}})',
+ tmplOperandText: '{{autoindent}}"{{token}}"',
+ tmplArgument: '{{token}}
',
+ tmplSubexpressionStart: '{{autoindent}}(',
+ tmplSubexpressionStop: ' )',
+ tmplIndentTab: ' ',
+ tmplIndentSpace: ' ',
+ newLine: '
',
+ autoLineBreak: 'TOK_TYPE_FUNCTION | TOK_TYPE_ARGUMENT | TOK_SUBTYPE_LOGICAL | TOK_TYPE_OP_IN ',
+ trim: true,
+ prefix: "=",
+ customTokenRender: null
+ };
+
+ return formatFormula(formula, options);
+ }
+
+ /**
+ *
+ * @memberof excelFormulaUtilities.convert
+ * @function
+ * @param {string} formula
+ * @returns {string}
+ */
+ var formula2CSharp = excelFormulaUtilities.formula2CSharp = function (formula) {
+
+ //Custom callback to format as c#
+ var functionStack = [];
+
+ var tokRender = function (tokenStr, token, indent, linbreak) {
+ var outstr = "",
+ /*tokenString = (token.value.length === 0) ? "" : token.value.toString(),*/
+ tokenString = tokenStr,
+ directConversionMap = {
+ "=": "==",
+ "<>": "!=",
+ "MIN": "Math.Min",
+ "MAX": "Math.Max",
+ "ABS": "Math.ABS",
+ "SUM": "",
+ "IF": "",
+ "&": "+"
+ },
+ currentFunctionOnStack = functionStack[functionStack.length - 1],
+ useTemplate = false;
+
+ switch (token.type.toString()) {
+
+ case TOK_TYPE_FUNCTION:
+
+ switch (token.subtype) {
+
+ case TOK_SUBTYPE_START:
+
+ functionStack.push({
+ name: tokenString,
+ argumentNumber: 0
+ });
+ outstr = typeof directConversionMap[tokenString.toUpperCase()] === "string" ? directConversionMap[tokenString.toUpperCase()] : tokenString;
+ useTemplate = true;
+
+ break;
+
+ case TOK_SUBTYPE_STOP:
+
+ useTemplate = true;
+ switch (currentFunctionOnStack.name.toLowerCase()) {
+ case "if":
+ outstr = ")";
+ useTemplate = false;
+ break;
+ default:
+ outstr = typeof directConversionMap[tokenString.toUpperCase()] === "string" ? directConversionMap[tokenString.toUpperCase()] : tokenString;
+ break
+ }
+ functionStack.pop();
+ break;
+ }
+
+ break;
+
+ case TOK_TYPE_ARGUMENT:
+ switch (currentFunctionOnStack.name.toLowerCase()) {
+ case "if":
+ switch (currentFunctionOnStack.argumentNumber) {
+ case 0:
+ outstr = "?";
+ break;
+ case 1:
+ outstr = ":";
+ break;
+ }
+ break;
+ case "sum":
+ outstr = "+";
+ break;
+ default:
+ outstr = typeof directConversionMap[tokenString.toUpperCase()] === "string" ? directConversionMap[tokenString.toUpperCase()] : tokenString;
+ useTemplate = true;
+ break;
+ }
+
+ currentFunctionOnStack.argumentNumber += 1;
+
+ break;
+
+ case TOK_TYPE_OPERAND:
+
+ switch (token.subtype) {
+
+ case TOK_SUBTYPE_RANGE:
+
+ switch (currentFunctionOnStack.name.toLowerCase()) {
+ // If in the sum function break aout cell names and add
+ case "sum":
+ //TODO make sure this is working
+ if(RegExp(":","gi").test(tokenString)){
+ outstr = breakOutRanges(tokenString, "+");
+ } else {
+ outStr = tokenString;
+ }
+
+ break;
+ // By Default return an array containing all cell names in array
+ default:
+ // Create array for ranges
+ if(RegExp(":","gi").test(tokenString)){
+ outstr = "[" + breakOutRanges(tokenString, ",") +"]";
+ } else {
+ outstr = tokenString;
+ }
+ //debugger;
+ break;
+ }
+
+ break;
+
+ default:
+ break;
+ }
+
+ default:
+ if( outstr === "" ){
+ outstr = typeof directConversionMap[tokenString.toUpperCase()] === "string" ? directConversionMap[tokenString.toUpperCase()] : tokenString;
+ }
+ useTemplate = true;
+ break;
+ }
+
+ return {
+ tokenString: outstr,
+ useTemplate: useTemplate
+ };
+ };
+
+ var cSharpOutput = formatFormula(
+ formula, {
+ tmplFunctionStart: '{{token}}(',
+ tmplFunctionStop: '{{token}})',
+ tmplOperandError: '{{token}}',
+ tmplOperandRange: '{{token}}',
+ tmplOperandLogical: '{{token}}',
+ tmplOperandNumber: '{{token}}',
+ tmplOperandText: '"{{token}}"',
+ tmplArgument: '{{token}}',
+ tmplOperandOperatorInfix: '{{token}}',
+ tmplFunctionStartArray: "",
+ tmplFunctionStartArrayRow: "{",
+ tmplFunctionStopArrayRow: "}",
+ tmplFunctionStopArray: "",
+ tmplSubexpressionStart: "(",
+ tmplSubexpressionStop: ")",
+ tmplIndentTab: "\t",
+ tmplIndentSpace: " ",
+ autoLineBreak: "TOK_SUBTYPE_STOP | TOK_SUBTYPE_START | TOK_TYPE_ARGUMENT",
+ trim: true,
+ customTokenRender: tokRender
+ });
+ return cSharpOutput;
+ };
+
+ /**
+ * Both the csharp and javascript are the same when converted, this is just an alias for convert2CSharp. there are some subtle differences such as == vrs ===, this will be addressed in a later version.
+ * @memberof excelFormulaUtilities.convert
+ * @function
+ * @param {string} formula
+ * @returns {string}
+ */
+ var formula2JavaScript = excelFormulaUtilities.formula2JavaScript = function (formula) {
+ return formula2CSharp(formula).replace('==', '===');
+ }
+
+ excelFormulaUtilities.getTokens = getTokens;
+
+}(window|| module.exports || {}));
+
+/*
+ * excelFormulaUtilitiesJS
+ * https://github.com/joshatjben/excelFormulaUtilitiesJS/
+ *
+ * Copyright 2011, Josh Bennett
+ * licensed under the MIT license.
+ * https://github.com/joshatjben/excelFormulaUtilitiesJS/blob/master/LICENSE.txt
+ *
+ * Some functionality based off of the jquery core lib
+ * Copyright 2011, John Resig
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * Based on Ewbi's Go Calc Prototype Excel Formula Parser. [http://ewbi.blogs.com/develops/2004/12/excel_formula_p.html]
+ */
+
+(function () {
+
+ if (typeof window === 'undefined') {
+ window = root;
+ }
+ var excelFormulaUtilities = window.excelFormulaUtilities = window.excelFormulaUtilities || {};
+ var core = window.excelFormulaUtilities.core = {};
+ window.excelFormulaUtilities.string = window.excelFormulaUtilities.string || {};
+
+ /**
+ * Simple/quick string formater. This will take an input string and apply n number of arguments to it.
+ *
+ * example:
+ *
+ *
+ * var foo = excelFormulaUtilities.core.formatStr("{0}", "foo"); // foo will be set to "foo"
+ * var fooBar = excelFormulaUtilities.core.formatStr("{0} {1}", "foo", "bar"); // fooBar will be set to "fooBar"
+ * var error = excelFormulaUtilities.core.formatStr("{1}", "error"); // will throw an index out of range error since only 1 extra argument was passed, which would be index 0.
+ *
+ *
+ *
+ * @memberOf window.excelFormulaUtilities.core
+ * @function
+ * @param {String} inStr
+ **/
+ var formatStr = window.excelFormulaUtilities.string.formatStr = function(inStr) {
+ var formattedStr = inStr;
+ var argIndex = 1;
+ for (; argIndex < arguments.length; argIndex++) {
+ var replaceIndex = (argIndex - 1);
+ var replaceRegex = new RegExp("\\{{1}" + replaceIndex.toString() + "{1}\\}{1}", "g");
+ formattedStr = formattedStr.replace(replaceRegex, arguments[argIndex]);
+ }
+ return formattedStr;
+ };
+
+ var trim = window.excelFormulaUtilities.string.trim = function(inStr){
+ return inStr.replace(/^\s|\s$/, "");
+ };
+
+ var trimHTML = window.excelFormulaUtilities.string.trim = function(inStr){
+ return inStr.replace(/^(?:\s| |<\s*br\s*\/*\s*>)*|(?:\s| |<\s*br\s*\/*\s*>)*$/, "");
+ };
+
+ //Quick and dirty type checks
+ /**
+ * @param {object} obj
+ * @returns {boolean}
+ * @memberOf window.excelFormulaUtilities.core
+ */
+ var isFunction = core.isFunction = function (obj) {
+ return (typeof obj) === "function";
+ };
+
+ /**
+ * @param {object} obj
+ * @returns {boolean}
+ * @memberOf window.excelFormulaUtilities.core
+ */
+ var isArray = core.isArray = function (obj) {
+ return (typeof obj) === "object" && obj.length;
+ };
+
+ /**
+ * @param {object} obj
+ * @returns {boolean}
+ * @memberOf window.excelFormulaUtilities.core
+ */
+ var isWindow = core.isWindow = function () {
+ return obj && typeof obj === "object" && "setInterval" in obj;
+ }; /*----The functionality below has based off of the jQuery core library----*/
+
+ /**
+ * Check if the object is a plain object or not. This has been pulled from the jQuery core and modified slightly.
+ * @param {object} obj
+ * @returns {boolean} returns weather the object is a plain object or not.
+ * @memberOf window.excelFormulaUtilities.core
+ */
+ var isPlainObject = core.isPlainObject = function (obj) {
+ // Must be an Object.
+ // Because of IE, we also have to check the presence of the constructor property.
+ // Make sure that DOM nodes and window objects don't pass through, as well
+ if (!obj || typeof obj !== "object" || obj.nodeType || isWindow(obj)) {
+ return false;
+ }
+ // Not own constructor property must be Object
+ if (obj.constructor && !hasOwnProperty.call(obj, "constructor") && !hasOwnProperty.call(obj.constructor.prototype, "isPrototypeOf")) {
+ return false;
+ }
+ // Own properties are enumerated firstly, so to speed up,
+ // if last one is own, then all properties are own.
+ var key;
+ for (key in obj) { }
+ return key === undefined || hasOwnProperty.call(obj, key);
+ };
+
+ /**
+ * This has been pulled from the jQuery core and modified slightly. see http://api.jquery.com/jQuery.extend/
+ * @param {object} target
+ * @param {object} object add one or more object to extend the target.
+ * @returns {object} returns the extended object.
+ * @memberOf window.excelFormulaUtilities.core
+ */
+ var extend = core.extend = function () {
+ var options, name, src, copy, copyIsArray, clone, target = arguments[0] || {},
+ i = 1,
+ length = arguments.length,
+ deep = false;
+ // Handle a deep copy situation
+ if (typeof target === "boolean") {
+ deep = target;
+ target = arguments[1] || {};
+ // skip the boolean and the target
+ i = 2;
+ }
+ // Handle case when target is a string or something (possible in deep copy)
+ if (typeof target !== "object" && !isFunction(target)) {
+ target = {};
+ }
+ // extend jQuery itself if only one argument is passed
+ if (length === i) {
+ target = this;
+ --i;
+ }
+ for (; i < length; i++) {
+ // Only deal with non-null/undefined values
+ if ((options = arguments[i]) != null) {
+ // Extend the base object
+ for (name in options) {
+ src = target[name];
+ copy = options[name];
+ // Prevent never-ending loop
+ if (target === copy) {
+ continue;
+ }
+ // Recurse if we're merging plain objects or arrays
+ if (deep && copy && (isPlainObject(copy) || (copyIsArray = isArray(copy)))) {
+ if (copyIsArray) {
+ copyIsArray = false;
+ clone = src && isArray(src) ? src : [];
+ } else {
+ clone = src && isPlainObject(src) ? src : {};
+ }
+ // Never move original objects, clone them
+ target[name] = core.extend(deep, clone, copy);
+ // Don't bring in undefined values
+ } else if (copy !== undefined) {
+ target[name] = copy;
+ }
+ }
+ }
+ }
+ // Return the modified object
+ return target;
+ }; /*----end of jquery functionality----*/
+
+
+}());
diff --git a/gulpfile.js b/gulpfile.js
index 4552e7b..528e859 100644
--- a/gulpfile.js
+++ b/gulpfile.js
@@ -5,10 +5,12 @@ var gulp = require('gulp'),
concat = require('gulp-concat'),
del = require('del');
-gulp.task('build', function() {
- return gulp.src('src/ExcelFormulaUtilities.js')
- .pipe(concat('src/ExcelFormulaUtilities.js'))
- .pipe(rename("excel-formula.js"))
- .pipe(gulp.dest('lib'))
- .pipe(notify({ message: 'Scripts task complete' }));
+gulp.task('minify', function() {
+ return gulp.src('src/*.js')
+ .pipe(concat('excel-formula.js'))
+ .pipe(gulp.dest('dist'))
+ .pipe(rename({suffix: '.min'}))
+ .pipe(uglify())
+ .pipe(gulp.dest('dist'))
+ .pipe(notify({ message: 'Scripts task complete' }));
});
diff --git a/src/main.coffee b/index.coffee
similarity index 100%
rename from src/main.coffee
rename to index.coffee
diff --git a/src/main.js b/index.js
similarity index 80%
rename from src/main.js
rename to index.js
index d047237..c906e1b 100644
--- a/src/main.js
+++ b/index.js
@@ -2,9 +2,9 @@
(function() {
var xl;
- require('./core');
+ require('./src/core');
- require('./ExcelFormulaUtilities');
+ require('./src/ExcelFormulaUtilities');
xl = excelFormulaUtilities;