NanoXLSX.Core 3.2.1
Loading...
Searching...
No Matches
DefinedName.cs
1/*
2 * NanoXLSX is a small .NET library to generate and read XLSX (Microsoft Excel 2007 or newer) files in an easy and native way
3 * Copyright Raphael Stoeckli © 2026
4 * This library is licensed under the MIT License.
5 * You find a copy of the license in project folder or on: http://opensource.org/licenses/MIT
6 */
7
8using System;
9using System.Collections.Generic;
10using System.Linq;
11using System.Text.RegularExpressions;
12using NanoXLSX.Enums;
15using NanoXLSX.Utils;
16using static NanoXLSX.Enums.Errors;
18
19namespace NanoXLSX
20{
32 public sealed class DefinedName : IEquatable<DefinedName>, IComparable<DefinedName>
33 {
34 #region enums
38 public enum NameType
39 {
48
49 }
50
51 #endregion
52
53 #region constants
54
55 private static readonly Regex EXT_WORKSHEET_REFERENCE_REGEX = new Regex(
56 @"^\‍[[0-9]+\‍].+", RegexOptions.Compiled | RegexOptions.CultureInvariant);
57
58 private const int MAX_NAME_LENGTH = 255;
59
63 private static readonly HashSet<string> DISALLOWED_NAMES = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
64 {
65 "C",
66 "R"
67 };
68
72 private static readonly char[] ALLOWED_NAME_START_CHARS = { '\\', '_' };
77 private static readonly char[] ALLOWED_NAME_CHARS = { '_', '.', '\\' };
78
79 #endregion
80
81 #region properties
82
86 public NameType Type { get; }
87
91 public string Name { get; }
92
96 public Worksheet TargetWorksheet { get; }
97
104 public string TextValue { get; private set; }
105
110 public object Value { get; private set; }
111
117 public Worksheet LocalSheet { get; }
118
123 public string Comment { get; }
124
129 public FormulaError Error { get; private set; }
130
134 public bool HasExternalReferences { get { return Features.ContainsExternalLinks; } }
135
136 internal FeatureSet Features { get; private set; } = FeatureSet.CreateDefinedName();
137
138 #endregion
139
140 #region constructors
141
158 internal DefinedName(Workbook workbook, NameType type, string name, object reference, Worksheet worksheet, Worksheet localSheet = null, string comment = null, bool? containsExternalLinks = null)
159 {
160 if (workbook == null)
161 {
162 throw new FormatException("To set a defined name, a workbook must be provided.");
163 }
164 ValidateName(workbook, name, localSheet);
165 if (reference == null || string.IsNullOrEmpty(reference.ToString()))
166 {
167 throw new FormatException("The reference of a defined name must not be null or empty.");
168 }
169 this.Type = type;
170 this.Name = name;
171 this.Value = reference;
172 this.TargetWorksheet = worksheet;
173 this.LocalSheet = localSheet;
174 this.Comment = comment;
175 CastValue(workbook);
176 this.Features.Add(workbook.Features); // Add feature reference already here
177 bool hasExternalLinks = containsExternalLinks
178 ?? (this.Type == NameType.Formula && ParserUtils.ContainsExternalReference(this.TextValue));
179 bool isFormula = this.Type == NameType.Formula || hasExternalLinks;
180 this.Features.SetDefinedNameFeatures(isFormula, hasExternalLinks);
181 }
182 #endregion
183
184 #region methods
185
195 internal static DefinedName ResolveDefinedName(string name, string reference, Workbook workbook, Worksheet localSheet, string comment)
196 {
197 string worksheetName;
198 NameType type;
199 FormulaError formulaError;
200 object value = GetParsedObject(reference, out type, out worksheetName, out formulaError);
201 bool containsExternalLink = ContainsExternalLink(worksheetName, type, value);
202 //workbook.Features.SetFormulaFeatures(false, containsExternalLink);
203 Worksheet worksheet = null;
204 if (worksheetName != null && !containsExternalLink)
205 {
206 foreach (Worksheet ws in workbook.Worksheets)
207 {
208 if (string.Equals(worksheetName, ws.SheetName, StringComparison.OrdinalIgnoreCase))
209 {
210 worksheet = ws;
211 break;
212 }
213 }
214 }
215 DefinedName definedName = new DefinedName(workbook, type, name, value, worksheet, localSheet, comment, containsExternalLink);
216 definedName.Error = formulaError;
217 //definedName.HasExternalReferences = containsExternalLink;
218 //definedName.Features.SetExternalLinkFeature(containsExternalLink);
219 return definedName;
220 }
221
227 internal void ReplaceExpression(string expression) // Do not remove. This method may be used by NanoXLSX.Compatibility
228 {
229 if (Type == NameType.Formula)
230 {
231 Value = expression;
232 TextValue = expression;
233 }
234 }
235
244 private static void ValidateName(Workbook workbook, string name, Worksheet localSheet)
245 {
246 if (string.IsNullOrWhiteSpace(name))
247 {
248 throw new FormatException("The name of a defined name must not be null or empty.");
249 }
250 if (name.Length > MAX_NAME_LENGTH)
251 {
252 throw new FormatException($"A defined name must not exceed {MAX_NAME_LENGTH} characters.");
253 }
254
255 char firstChar = name[0];
256
257 if (!char.IsLetter(firstChar)
258 && !ALLOWED_NAME_START_CHARS.Contains(firstChar))
259 {
260 throw new FormatException($"The name of a defined name must start with a letter, underscore, or backslash. Provided: '{name}'");
261 }
262 if (DISALLOWED_NAMES.Contains(name))
263 {
264 throw new FormatException($"'{name}' cannot be used as a defined name.");
265 }
266 for (int i = 1; i < name.Length; i++)
267 {
268 char character = name[i];
269
270 if (!char.IsLetterOrDigit(character) && !ALLOWED_NAME_CHARS.Contains(character))
271 {
272 throw new FormatException($"The character '{character}' at position {i} is not valid in the defined name '{name}'.");
273 }
274 }
275 if (workbook.FindDefinedNameIndex(name, localSheet) >= 0)
276 {
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.");
279 }
280 try
281 {
282 Validators.ValidateCellAddressExpression(name, Cell.AddressScope.SingleAddress);
283 }
284 catch
285 {
286 // Not a valid cell address; therefore it may be used as a defined name.
287 return;
288 }
289 throw new FormatException($"The defined name '{name}' must not be a valid cell address.");
290 }
291
297 private void CastValue(Workbook workbook)
298 {
299 switch (this.Type)
300 {
301 // The object type is assumed to be validated prior
302 case NameType.Cell:
303 string address = this.Value is Address addressValue ? addressValue.ToString() : this.Value as string;
304 Validators.ValidateCellAddressExpression(address, Cell.AddressScope.SingleAddress); // throw if not an address
305 Address fixedAddress = new Address(address, Cell.AddressType.FixedRowAndColumn);
306 this.TextValue = fixedAddress.ToString();
307 this.Value = fixedAddress; // Reformat passed object
308 break;
309 case NameType.Range:
310 string range = this.Value is Range rangeValue ? rangeValue.ToString() : this.Value as string;
311 Validators.ValidateCellAddressExpression(range, Cell.AddressScope.Range); // throw if not valid range
312 Range tempRange = new Range(range);
313 Range fixedRange = new Range(new Address(tempRange.StartAddress.Column, tempRange.StartAddress.Row, Cell.AddressType.FixedRowAndColumn), new Address(tempRange.EndAddress.Column, tempRange.EndAddress.Row, Cell.AddressType.FixedRowAndColumn));
314 this.TextValue = fixedRange.ToString();
315 this.Value = fixedRange;
316 break;
317 case NameType.Formula:
318 this.TextValue = this.Value.ToString(); // No formula validation yet
319 break;
320 default: // constant
321 this.TextValue = ParserUtils.ToCachedValueString(this.Value, false);
322 break;
323 }
324 }
325
333 private static bool ContainsExternalLink(string worksheet, NameType type, object value)
334 {
335 switch (type)
336 {
337 case NameType.Formula:
338 string formula = value as string;
339 return ParserUtils.ContainsExternalReference(formula);
340 case NameType.Range:
341 case NameType.Cell:
342 return worksheet != null && EXT_WORKSHEET_REFERENCE_REGEX.IsMatch(worksheet);
343 default: // constant
344 break; // NoOp
345 }
346 return false;
347 }
348
357 private static object GetParsedObject(string reference, out NameType type, out string worksheet, out FormulaError error)
358 {
359 reference = reference?.Trim();
360 error = FormulaError.NoError;
361 worksheet = null;
362 if (ParserUtils.TryParseFormulaStringConstant(reference, out string stringValue))
363 {
364 type = NameType.Constant; // Formula string is interpreted as constant in this case
365 return stringValue;
366 }
367 if (ParserUtils.TryParseBool(reference, out bool boolValue))
368 {
369 type = NameType.Constant;
370 return boolValue;
371 }
372 if (ParserUtils.TryParseInt(reference, out int intValue))
373 {
374 type = NameType.Constant;
375 return intValue;
376 }
377 if (ParserUtils.TryParseDouble(reference, out double doubleValue))
378 {
379 type = NameType.Constant;
380 return doubleValue;
381 }
382 string worksheetName;
383 string addressExpression;
384 if (ParserUtils.TryParseWorksheetQualifiedReference(reference, out worksheetName, out addressExpression))
385 {
386 worksheet = worksheetName;
387 try
388 {
389 Address addressValue = new Address(addressExpression);
390 type = NameType.Cell;
391 return addressValue;
392 }
393 catch
394 {
395 // NoOp
396 }
397 try
398 {
399 Range range = new Range(addressExpression);
400 type = NameType.Range;
401 return range;
402 }
403 catch
404 {
405 // NoOp
406 }
407 }
408 FormulaError referenceError;
409 if (Errors.TryParseFormulaError(reference, out referenceError))
410 {
411 error = referenceError;
412 }
413 type = NameType.Formula;
414 return reference;
415 }
416
424 public bool Equals(DefinedName other)
425 {
426 if (other is null)
427 {
428 return false;
429 }
430 if (ReferenceEquals(this, other))
431 {
432 return true;
433 }
434 return string.Equals(Name, other.Name, StringComparison.OrdinalIgnoreCase)
435 && Enum.Equals(Type, other.Type)
436 && string.Equals(TextValue, other.TextValue, StringComparison.Ordinal) // object implicit compared by string
437 && string.Equals(Comment, other.Comment, StringComparison.Ordinal)
438 && ReferenceEquals(TargetWorksheet, other.TargetWorksheet)
439 && ReferenceEquals(LocalSheet, other.LocalSheet);
440 }
441
447 public override bool Equals(object obj)
448 {
449 return Equals(obj as DefinedName);
450 }
451
456 public override int GetHashCode()
457 {
458 unchecked
459 {
460 int hash = 17;
461 hash = (hash * 31) + (Name != null ? StringComparer.OrdinalIgnoreCase.GetHashCode(Name) : 0);
462 hash = (hash * 31) + Type.GetHashCode();
463 hash = (hash * 31) + (TextValue != null ? TextValue.GetHashCode() : 0); // Object implicit covered by string
464 hash = (hash * 31) + (Comment != null ? Comment.GetHashCode() : 0);
465 hash = (hash * 31) + (TargetWorksheet != null ? System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(TargetWorksheet) : 0);
466 hash = (hash * 31) + (LocalSheet != null ? System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(LocalSheet) : 0);
467 return hash;
468 }
469 }
470
479 public int CompareTo(DefinedName other)
480 {
481 if (other is null)
482 {
483 return 1;
484 }
485 int cmp = StringComparer.OrdinalIgnoreCase.Compare(Name, other.Name);
486 if (cmp != 0)
487 {
488 return cmp;
489 }
490 cmp = Type.CompareTo(other.Type);
491 if (cmp != 0)
492 {
493 return cmp;
494 }
495 cmp = CompareScope(LocalSheet, other.LocalSheet);
496 if (cmp != 0)
497 {
498 return cmp;
499 }
500 cmp = CompareScope(TargetWorksheet, other.TargetWorksheet);
501 if (cmp != 0)
502 {
503 return cmp;
504 }
505 cmp = string.CompareOrdinal(TextValue, other.TextValue);
506 if (cmp != 0)
507 {
508 return cmp;
509 }
510 return string.CompareOrdinal(Comment, other.Comment);
511 }
512
517 public override string ToString()
518 {
519 string scope = LocalSheet == null ? "workbook" : "sheet:" + LocalSheet.SheetName;
520 return "DefinedName{name=" + Name + ", scope=" + scope + ", ref=" + TextValue + "}";
521 }
522
530 private static int CompareScope(Worksheet left, Worksheet right)
531 {
532 if (ReferenceEquals(left, right))
533 {
534 return 0;
535 }
536 if (left == null)
537 {
538 return -1;
539 }
540 if (right == null)
541 {
542 return 1;
543 }
544 return left.SheetID.CompareTo(right.SheetID);
545 }
546 #endregion
547 }
548}
Class representing a cell of a worksheet.
Definition Cell.cs:25
Class representing a defined name within a workbook. A defined name is a descriptive text that repres...
object Value
Gets the raw reference of the defined Name. The value will be transformed in its appropriate text val...
override bool Equals(object obj)
Determines whether the specified object is equal to the current instance.
FormulaError Error
Gets a possible error of the whole value in a defined name. Default is FormulaError....
bool Equals(DefinedName other)
Determines whether the specified DefinedName instance is equal to the current instance....
string TextValue
Gets the textual reference of the defined name. This is stored verbatim and may be a cell address (e....
Worksheet LocalSheet
Gets the worksheet that scopes (constraint) this defined name. If null, the defined name has workbook...
bool HasExternalReferences
Gets whether the value contains a reference or multiple references to an external source (e....
override string ToString()
Returns a textual representation of the defined name (intended for debugging).
NameType Type
Type of the defined name.
override int GetHashCode()
Returns a hash code consistent with Equals(DefinedName).
string Comment
Gets the optional comment associated with the defined name. Maps to the comment attribute in OOXML....
Worksheet TargetWorksheet
Gets the target worksheet in case of Cell or Range values. For other types, like formulas or constant...
string Name
Gets the name of the defined name as it appears in the workbook (e.g. MyRange).
int CompareTo(DefinedName other)
Compares this instance with another DefinedName for ordering. The order is determined by Name (ordina...
NameType
Enum to specify the type of the defined name.
@ Cell
Defined name is a single cell.
@ Formula
Defined name is a formula.
@ Range
Defined name is a cell range.
@ Constant
Defined name is a constant value.
Static class that contains shared enums for error cases.
Definition Errors.cs:16
Class for exceptions regarding format error incidents.
Class representing a workbook.
Definition Workbook.cs:25
List< Worksheet > Worksheets
Gets the list of worksheets in the workbook.
Definition Workbook.cs:132
Class representing a worksheet of a workbook.
Definition Worksheet.cs:27
int SheetID
Gets or sets the internal ID of the worksheet.
Definition Worksheet.cs:331
string SheetName
Gets or sets the name of the worksheet.
Definition Worksheet.cs:347
int Row
Row number (zero based).
Definition Address.cs:28
override string ToString()
Overwritten ToString method.
Definition Address.cs:102
int Column
Column number (zero based).
Definition Address.cs:24
Struct representing a cell range with a start and end address.
Definition Range.cs:16
override string ToString()
Overwritten ToString method.
Definition Range.cs:136
Address StartAddress
Start address of the range.
Definition Range.cs:27
Address EndAddress
End address of the range.
Definition Range.cs:23