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( "" );


 	
-	
+
 	

excelFormulaUtilities.formatFormulaHTML( "" );


 	
- +

excelFormulaUtilities.formula2CSharp( "" );

- +

excelFormulaUtilities.formula2Javascript( "" );


-	
\ 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 += ""; + tokensHtml += ""; + tokensHtml += ""; + tokensHtml += ""; + tokensHtml += ""; + + while (tokens.moveNext()) { + + var token = tokens.current(); + + if (token.subtype === TOK_SUBTYPE_STOP) { + indentCount -= ((indentCount > 0) ? 1 : 0); + } + + tokensHtml += ""; + + tokensHtml += ""; + + tokensHtml += ""; + tokensHtml += ""; + tokensHtml += ""; + tokensHtml += ""; + + tokensHtml += ""; + + if (token.subtype === TOK_SUBTYPE_START) { + indentCount += 1; + } + + } + + tokensHtml += "
indextypesubtypetokentoken tree
" + (tokens.index + 1) + "" + token.type + "" + ((token.subtype.length === 0) ? " " : token.subtype.toString()) + "" + ((token.value.length === 0) ? " " : token.value).split(" ").join(" ") + "" + indent() + ((token.value.length === 0) ? " " : token.value).split(" ").join(" ") + "
"; + + 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+="",u+="",u+="",u+="",u+="";a.moveNext();){var p=a.current();p.subtype===v&&(n-=n>0?1:0),u+="",u+="",u+="",u+="",u+="",u+="",u+="",p.subtype===x&&(n+=1)}u+="
indextypesubtypetokentoken tree
"+(a.index+1)+""+p.type+""+(0===p.subtype.length?" ":p.subtype.toString())+""+(0===p.value.length?" ":p.value).split(" ").join(" ")+""+r()+(0===p.value.length?" ":p.value).split(" ").join(" ")+"
",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 += ""; + tokensHtml += ""; + tokensHtml += ""; + tokensHtml += ""; + tokensHtml += ""; + + while (tokens.moveNext()) { + + var token = tokens.current(); + + if (token.subtype === TOK_SUBTYPE_STOP) { + indentCount -= ((indentCount > 0) ? 1 : 0); + } + + tokensHtml += ""; + + tokensHtml += ""; + + tokensHtml += ""; + tokensHtml += ""; + tokensHtml += ""; + tokensHtml += ""; + + tokensHtml += ""; + + if (token.subtype === TOK_SUBTYPE_START) { + indentCount += 1; + } + + } + + tokensHtml += "
indextypesubtypetokentoken tree
" + (tokens.index + 1) + "" + token.type + "" + ((token.subtype.length === 0) ? " " : token.subtype.toString()) + "" + ((token.value.length === 0) ? " " : token.value).split(" ").join(" ") + "" + indent() + ((token.value.length === 0) ? " " : token.value).split(" ").join(" ") + "
"; + + 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;