diff --git a/EPPlus/ExcelAddress.cs b/EPPlus/ExcelAddress.cs index 651dfbe1b..01cd97d75 100644 --- a/EPPlus/ExcelAddress.cs +++ b/EPPlus/ExcelAddress.cs @@ -34,6 +34,7 @@ using System.Globalization; using System.Text; using System.Text.RegularExpressions; +using OfficeOpenXml.FormulaParsing.Utilities; namespace OfficeOpenXml { @@ -395,7 +396,7 @@ private string GetAddress() if (!string.IsNullOrEmpty(_ws)) { - adr += string.Format("'{0}'!", _ws); + adr += GetWorksheetNameEscaped() + "!"; } if (IsName) adr += GetAddress(_fromRow, _fromCol, _toRow, _toCol); @@ -403,6 +404,11 @@ private string GetAddress() adr += GetAddress(_fromRow, _fromCol, _toRow, _toCol, _fromRowFixed, _fromColFixed, _toRowFixed, _toColFixed); return adr; } + + public string GetWorksheetNameEscaped() + { + return Regex.IsMatch(_ws, RegexConstants.SheetNameSingleQuotes) ? $"'{_ws.Replace("'", "''")}'" : _ws; + } #endregion protected ExcelCellAddress _start = null; /// @@ -889,8 +895,9 @@ internal enum AddressType R1C1 } - internal static AddressType IsValid(string Address, bool r1c1=false) + internal static AddressType IsValid(string Address, out string normalizedAddress, bool r1c1 = false) { + normalizedAddress = Address; double d; if (Address == "#REF!") { @@ -918,15 +925,25 @@ internal static AddressType IsValid(string Address, bool r1c1=false) { if (intAddress.Contains("[")) //Table reference { - return string.IsNullOrEmpty(wb) ? AddressType.InternalAddress : AddressType.ExternalAddress; - } - else if (intAddress.Contains(",")) - { - intAddress = intAddress.Substring(0, intAddress.IndexOf(',')); + if (!string.IsNullOrEmpty(wb)) + { + return AddressType.ExternalAddress; + } + + normalizedAddress = NormalizeAddress(Address, ws, intAddress); + return AddressType.InternalAddress; } - if (IsAddress(intAddress)) + + string addressToTest = intAddress.Contains(",") ? intAddress.Substring(0, intAddress.IndexOf(',')) : intAddress; + if (IsAddress(addressToTest)) { - return string.IsNullOrEmpty(wb) ? AddressType.InternalAddress : AddressType.ExternalAddress; + if (!string.IsNullOrEmpty(wb)) + { + return AddressType.ExternalAddress; + } + + normalizedAddress = NormalizeAddress(Address, ws, intAddress); + return AddressType.InternalAddress; } else { @@ -941,6 +958,11 @@ internal static AddressType IsValid(string Address, bool r1c1=false) } } + private static string NormalizeAddress(string fullAddress, string ws, string intAddress) + { + return fullAddress.StartsWith("!") ? fullAddress : string.IsNullOrEmpty(ws) ? intAddress : $"{ws}!{intAddress.ToUpperInvariant()}"; + } + private static bool IsR1C1(string address) { if (address.StartsWith("!")) diff --git a/EPPlus/ExcelCellBase.cs b/EPPlus/ExcelCellBase.cs index 86a990496..81fed69ed 100644 --- a/EPPlus/ExcelCellBase.cs +++ b/EPPlus/ExcelCellBase.cs @@ -150,7 +150,10 @@ private static string ToR1C1(string part, int row, int col) string p1 = ToR1C1_1(part.Substring(0, delim), row, col); string p2 = ToR1C1_1(part.Substring(delim + 1), row, col); if (p1.Equals(p2)) - return p1; + { + return sh + p1; + } + return sh + p1 + ":" + p2; } @@ -209,7 +212,10 @@ private static string ToAbs(string part, int row, int col) string p1 = ToAbs_1(part.Substring(0, delim), row, col, false); string p2 = ToAbs_1(part.Substring(delim + 1), row, col, false); if (p1.Equals(p2)) - return p1; + { + return sh + p1; + } + return sh + p1 + ":" + p2; } else diff --git a/EPPlus/ExcelRangeBase.cs b/EPPlus/ExcelRangeBase.cs index 0519de605..ffabb2748 100644 --- a/EPPlus/ExcelRangeBase.cs +++ b/EPPlus/ExcelRangeBase.cs @@ -899,44 +899,31 @@ public void AutoFitColumns(double MinimumWidth, double MaximumWidth) if (fnt.Italic) fs |= FontStyle.Italic; if (fnt.Strike) fs |= FontStyle.Strikeout; f = new Font(fnt.Name, fnt.Size, fs); - //f = new wm.Typeface(new System.Windows.Media.FontFamily(fnt.Name), fnt.Italic ? System.Windows.FontStyles.Italic : System.Windows.FontStyles.Normal, fnt.Bold ? System.Windows.FontWeights.Bold : System.Windows.FontWeights.Normal, System.Windows.FontStretches.Normal); fontCache.Add(fntID, f); } var ind = styles.CellXfs[cell.StyleID].Indent; var textForWidth = cell.TextForWidth; var t = textForWidth + (ind > 0 && !string.IsNullOrEmpty(textForWidth) ? new string('_', ind) : ""); + if (t.Length > 32000) t = t.Substring(0, 32000); //Issue var size = g.MeasureString(t, f, 10000, StringFormat.GenericDefault); - //var ft = new wm.FormattedText(t, CultureInfo.CurrentCulture, w.FlowDirection.LeftToRight, - // f, - // styles.Fonts[fntID].Size, System.Windows.Media.Brushes.Black); - //var wd = ft.WidthIncludingTrailingWhitespace; - - - //var wi = ft.WidthIncludingTrailingWhitespace / (72 / 96D); //Typounit=72 DPI, WPF=96DPI - //var he = ft.Height / (72 / 96D); - double width; double r = styles.CellXfs[cell.StyleID].TextRotation; if (r <= 0) { - //width = (wi + 15) / normalSize; width = (size.Width + 5) / normalSize; } else { r = (r <= 90 ? r : r - 90); width = (((size.Width - size.Height) * Math.Abs(System.Math.Cos(System.Math.PI * r / 180.0)) + size.Height) + 5) / normalSize; - //width = (((wi - he) * Math.Abs(Math.Cos(Math.PI * r / 180.0)) + he) + 15) / normalSize; - //width= (((size.Width-size.Height) * Math.Abs(Math.Cos(Math.PI * r / 180.0)) + size.Height) +15) / normalSize; } foreach (var a in afAddr) { if (a.Collide(cell) != eAddressCollition.No) { - //width += 2.8; width += 2.25; break; } @@ -1110,6 +1097,10 @@ private static string GetDateText(DateTime d, string format, ExcelNumberFormatXm { return d.ToLongTimeString(); } + else if (nf.SpecialDateFormat == ExcelNumberFormatXml.ExcelFormatTranslator.eSystemDateFormat.SystemShortDate) + { + return d.ToShortDateString(); + } if (format == "d" || format == "D") { return d.Day.ToString(); diff --git a/EPPlus/ExcelWorkbook.cs b/EPPlus/ExcelWorkbook.cs index 0887bd359..51484d531 100644 --- a/EPPlus/ExcelWorkbook.cs +++ b/EPPlus/ExcelWorkbook.cs @@ -188,7 +188,7 @@ internal void GetDefinedNames() nameWorksheet=Worksheets[localSheetID + _package._worksheetAdd]; } - var addressType = ExcelAddressBase.IsValid(fullAddress); + var addressType = ExcelAddressBase.IsValid(fullAddress, out _); ExcelRangeBase range; ExcelNamedRange namedRange; diff --git a/EPPlus/FormulaParsing/ExcelAddressCache.cs b/EPPlus/FormulaParsing/ExcelAddressCache.cs index 55fcdd69f..10ef872ee 100644 --- a/EPPlus/FormulaParsing/ExcelAddressCache.cs +++ b/EPPlus/FormulaParsing/ExcelAddressCache.cs @@ -97,4 +97,4 @@ public void Clear() } } -} +} \ No newline at end of file diff --git a/EPPlus/FormulaParsing/LexicalAnalysis/TokenFactory.cs b/EPPlus/FormulaParsing/LexicalAnalysis/TokenFactory.cs index 253c42922..9f0715c40 100644 --- a/EPPlus/FormulaParsing/LexicalAnalysis/TokenFactory.cs +++ b/EPPlus/FormulaParsing/LexicalAnalysis/TokenFactory.cs @@ -152,10 +152,10 @@ public Token Create(IEnumerable tokens, string token, string worksheet) { return new Token(token, TokenType.Enumerable); } - var at = OfficeOpenXml.ExcelAddressBase.IsValid(token, _r1c1); + var at = OfficeOpenXml.ExcelAddressBase.IsValid(token, out string normalizedToken, _r1c1); if (at==ExcelAddressBase.AddressType.InternalAddress) { - return new Token(token.ToUpper(CultureInfo.InvariantCulture), TokenType.ExcelAddress); + return new Token(normalizedToken, TokenType.ExcelAddress); } else if (at == ExcelAddressBase.AddressType.R1C1) { diff --git a/EPPlus/FormulaParsing/Utilities/RegexConstants.cs b/EPPlus/FormulaParsing/Utilities/RegexConstants.cs index 629d78415..2c78796c9 100644 --- a/EPPlus/FormulaParsing/Utilities/RegexConstants.cs +++ b/EPPlus/FormulaParsing/Utilities/RegexConstants.cs @@ -41,6 +41,7 @@ public static class RegexConstants //Changed JK 26/2-2013 public const string ExcelAddress = @"^(('[^/\\?*\[\]]{1,31}'|[A-Za-z_]{1,31})!)?[\$]{0,1}([A-Z]|[A-Z]{1,3}[\$]{0,1}[1-9]{1}[0-9]{0,7})(\:({0,1}[A-Z]|[A-Z]{1,3}[\$]{0,1}[1-9]{1}[0-9]{0,7})){0,1}$"; //public const string ExcelAddress = @"^([\$]{0,1}([A-Z]{1,3}[\$]{0,1}[0-9]{1,7})(\:([\$]{0,1}[A-Z]{1,3}[\$]{0,1}[0-9]{1,7}){0,1})|([\$]{0,1}[A-Z]{1,3}\:[\$]{0,1}[A-Z]{1,3})|([\$]{0,1}[0-9]{1,7}\:[\$]{0,1}[0-9]{1,7}))$"; + public const string SheetNameSingleQuotes = @"^[A-Z]{1,3}[1-9]{1}[0-9]{0,7}$|^R-?\d*C-?\d*$|[\s()'$,;\-{}!]"; public const string Boolean = @"^(true|false)$"; public const string Decimal = @"^[0-9]+\.[0-9]+$"; public const string Integer = @"^[0-9]+$"; diff --git a/EPPlus/Style/XmlAccess/ExcelNumberFormatXml.cs b/EPPlus/Style/XmlAccess/ExcelNumberFormatXml.cs index 5c114bc86..a14327a11 100644 --- a/EPPlus/Style/XmlAccess/ExcelNumberFormatXml.cs +++ b/EPPlus/Style/XmlAccess/ExcelNumberFormatXml.cs @@ -203,14 +203,16 @@ internal enum eSystemDateFormat None, SystemLongDate, SystemLongTime, - Conditional + Conditional, + SystemShortDate, } internal ExcelFormatTranslator(string format, int numFmtID) { if (numFmtID == 14) { - NetFormat = NetFormatForWidth = "d"; + NetFormat = NetFormatForWidth = ""; NetTextFormat = NetTextFormatForWidth = ""; + SpecialDateFormat = eSystemDateFormat.SystemShortDate; DataType = eFormatType.DateTime; } else if (format.Equals("general",StringComparison.OrdinalIgnoreCase)) diff --git a/EPPlusTest/ExcelCellBaseTests.cs b/EPPlusTest/ExcelCellBaseTests.cs index 130e24246..fddeea888 100644 --- a/EPPlusTest/ExcelCellBaseTests.cs +++ b/EPPlusTest/ExcelCellBaseTests.cs @@ -55,8 +55,8 @@ public void UpdateFormulaReferencesReferencingADifferentSheetIsNotUpdated() [TestMethod] public void UpdateFormulaSheetReferences() { - var result = ExcelCellBase.UpdateFormulaSheetReferences("5+'OldSheet'!$G3+'Some Other Sheet'!C3+SUM(1,2,3)", "OldSheet", "NewSheet"); - Assert.AreEqual("5+'NewSheet'!$G3+'Some Other Sheet'!C3+SUM(1,2,3)", result); + var result = ExcelCellBase.UpdateFormulaSheetReferences("5+OldSheet!$G3+'Some Other Sheet'!C3+SUM(1,2,3)", "OldSheet", "NewSheet"); + Assert.AreEqual("5+NewSheet!$G3+'Some Other Sheet'!C3+SUM(1,2,3)", result); } [TestMethod] @@ -86,6 +86,34 @@ public void UpdateFormulaSheetReferencesEmptyNewSheetThrowsException() { ExcelCellBase.UpdateFormulaSheetReferences("formula", "sheet1", string.Empty); } + + [TestMethod] + public void UpdateFormulaSheetReferencesToSheetNameWithSpace() + { + var result = ExcelCellBase.UpdateFormulaSheetReferences("Sheet1!A1", "Sheet1", "Sheet 1"); + Assert.AreEqual("'Sheet 1'!A1", result); + } + + [TestMethod] + public void UpdateFormulaSheetReferencesToSheetNameWithSpecialChars() + { + var result = ExcelCellBase.UpdateFormulaSheetReferences("Sheet1!A1", "Sheet1", "(Sheet)'1!"); + Assert.AreEqual("'(Sheet)''1!'!A1", result); + } + + [TestMethod] + public void UpdateFormulaSheetReferencesToSheetNameWithA1Notation() + { + var result = ExcelCellBase.UpdateFormulaSheetReferences("Sheet1!A1", "Sheet1", "B1048576"); + Assert.AreEqual("'B1048576'!A1", result); + } + + [TestMethod] + public void UpdateFormulaSheetReferencesToSheetNameWithR1C1Notation() + { + var result = ExcelCellBase.UpdateFormulaSheetReferences("Sheet1!A1", "Sheet1", "RC"); + Assert.AreEqual("'RC'!A1", result); + } #endregion } } diff --git a/EPPlusTest/FormulaParsing/FormulaR1C1Test.cs b/EPPlusTest/FormulaParsing/FormulaR1C1Test.cs index d2d2dbdf2..16ec2b14f 100644 --- a/EPPlusTest/FormulaParsing/FormulaR1C1Test.cs +++ b/EPPlusTest/FormulaParsing/FormulaR1C1Test.cs @@ -234,5 +234,46 @@ public void OutOfRangeCol() Assert.AreEqual("A3", _sheet.Cells["B3"].Formula); } + + [TestMethod] + public void SheetNameTest() + { + const string formulaR1C1 = "SUM(Test2!R[-4]C[-4]:R[-1]C[-1])"; + _sheet.Cells[5, 5].FormulaR1C1 = formulaR1C1; + string formula = _sheet.Cells[5, 5].Formula; + Assert.AreEqual("SUM(Test2!A1:D4)", formula); + } + + [TestMethod] + public void TranslateToR1C1Test1() + { + const string formula = "SUM(Sheet1!A:A)"; + var formulaR1C1 = ExcelCellBase.TranslateToR1C1(formula, 1,2); + Assert.AreEqual("SUM(Sheet1!C[-1])",formulaR1C1); + } + + [TestMethod] + public void TranslateToR1C1Test2() + { + const string formula = "SUM(Sheet1!1:1)"; + var formulaR1C1 = ExcelCellBase.TranslateToR1C1(formula, 2,1); + Assert.AreEqual("SUM(Sheet1!R[-1])",formulaR1C1); + } + + [TestMethod] + public void TranslateFromR1C1Test1() + { + const string formulaR1C1 = "SUM(Sheet1!C[-1])"; + var formula = ExcelCellBase.TranslateFromR1C1(formulaR1C1, 1,2); + Assert.AreEqual("SUM(Sheet1!A:A)", formula); + } + + [TestMethod] + public void TranslateFromR1C1Test2() + { + const string formulaR1C1 = "SUM(Sheet1!R[-1])"; + var formula = ExcelCellBase.TranslateFromR1C1(formulaR1C1, 2,1); + Assert.AreEqual("SUM(Sheet1!1:1)", formula); + } } } diff --git a/EPPlusTest/FormulaParsing/LexicalAnalysis/TokenFactoryTests.cs b/EPPlusTest/FormulaParsing/LexicalAnalysis/TokenFactoryTests.cs index aa5578ea8..a6a764c91 100644 --- a/EPPlusTest/FormulaParsing/LexicalAnalysis/TokenFactoryTests.cs +++ b/EPPlusTest/FormulaParsing/LexicalAnalysis/TokenFactoryTests.cs @@ -154,7 +154,7 @@ public void CreateShouldCreateExcelRangeOnOtherSheetAsExcelAddressToken() var input = "ws!A1:B15"; var token = _tokenFactory.Create(Enumerable.Empty(), input); Assert.AreEqual(TokenType.ExcelAddress, token.TokenType); - Assert.AreEqual("WS!A1:B15", token.Value); + Assert.AreEqual("ws!A1:B15", token.Value); } [TestMethod] diff --git a/EPPlusTest/Issues.cs b/EPPlusTest/Issues.cs index 028cbf23c..5c38c5e73 100644 --- a/EPPlusTest/Issues.cs +++ b/EPPlusTest/Issues.cs @@ -2387,5 +2387,25 @@ public void Issue387() workbook.Names.Add("Q0", cells); } } + [TestMethod] + public void Issue333() + { + using (var package = new ExcelPackage()) + { + var ws = package.Workbook.Worksheets.Add("TextBug"); + ws.Cells["A1"].Value = new DateTime(2019, 3, 7); + ws.Cells["A1"].Style.Numberformat.Format = "mm-dd-yy"; + + Assert.AreEqual("2019-03-07", ws.Cells["A1"].Text); + } + } + [TestMethod] + public void Issue445() + { + ExcelPackage p = new ExcelPackage(); + ExcelWorksheet ws = p.Workbook.Worksheets.Add("AutoFit"); //<-- This line takes forever. The process hangs. + ws.Cells[1, 1].Value = new string('a', 50000); + ws.Cells[1, 1].AutoFitColumns(); + } } }