32 public sealed class DefinedName : IEquatable<DefinedName>, IComparable<DefinedName>
55 private static readonly Regex EXT_WORKSHEET_REFERENCE_REGEX =
new Regex(
56 @"^\[[0-9]+\].+", RegexOptions.Compiled | RegexOptions.CultureInvariant);
58 private const int MAX_NAME_LENGTH = 255;
63 private static readonly HashSet<string> DISALLOWED_NAMES =
new HashSet<string>(StringComparer.OrdinalIgnoreCase)
72 private static readonly
char[] ALLOWED_NAME_START_CHARS = {
'\\',
'_' };
77 private static readonly
char[] ALLOWED_NAME_CHARS = {
'_',
'.',
'\\' };
91 public string Name {
get; }
110 public object Value {
get;
private set; }
129 public FormulaError
Error {
get;
private set; }
136 internal FeatureSet Features {
get;
private set; } = FeatureSet.CreateDefinedName();
160 if (workbook ==
null)
162 throw new FormatException(
"To set a defined name, a workbook must be provided.");
164 ValidateName(workbook, name, localSheet);
165 if (reference ==
null ||
string.IsNullOrEmpty(reference.ToString()))
167 throw new FormatException(
"The reference of a defined name must not be null or empty.");
171 this.Value = reference;
172 this.TargetWorksheet = worksheet;
173 this.LocalSheet = localSheet;
174 this.Comment = comment;
176 this.Features.Add(workbook.Features);
177 bool hasExternalLinks = containsExternalLinks
180 this.Features.SetDefinedNameFeatures(isFormula, hasExternalLinks);
195 internal static DefinedName ResolveDefinedName(
string name,
string reference, Workbook workbook, Worksheet localSheet,
string comment)
197 string worksheetName;
199 FormulaError formulaError;
200 object value = GetParsedObject(reference, out type, out worksheetName, out formulaError);
201 bool containsExternalLink = ContainsExternalLink(worksheetName, type, value);
203 Worksheet worksheet =
null;
204 if (worksheetName !=
null && !containsExternalLink)
208 if (
string.
Equals(worksheetName, ws.
SheetName, StringComparison.OrdinalIgnoreCase))
215 DefinedName definedName =
new DefinedName(workbook, type, name, value, worksheet, localSheet, comment, containsExternalLink);
216 definedName.Error = formulaError;
227 internal void ReplaceExpression(
string expression)
244 private static void ValidateName(Workbook workbook,
string name, Worksheet localSheet)
246 if (
string.IsNullOrWhiteSpace(name))
248 throw new FormatException(
"The name of a defined name must not be null or empty.");
250 if (name.Length > MAX_NAME_LENGTH)
252 throw new FormatException($
"A defined name must not exceed {MAX_NAME_LENGTH} characters.");
255 char firstChar = name[0];
257 if (!
char.IsLetter(firstChar)
258 && !ALLOWED_NAME_START_CHARS.Contains(firstChar))
260 throw new FormatException($
"The name of a defined name must start with a letter, underscore, or backslash. Provided: '{name}'");
262 if (DISALLOWED_NAMES.Contains(name))
264 throw new FormatException($
"'{name}' cannot be used as a defined name.");
266 for (
int i = 1; i < name.Length; i++)
268 char character = name[i];
270 if (!
char.IsLetterOrDigit(character) && !ALLOWED_NAME_CHARS.Contains(character))
272 throw new FormatException($
"The character '{character}' at position {i} is not valid in the defined name '{name}'.");
275 if (workbook.FindDefinedNameIndex(name, localSheet) >= 0)
277 string scope = localSheet ==
null ?
"workbook" :
"worksheet '" + localSheet.SheetName +
"'";
278 throw new WorksheetException(
"A defined name with the name '" + name +
"' already exists in the " + scope +
" scope.");
282 Validators.ValidateCellAddressExpression(name,
Cell.AddressScope.SingleAddress);
289 throw new FormatException($
"The defined name '{name}' must not be a valid cell address.");
297 private void CastValue(Workbook workbook)
303 string address = this.Value is Address addressValue ? addressValue.ToString() : this.Value as string;
304 Validators.ValidateCellAddressExpression(address,
Cell.AddressScope.SingleAddress);
305 Address fixedAddress =
new Address(address,
Cell.AddressType.FixedRowAndColumn);
306 this.TextValue = fixedAddress.
ToString();
307 this.Value = fixedAddress;
310 string range = this.Value is
Range rangeValue ? rangeValue.ToString() : this.Value as string;
311 Validators.ValidateCellAddressExpression(range,
Cell.AddressScope.Range);
314 this.TextValue = fixedRange.
ToString();
315 this.Value = fixedRange;
318 this.TextValue = this.
Value.ToString();
321 this.TextValue = ParserUtils.ToCachedValueString(this.
Value,
false);
333 private static bool ContainsExternalLink(
string worksheet,
NameType type,
object value)
338 string formula = value as string;
339 return ParserUtils.ContainsExternalReference(formula);
342 return worksheet !=
null && EXT_WORKSHEET_REFERENCE_REGEX.IsMatch(worksheet);
357 private static object GetParsedObject(
string reference, out
NameType type, out
string worksheet, out FormulaError error)
359 reference = reference?.Trim();
360 error = FormulaError.NoError;
362 if (ParserUtils.TryParseFormulaStringConstant(reference, out
string stringValue))
367 if (ParserUtils.TryParseBool(reference, out
bool boolValue))
372 if (ParserUtils.TryParseInt(reference, out
int intValue))
377 if (ParserUtils.TryParseDouble(reference, out
double doubleValue))
382 string worksheetName;
383 string addressExpression;
384 if (ParserUtils.TryParseWorksheetQualifiedReference(reference, out worksheetName, out addressExpression))
386 worksheet = worksheetName;
389 Address addressValue =
new Address(addressExpression);
408 FormulaError referenceError;
409 if (Errors.TryParseFormulaError(reference, out referenceError))
411 error = referenceError;
430 if (ReferenceEquals(
this, other))
434 return string.Equals(
Name, other.
Name, StringComparison.OrdinalIgnoreCase)
437 &&
string.Equals(
Comment, other.
Comment, StringComparison.Ordinal)
449 return Equals(obj as DefinedName);
461 hash = (hash * 31) + (
Name !=
null ? StringComparer.OrdinalIgnoreCase.GetHashCode(
Name) : 0);
462 hash = (hash * 31) +
Type.GetHashCode();
464 hash = (hash * 31) + (
Comment !=
null ?
Comment.GetHashCode() : 0);
466 hash = (hash * 31) + (
LocalSheet !=
null ? System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(
LocalSheet) : 0);
485 int cmp = StringComparer.OrdinalIgnoreCase.Compare(
Name, other.
Name);
520 return "DefinedName{name=" +
Name +
", scope=" + scope +
", ref=" +
TextValue +
"}";
532 if (ReferenceEquals(left, right))