diff --git a/EPPlus/EPPlus.csproj b/EPPlus/EPPlus.csproj index a6c03aab..b7f3a284 100644 --- a/EPPlus/EPPlus.csproj +++ b/EPPlus/EPPlus.csproj @@ -1,894 +1,894 @@ - - - - Debug - AnyCPU - 9.0.30729 - 2.0 - {BE4A6343-F411-44A3-8D6F-F40747ED7BA5} - Library - Properties - OfficeOpenXml - EPPlus - true - OpenOfficeXml.snk - - - - - - - - - - - - - 3.5 - v3.5 - publish\ - true - Disk - false - Foreground - 7 - Days - false - false - true - 0 - 1.0.0.%2a - false - false - true - Client - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - bin\Debug\EPPlus.XML - AllRules.ruleset - AnyCPU - 15191, CS1591 - false - true - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - bin\Release\EPPlus.XML - AllRules.ruleset - AnyCPU - false - - - bin\ - false - - - - - ..\..\..\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v3.5\Profile\Client\PresentationFramework.dll - - - - ..\..\..\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v3.5\Profile\Client\System.configuration.dll - - - C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.5\System.Core.dll - - - - - - - ..\..\..\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v3.5\Profile\Client\WindowsBase.dll - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Code - - - Code - - - - - - - - - - - - - - - - - - Code - - - - - Code - - - - Code - - - - - Code - - - Code - - - - - Code - - - Code - - - - - - - - - - - - - - - - - Code - - - - - - - - - - - - - - - - - - - Code - - - - - - - - - - Code - - - Code - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Code - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Code - - - - - Code - - - - Code - - - Code - - - - - - Code - - - - Code - - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - - - - - - - - - - - Code - - - Code - - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - False - .NET Framework 3.5 SP1 Client Profile - false - - - False - .NET Framework 3.5 SP1 - true - - - False - Windows Installer 3.1 - true - - - - - - - + + + + Debug + AnyCPU + 9.0.30729 + 2.0 + {BE4A6343-F411-44A3-8D6F-F40747ED7BA5} + Library + Properties + OfficeOpenXml + EPPlus + true + OpenOfficeXml.snk + + + + + + + + + + + + + 3.5 + v3.5 + publish\ + true + Disk + false + Foreground + 7 + Days + false + false + true + 0 + 1.0.0.%2a + false + false + true + Client + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + bin\Debug\EPPlus.XML + AllRules.ruleset + AnyCPU + 15191, CS1591 + false + true + + + pdbonly + true + ..\..\..\lib\ + TRACE + prompt + 4 + bin\Release\EPPlus.XML + AllRules.ruleset + AnyCPU + false + + + bin\ + false + + + + + ..\..\..\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v3.5\Profile\Client\PresentationFramework.dll + + + + ..\..\..\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v3.5\Profile\Client\System.configuration.dll + + + C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.5\System.Core.dll + + + + + + + ..\..\..\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v3.5\Profile\Client\WindowsBase.dll + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Code + + + Code + + + + + + + + + + + + + + + + + + Code + + + + + Code + + + + Code + + + + + Code + + + Code + + + + + Code + + + Code + + + + + + + + + + + + + + + + + Code + + + + + + + + + + + + + + + + + + + Code + + + + + + + + + + Code + + + Code + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Code + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Code + + + + + Code + + + + Code + + + Code + + + + + + Code + + + + Code + + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + + + + + + + + + + + Code + + + Code + + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + False + .NET Framework 3.5 SP1 Client Profile + false + + + False + .NET Framework 3.5 SP1 + true + + + False + Windows Installer 3.1 + true + + + + + + + - - - - - - + --> + + + + + + \ No newline at end of file diff --git a/EPPlus/ExcelCellBase.cs b/EPPlus/ExcelCellBase.cs index da376491..9f764f1c 100644 --- a/EPPlus/ExcelCellBase.cs +++ b/EPPlus/ExcelCellBase.cs @@ -111,50 +111,21 @@ private static string Translate(string value, dlgTransl addressTranslator, int r { if (value == "") return ""; - bool isText = false; - string ret = ""; - string part = ""; - char prevTQ = (char)0; - for (int pos = 0; pos < value.Length; pos++) + + var lexer = new Lexer(SourceCodeTokenizer.Default, new SyntacticAnalyzer()); + var tokens = lexer.Tokenize(value); + foreach (var token in tokens) { - char c = value[pos]; - if (((c == '"' || c=='\'') && !isText) || (isText && c == prevTQ)) - { - if (isText == false && part != "" && prevTQ==c) - { - ret += addressTranslator(part, row, col, rowIncr, colIncr); - part = ""; - prevTQ = (char)0; - } - isText = !isText; - prevTQ = c; - ret += c; - } - else if (isText) + //Console.WriteLine($"{token.TokenType} : {token.Value}"); + if (token.TokenType == TokenType.ExcelAddress || token.TokenType.Equals(TokenType.NameValue)) { - ret += c; + var part = addressTranslator(token.Value, row, col, rowIncr, colIncr); + //Console.Write($"==> " + part); + token.Value = part; } - else - { - if ((c == '-' || c == '+' || c == '*' || c == '/' || - c == '=' || c == '^' || c == ',' || c == ':' || - c == '<' || c == '>' || c == '(' || c == ')' || c == '!' || - c == ' ' || c == '&' || c == '%') && - (pos == 0 || value[pos - 1] != '[')) //Last part to allow for R1C1 style [-x] - { - ret += addressTranslator(part, row, col, rowIncr, colIncr) + c; - part = ""; - } - else - { - part += c; - } - } - } - if (part != "") - { - ret += addressTranslator(part, row, col, rowIncr, colIncr); + } + var ret = string.Join("", tokens.Select(x => x.Value).ToArray()); return ret; } /// @@ -167,42 +138,54 @@ private static string Translate(string value, dlgTransl addressTranslator, int r /// /// private static string ToR1C1(string part, int row, int col, int rowIncr, int colIncr) + { + int shInd = part.IndexOf('!'); + string sh = ""; + if (shInd > 0) + { + sh = part.Substring(0, shInd + 1); + part = part.Substring(shInd + 1); + } + int delim = part.IndexOf(':'); + if (delim > 0) + { + string p1 = ToR1C1_1(part.Substring(0, delim), row, col, rowIncr, colIncr); + string p2 = ToR1C1_1(part.Substring(delim + 1), row, col, rowIncr, colIncr); + if (p1.Equals(p2)) + return p1; + return sh + p1 + ":" + p2; + } + + else + return sh + ToR1C1_1(part, row, col, rowIncr, colIncr); + } + private static string ToR1C1_1(string part, int row, int col, int rowIncr, int colIncr) { int addrRow, addrCol; - string Ret = "R"; - if (GetRowCol(part, out addrRow, out addrCol, false)) + bool fixRow, fixCol; + StringBuilder sb = new StringBuilder(); + if (GetRowCol(part, out addrRow, out addrCol, false, out fixRow, out fixCol)) { - if (addrRow == 0 || addrCol == 0) + if (addrRow == 0 && addrCol == 0) { return part; } - if (part.IndexOf('$', 1) > 0) - { - Ret += addrRow.ToString(); - } - else if (addrRow - row != 0) - { - Ret += string.Format("[{0}]", addrRow - row); - } - - if (Utils.ConvertUtil._invariantCompareInfo.IsPrefix(part, "$")) - { - return Ret + "C" + addrCol; - } - else if (addrCol - col != 0) + if (addrRow > 0) { - return Ret + "C" + string.Format("[{0}]", addrCol - col); + sb.Append(fixRow ? $"R{addrRow}" : (addrRow == row ? "R" : $"R[{addrRow - row}]")); } - else + if (addrCol > 0) { - return Ret + "C"; + sb.Append(fixCol ? $"C{addrCol}" : (addrCol == col ? "C" : $"C[{addrCol - col}]")); } + return sb.ToString(); } else { return part; } } + /// /// Translates to absolute address /// @@ -214,34 +197,98 @@ private static string ToR1C1(string part, int row, int col, int rowIncr, int col /// private static string ToAbs(string part, int row, int col, int rowIncr, int colIncr) { - string check = Utils.ConvertUtil._invariantTextInfo.ToUpper(part); + int shInd = part.IndexOf('!'); + string sh = ""; + if (shInd > 0) + { + sh = part.Substring(0, shInd + 1); + part = part.Substring(shInd + 1); + } + int delim = part.IndexOf(':'); + if (delim > 0) + { + string p1 = ToAbs_1(part.Substring(0, delim), row, col, rowIncr, colIncr); + string p2 = ToAbs_1(part.Substring(delim + 1), row, col, rowIncr, colIncr); + if (p1.Equals(p2)) + return p1; + return sh + p1 + ":" + p2; + } + else + return sh + ToAbs_1(part, row, col, rowIncr, colIncr); + } + private static string ToAbs_1(string part, int row, int col, int rowIncr, int colIncr) + { + string check = Utils.ConvertUtil._invariantTextInfo.ToUpper(part); + // Bug int rStart = check.IndexOf("R"); - if (rStart != 0) + int cStart = check.IndexOf("C"); + //if (rStart != 0) + // return part; + if (rStart != 0 && cStart != 0) return part; - if (part.Length == 1) //R + if (part.Length == 1) //R or C { - return GetAddress(row, col); + if (rStart == 0) + { + //return GetAddress(row); + return $"{row}:{row}"; + } + else + { + var cLetter = GetColumnLetter(col); + return $"{cLetter}:{cLetter}"; + } + } - int cStart = check.IndexOf("C"); bool absoluteRow, absoluteCol; if (cStart == -1) { - int RNum = GetRC(part, row, out absoluteRow); + int RNum = GetRC(part.Substring(1), row, out absoluteRow); if (RNum > int.MinValue) { - return GetAddress(RNum, absoluteRow, col, false); + return GetAddressRow(RNum, absoluteRow); } else { return part; } } - else + if (rStart == -1) { - int RNum = GetRC(part.Substring(1, cStart - 1), row, out absoluteRow); - int CNum = GetRC(part.Substring(cStart + 1, part.Length - cStart - 1), col, out absoluteCol); + int CNum = GetRC(part.Substring(1), col, out absoluteCol); + if (CNum > int.MinValue) + { + return GetAddressCol(CNum, absoluteCol); + } + else + { + return part; + } + } + { + int RNum, CNum; + if (1 == cStart) + { + RNum = row; + absoluteRow = false; + } + else + { + RNum = GetRC(part.Substring(1, cStart - 1), row, out absoluteRow); + } + if ((part.Length - 1) == cStart) + { + CNum = col; + absoluteCol = false; + } + else + { + CNum = GetRC(part.Substring(cStart + 1, part.Length - cStart - 1), col, out absoluteCol); + } + + if (RNum > int.MinValue && CNum > int.MinValue) { return GetAddress(RNum, absoluteRow, CNum, absoluteCol); @@ -466,7 +513,10 @@ internal static bool GetRowColFromAddress(string CellAddress, out int row, out i { return GetRowCol(CellAddress, out row, out col, true, out fixedRow, out fixedCol); } - + internal static bool IsAlpha(char c) + { + return c >= 'A' && c <= 'Z'; + } /// /// Get the row/column for a Cell-address /// @@ -520,7 +570,7 @@ internal static bool GetRowCol(string address, out int row, out int col, bool th } else if (c == '$') { - if (i == colStartIx) + if (IsAlpha(address[i+1])) { colStartIx++; fixedCol = true; @@ -559,6 +609,19 @@ private static int GetColumn(string sCol) return col; } #region GetAddress + public static string GetAddressRow(int Row, bool Absolute = false) + { + if (Absolute) + return $"${Row}:${Row}"; + return $"{Row}:{Row}"; + } + public static string GetAddressCol(int Col, bool Absolute = false) + { + var colLetter = GetColumnLetter(Col); + if (Absolute) + return $"${colLetter}:${colLetter}"; + return $"{colLetter}:{colLetter}"; + } /// /// Returns the AlphaNumeric representation that Excel expects for a Cell Address /// diff --git a/EPPlusTest/EPPlusTest.csproj b/EPPlusTest/EPPlusTest.csproj index 95efa9bb..b1fac8e6 100644 --- a/EPPlusTest/EPPlusTest.csproj +++ b/EPPlusTest/EPPlusTest.csproj @@ -114,6 +114,7 @@ + diff --git a/EPPlusTest/FormulaParsing/FormulaR1C1Test.cs b/EPPlusTest/FormulaParsing/FormulaR1C1Test.cs new file mode 100644 index 00000000..39f9568b --- /dev/null +++ b/EPPlusTest/FormulaParsing/FormulaR1C1Test.cs @@ -0,0 +1,154 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using OfficeOpenXml; + +namespace EPPlusTest.FormulaParsing +{ + [TestClass] + public class FormulaR1C1Tests + { + private ExcelPackage _package; + private ExcelWorksheet _sheet; + private ExcelWorksheet _sheet2; + [TestInitialize] + public void Initialize() + { + _package = new ExcelPackage(); + var s1 = _package.Workbook.Worksheets.Add("test"); + s1.Cells["A1"].Value = 1; + s1.Cells["A2"].Value = 2; + s1.Cells["A3"].Value = 3; + s1.Cells["A4"].Value = 4; + + s1.Cells["B1"].Value = 5; + s1.Cells["B2"].Value = 6; + s1.Cells["B3"].Value = 7; + s1.Cells["B4"].Value = 8; + + s1.Cells["C1"].Value = 5; + s1.Cells["C2"].Value = 6; + s1.Cells["C3"].Value = 7; + s1.Cells["C4"].Value = 8; + + _sheet = s1; + _sheet2 = _package.Workbook.Worksheets.Add("test2",s1); + } + + [TestCleanup] + public void Cleanup() + { + _package.Dispose(); + } + + [TestMethod] + public void RC2() + { + string fR1C1 = "RC2"; + _sheet.Cells[5, 1].FormulaR1C1 = fR1C1; + string f = _sheet.Cells[5, 1].Formula; + _sheet.Cells[5, 1].Formula = f; + Assert.AreEqual(fR1C1, _sheet.Cells[5,1].FormulaR1C1); + } + [TestMethod] + public void C() + { + string fR1C1 = "SUMIFS(C,C2,RC1)"; + _sheet.Cells[5, 3].FormulaR1C1 = fR1C1; + string f = _sheet.Cells[5, 3].Formula; + _sheet.Cells[5, 3].Formula = f; + Assert.AreEqual(fR1C1, _sheet.Cells[5, 3].FormulaR1C1); + } + [TestMethod] + public void C2Abs() + { + string fR1C1 = "SUM(C2)"; + _sheet.Cells[5, 3].FormulaR1C1 = fR1C1; + string f = _sheet.Cells[5, 3].Formula; + Assert.AreEqual("SUM($B:$B)", f); + } + [TestMethod] + public void C2AbsWithSheet() + { + string fR1C1 = "SUM(A!C2)"; + _sheet.Cells[5, 3].FormulaR1C1 = fR1C1; + string f = _sheet.Cells[5, 3].Formula; + Assert.AreEqual("SUM(A!$B:$B)", f); + } + [TestMethod] + public void C2() + { + string fR1C1 = "SUM(C2)"; + _sheet.Cells[5, 3].FormulaR1C1 = fR1C1; + string f = _sheet.Cells[5, 3].Formula; + _sheet.Cells[5, 3].Formula = f; + Assert.AreEqual(fR1C1, _sheet.Cells[5, 3].FormulaR1C1); + } + [TestMethod] + public void R2Abs() + { + string fR1C1 = "SUM(R2)"; + _sheet.Cells[5, 3].FormulaR1C1 = fR1C1; + string f = _sheet.Cells[5, 3].Formula; + Assert.AreEqual("SUM($2:$2)",f); + + fR1C1 = "SUM(TEST2!R2)"; + _sheet.Cells[5, 3].FormulaR1C1 = fR1C1; + f = _sheet.Cells[5, 3].Formula; + Assert.AreEqual("SUM(TEST2!$2:$2)", f); + + } + [TestMethod] + public void R2() + { + string fR1C1 = "SUM(R2)"; + _sheet.Cells[5, 3].FormulaR1C1 = fR1C1; + string f = _sheet.Cells[5, 3].Formula; + _sheet.Cells[5, 3].Formula = f; + Assert.AreEqual(fR1C1, _sheet.Cells[5, 3].FormulaR1C1); + } + [TestMethod] + public void RCRelativeToAB() + { + string fR1C1 = "SUMIFS(C,C2,RC1)"; + _sheet.Cells[5, 3].FormulaR1C1 = fR1C1; + string f = _sheet.Cells[5, 3].Formula; + Assert.AreEqual("SUMIFS(C:C,$B:$B,$A5)", f); + } + [TestMethod] + public void RCRelativeToABToR1C1() + { + string fR1C1 = "SUMIFS(C,C2,RC1)"; + _sheet.Cells[5, 3].FormulaR1C1 = fR1C1; + string f = _sheet.Cells[5, 3].Formula; + _sheet.Cells[5, 3].Formula = f; + Assert.AreEqual(fR1C1, _sheet.Cells[5, 3].FormulaR1C1); + } + [TestMethod] + public void RCRelativeToABToR1C1_2() + { + string fR1C1 = "SUM(RC9:RC[-1])"; + _sheet.Cells[5, 13].FormulaR1C1 = fR1C1; + string f = _sheet.Cells[5, 13].Formula; + Assert.AreEqual("SUM($I5:L5)", f); + _sheet.Cells[5, 13].Formula = f; + Assert.AreEqual(fR1C1, _sheet.Cells[5, 13].FormulaR1C1); + + //"RC{colShort} - SUM(RC21:RC12)"; + } + [TestMethod] + public void RCFixToABToR1C1_2() + { + string fR1C1 = "RC28-SUM(RC12:RC21)"; + _sheet.Cells[6, 13].FormulaR1C1 = fR1C1; + string f = _sheet.Cells[6, 13].Formula; + Assert.AreEqual("$AB6-SUM($L6:$U6)", f); + _sheet.Cells[6, 13].Formula = f; + Assert.AreEqual(fR1C1, _sheet.Cells[6, 13].FormulaR1C1); + + } + } +}