Skip to content
This repository has been archived by the owner on Mar 9, 2020. It is now read-only.

Fix sheet reference in ToR1C1 formula translation #480

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
40 changes: 31 additions & 9 deletions EPPlus/ExcelAddress.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
using System.Globalization;
using System.Text;
using System.Text.RegularExpressions;
using OfficeOpenXml.FormulaParsing.Utilities;

namespace OfficeOpenXml
{
Expand Down Expand Up @@ -395,14 +396,19 @@ private string GetAddress()

if (!string.IsNullOrEmpty(_ws))
{
adr += string.Format("'{0}'!", _ws);
adr += GetWorksheetNameEscaped() + "!";
}
if (IsName)
adr += GetAddress(_fromRow, _fromCol, _toRow, _toCol);
else
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;
/// <summary>
Expand Down Expand Up @@ -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!")
{
Expand Down Expand Up @@ -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
{
Expand All @@ -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("!"))
Expand Down
10 changes: 8 additions & 2 deletions EPPlus/ExcelCellBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down Expand Up @@ -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
Expand Down
19 changes: 5 additions & 14 deletions EPPlus/ExcelRangeBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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();
Expand Down
2 changes: 1 addition & 1 deletion EPPlus/ExcelWorkbook.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
2 changes: 1 addition & 1 deletion EPPlus/FormulaParsing/ExcelAddressCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -97,4 +97,4 @@ public void Clear()
}

}
}
}
4 changes: 2 additions & 2 deletions EPPlus/FormulaParsing/LexicalAnalysis/TokenFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -152,10 +152,10 @@ public Token Create(IEnumerable<Token> 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)
{
Expand Down
1 change: 1 addition & 0 deletions EPPlus/FormulaParsing/Utilities/RegexConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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]+$";
Expand Down
6 changes: 4 additions & 2 deletions EPPlus/Style/XmlAccess/ExcelNumberFormatXml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
32 changes: 30 additions & 2 deletions EPPlusTest/ExcelCellBaseTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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
}
}
41 changes: 41 additions & 0 deletions EPPlusTest/FormulaParsing/FormulaR1C1Test.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ public void CreateShouldCreateExcelRangeOnOtherSheetAsExcelAddressToken()
var input = "ws!A1:B15";
var token = _tokenFactory.Create(Enumerable.Empty<Token>(), input);
Assert.AreEqual(TokenType.ExcelAddress, token.TokenType);
Assert.AreEqual("WS!A1:B15", token.Value);
Assert.AreEqual("ws!A1:B15", token.Value);
}

[TestMethod]
Expand Down
20 changes: 20 additions & 0 deletions EPPlusTest/Issues.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}
}