Skip to content

Feature/name formula #1999

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Open
wants to merge 4 commits into
base: develop8_1
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/EPPlus/Core/Worksheet/WorksheetCopyHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -924,7 +924,7 @@ private static bool HasExternalReference(string formula)
{
if(formula!=null && formula.IndexOf('[') >= 0)
{
var t=SourceCodeTokenizer.Default.Tokenize(formula);
var t=SourceCodeTokenizer.Default.Tokenize(formula);
return t.Any(x => x.TokenType == TokenType.ExternalReference);
}
return false;
Expand Down
4 changes: 2 additions & 2 deletions src/EPPlus/DataValidation/ExcelDatavalidationAddress.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ internal ExcelDatavalidationAddress(string address, ExcelDataValidation val) : b
/// <summary>
/// Called before the address changes
/// </summary>
internal protected override void BeforeChangeAddress()
internal protected override void BeforeChangeAddress(string value)
{
addressBeforeChange = _val.Address;
_val._ws.DataValidations.ClearRangeDictionary(_val.Address);
Expand All @@ -34,7 +34,7 @@ internal protected override void BeforeChangeAddress()
/// <summary>
/// Called when the address changes
/// </summary>
internal protected override void ChangeAddress()
internal protected override void ChangeAddress(string value)
{
_val._ws.DataValidations.dvQuadTree.UpdateAddress(addressBeforeChange, _val.Address, _val);
_val._ws.DataValidations.AddToRangeDictionary(_val);
Expand Down
3 changes: 1 addition & 2 deletions src/EPPlus/EPPlus.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -702,8 +702,7 @@
<PackageReference Include="System.Security.Cryptography.Xml" />
</ItemGroup>

<ItemGroup
Condition="'$(TargetFramework)' != 'net35' and '$(TargetFramework)' != 'net462' and '$(TargetFramework)' != 'net9.0'">
<ItemGroup Condition="'$(TargetFramework)' != 'net35' and '$(TargetFramework)' != 'net462' and '$(TargetFramework)' != 'net9.0'">
<PackageReference Include="Microsoft.Extensions.Configuration.Json" />
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" />
<PackageReference Include="System.Security.Cryptography.Pkcs" />
Expand Down
6 changes: 3 additions & 3 deletions src/EPPlus/ExcelAddress.cs
Original file line number Diff line number Diff line change
Expand Up @@ -104,10 +104,10 @@ public ExcelAddress(string Address, ExcelPackage package, ExcelAddressBase refer
}
set
{
BeforeChangeAddress();
BeforeChangeAddress(value);
SetAddress(value, null, null);
ChangeAddress();
ChangeAddress(value);
}
}
}
}
}
6 changes: 2 additions & 4 deletions src/EPPlus/ExcelAddressBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -465,17 +465,15 @@ internal ExcelAddressBase ToInternalAddress()
/// <summary>
/// Method for actions that must be taken before address is changed
/// </summary>
internal protected virtual void BeforeChangeAddress()
internal protected virtual void BeforeChangeAddress(string value)
{
}
/// <summary>
/// Called when the address changes
/// </summary>
internal protected virtual void ChangeAddress()
internal protected virtual void ChangeAddress(string value)
{
}


private void SetWbWs(string address)
{
int pos;
Expand Down
2 changes: 1 addition & 1 deletion src/EPPlus/ExcelFormulaAddress.cs
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ private void GetFixed(string address, out bool rowFixed, out bool colFixed)
set
{
SetAddress(value, null, null);
ChangeAddress();
ChangeAddress(value);
SetFixed();
}
}
Expand Down
198 changes: 190 additions & 8 deletions src/EPPlus/ExcelNamedRange.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,13 @@ Date Author Change
01/27/2020 EPPlus Software AB Initial release EPPlus 5
*************************************************************************************************/
using OfficeOpenXml.Core;
using OfficeOpenXml.FormulaParsing.Excel.Functions.Information;
using OfficeOpenXml.FormulaParsing.Excel.Functions.MathFunctions;
using OfficeOpenXml.FormulaParsing.LexicalAnalysis;
using OfficeOpenXml.FormulaParsing.Ranges;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using OfficeOpenXml.Utils;
using OfficeOpenXml.FormulaParsing;
using OfficeOpenXml.Utils.Formula;

namespace OfficeOpenXml
{
Expand Down Expand Up @@ -142,11 +139,196 @@ internal object NameValue
set;
}
IList<Token> _tokens = null;
/// <summary>
/// Set to true to validate and update formulas with cell references.
/// </summary>
public static bool ValidateCellAddressInFormulas = true;
private string _nameFormula;
internal string NameFormula
{
get;
set;
}
get
{
return _nameFormula;
}
set
{
if (value == null)
{
_nameFormula = value;
return;
}
_nameFormula = ValidateCellAddressInFormulas ? FormulaUtils.AddWorksheetReferenceToFormula(value, _worksheet, AllowRelativeAddress) : value;
}
}
/// <inheritdoc/>
protected internal override void BeforeChangeAddress(string value)
{
if(!string.IsNullOrEmpty(value) && !value.Contains("!") && Worksheet == null)
{
throw new InvalidOperationException("Workbook name needs a worksheet in the address.");
}

}
/// <inheritdoc/>
protected internal override void ChangeAddress(string value)
{

}

/// <summary>
/// Set a range for this name. Will remove exsisting formula and value.
/// </summary>
/// <param name="range"></param>
/// <param name="allowRelativeAddress"></param>
public void SetRange(ExcelRangeBase range, bool allowRelativeAddress = false)
{
if (range.Worksheet != _worksheet)
{
throw new InvalidOperationException($"Cannot change range to another worksheet or set a range to a workbook name: {Name}. Either create a new name or move current name first.");
}
ResetObject();
NameFormula = null;
NameValue = null;

_workbook = range._workbook;
_worksheet = range._worksheet;
if (allowRelativeAddress)
{
Address = range.FullAddress;
}
else
{
Address = range.FullAddressAbsolute;
}
Value = range.Value;
}

/// <summary>
/// Set a formula for this name. Will remove exsisting range and value.
/// </summary>
/// <param name="formula">The formula for this name.</param>
public void SetFormula(string formula)
{
ResetObject();
NameFormula = formula;
this.Formula = NameFormula;
NameValue = null;
}

/// <summary>
/// Set a value for this name. Will remove exsisting range and formula.
/// </summary>
/// <param name="value"></param>
public void SetValue(object value)
{
ResetObject();
NameFormula = null;
NameValue = value;
Value = value;
}
/// <summary>
/// Move this defined name to a target worksheet.
/// </summary>
/// <param name="worksheet">Worksheet to move this name to.</param>
/// /// <param name="name">Optional new name for the defined name.</param>
/// <returns>This name.</returns>
/// <exception cref="InvalidOperationException">If this name does not contain a formula, value or range, this exception occurs.</exception>
public ExcelNamedRange Move(ExcelWorksheet worksheet, string name = null)
{
ExcelNamedRange enr = null;
name = name == null ? Name : name;
//Detect if formula, value or range
if (NameFormula != null)
enr = worksheet.Names.AddFormula(Name, NameFormula);
else if (NameValue != null)
enr = worksheet.Names.AddValue(Name, NameValue);
else if (LocalAddress != "#REF!")
enr = worksheet.Names.AddRange(Name, worksheet.Cells[Address]);
if (enr == null) throw new InvalidOperationException($"No value, formula or address has been set for this name: {Name}");
if(_worksheet != null)
_worksheet.Names.Remove(Name);
else if(_workbook != null)
_workbook.Names.Remove(Name);
else
throw new InvalidOperationException($"No workbook or worksheet has been set for this name: {Name}");
return enr;
}
/// <summary>
/// Move this defined name to target workbook.
/// </summary>
/// <param name="workbook">Workbook to move this name to.</param>
/// <param name="name">Optional new name for the defined name.</param>
/// <returns>This name.</returns>
/// <exception cref="InvalidOperationException">If this name does not contain a formula, value or range, this exception occurs.</exception>
public ExcelNamedRange Move(ExcelWorkbook workbook, string name = null)
{
ExcelNamedRange enr = null;
name = name == null ? Name : name;
//Detect if formula, value or range
if (NameFormula != null)
enr = workbook.Names.AddFormula(Name, NameFormula);
else if (NameValue != null)
enr = workbook.Names.AddValue(Name, NameValue);
else if (LocalAddress != "#REF!")
enr = workbook.Names.AddRange(Name, _worksheet.Cells[Address]);
if(enr == null) throw new InvalidOperationException($"No value, formula or address has been set for this name: {Name}");
if (_worksheet != null)
_worksheet.Names.Remove(Name);
else if (_workbook != null)
_workbook.Names.Remove(Name);
else
throw new InvalidOperationException($"No workbook or worksheet has been set for this name: {Name}");
return enr;
}
/// <summary>
/// Creates a copy of the defined name to target worksheet.
/// </summary>
/// <param name="worksheet">Worksheet to copy to.</param>
/// <param name="name">The name for the copy.</param>
/// <returns>A new ExcelNameRange</returns>
/// <exception cref="InvalidOperationException">If the original does not contain a formula, value or range, this exception occurs.</exception>
public ExcelNamedRange Copy(ExcelWorksheet worksheet, string name)
{
//Detect if formula, value or range
if (NameFormula != null)
return worksheet.Names.AddFormula(name, NameFormula);
else if (NameValue != null)
return worksheet.Names.AddValue(name, NameValue);
else if (LocalAddress != "#REF!")
return worksheet.Names.AddRange(name, worksheet.Cells[Address]);
throw new InvalidOperationException($"No value, formula or address has been set for this name: {Name}");
}
/// <summary>
/// Creates a copy of the defined name to target workbook.
/// </summary>
/// <param name="workbook">Workbook to copy to.</param>
/// <param name="name">The name for the copy.</param>
/// <returns>A new ExcelNameRange</returns>
/// <exception cref="InvalidOperationException">If the original does not contain a formula, value or range, this exception occurs.</exception>
public ExcelNamedRange Copy(ExcelWorkbook workbook, string name)
{
//Detect if formula, value or range
if (NameFormula != null)
return workbook.Names.AddFormula(name, NameFormula);
else if (NameValue != null)
return workbook.Names.AddValue(name, NameValue);
else if (LocalAddress != "#REF!")
return workbook.Names.AddRange(name, _worksheet.Cells[Address]);
throw new InvalidOperationException($"No value, formula or address has been set for this name: {Name}");
}

private void ResetObject()
{
Init(Name, _sheet, Index, AllowRelativeAddress);
_address = Name;
_fromCol = -1;
_fromRow = -1;
_toCol = -1;
_toRow = -1;
_start = null;
_end = null;
}

string _r1c1Formula = "";
internal string GetRelativeFormula(int row, int col)
{
Expand Down Expand Up @@ -305,7 +487,7 @@ internal object GetValue(FormulaCellAddress currentCell)
}
else
{
var values = NameValue as Dictionary<ulong, object>;
var values = NameValue as Dictionary<ulong, object>;
if(values!=null)
{
if(values.ContainsKey(currentCell.CellId))
Expand Down
Loading