8using NanoXLSX.Exceptions;
9using NanoXLSX.Interfaces;
10using NanoXLSX.Interfaces.Reader;
11using NanoXLSX.Registry;
12using NanoXLSX.Registry.Attributes;
15using System.Collections.Generic;
23 [NanoXlsxQueuePlugIn(PlugInUUID =
"EXTERNAL_LINK_READ_PROCESSOR", QueueUUID = PlugInUUID.FinalizingInlineProcessor, PlugInOrder = 20000)]
24 internal class ExternalLinkReadProcessor : IPluginInlineReadProcessor
30 public Workbook Workbook {
get;
set; }
40 public void Init(Workbook workbook, IOptions readerOptions,
int? index =
null)
42 this.Workbook = workbook;
51 List<ExternalLink> externalLinks = Workbook.AuxiliaryData.GetDataList<ExternalLink>(PlugInUUID.CompatibilityInlineProcessor, CompatibilityConstants.EXTERNAL_LINK_OBJECT_ENTITY);
52 List<string> externalReferenceRids = Workbook.AuxiliaryData.GetData<List<string>>(PlugInUUID.CompatibilityInlineProcessor, CompatibilityConstants.EXTERNAL_REFERENCE_WORKBOOK_RID_ENTITY);
53 List<ExternalDefinedNameReference> rawDefinedNames = Workbook.AuxiliaryData.GetData<List<ExternalDefinedNameReference>>(PlugInUUID.CompatibilityInlineProcessor, CompatibilityConstants.EXTERNAL_REFERENCE_DEFINED_NAMES_ENTITY);
54 if (externalReferenceRids ==
null)
58 Dictionary<string, ExternalLink> externalReferences =
new Dictionary<string, ExternalLink>();
59 Dictionary<string, DefinedName> replacementMap =
new Dictionary<string, DefinedName>();
60 Dictionary<string, string> structuralReplacements =
new Dictionary<string, string>();
61 PrepareUpdateDefinedNames(
63 externalReferenceRids,
66 structuralReplacements,
68 RebuildDefinedNames(structuralReplacements, replacementMap);
69 if (Workbook.Features.ContainsWorksheetFormulas && externalReferences.Count != 0 && replacementMap.Count > 0)
71 UpdateCellFormulas(externalReferences, replacementMap);
76 private void PrepareUpdateDefinedNames(
77 List<ExternalLink> externalLinks,
78 List<string> externalReferenceRids,
79 Dictionary<string, ExternalLink> externalReferences,
80 Dictionary<string, DefinedName> replacementMap,
81 Dictionary<string, string> structuralReplacements,
82 List<ExternalDefinedNameReference> rawDefinedNames)
84 for (
int i = 0; i < externalReferenceRids.Count; i++)
86 ExternalLink link = externalLinks.FirstOrDefault(l => l !=
null && externalReferenceRids[i].Equals(l.WorkbookRId, StringComparison.OrdinalIgnoreCase));
89 throw new IOException(
"Mismatch of read external links and references in workbooks detected. External references cannot be resolved.");
91 string rId = GetExternalLinkId(i);
92 externalReferences[rId] = link;
94 IReadOnlyList<DefinedName> definedNames = Workbook.GetDefinedNames();
95 for (
int i = 0; i < definedNames.Count; i++)
97 DefinedName defiedName = definedNames[i];
98 string expression = GetDefinedNameExpression(defiedName, rawDefinedNames);
99 if (ExternalLinkFormulaUtils.DetectExternalLinkId(expression))
101 string replacedExpression = ExternalLinkFormulaUtils.ReplaceExternalLinkId(expression, externalReferences);
102 if (ExternalLinkFormulaUtils.DetectExternalLinkId(replacedExpression))
104 throw new IOException(
"Mismatch of read external links and references in defined names detected. External references cannot be resolved.");
106 string id = GetDefinedNameId(defiedName);
107 if (defiedName.Type == DefinedName.NameType.Formula)
109 defiedName.ReplaceExpression(replacedExpression);
110 replacementMap[id] = defiedName;
114 structuralReplacements[id] = replacedExpression;
120 private string GetDefinedNameExpression(DefinedName definedName, List<ExternalDefinedNameReference> rawDefinedNames)
122 if (rawDefinedNames !=
null)
124 foreach (ExternalDefinedNameReference rawDefinedName
in rawDefinedNames)
126 if (
string.Equals(rawDefinedName.Name, definedName.Name, StringComparison.OrdinalIgnoreCase) &&
127 IsSameScope(rawDefinedName.LocalSheetIndex, definedName.LocalSheet))
129 return rawDefinedName.Expression;
133 return definedName.TextValue;
136 private bool IsSameScope(
int? localSheetIndex, Worksheet localSheet)
138 if (!localSheetIndex.HasValue)
140 return localSheet ==
null;
142 int index = localSheetIndex.Value;
143 return index >= 0 && index < Workbook.Worksheets.Count &&
object.ReferenceEquals(Workbook.Worksheets[index], localSheet);
146 private void UpdateCellFormulas(Dictionary<string, ExternalLink> externalReferences, Dictionary<string, DefinedName> replacementMap)
148 foreach (Worksheet worksheet
in Workbook.Worksheets)
150 foreach (KeyValuePair<string, Cell> cell
in worksheet.Cells)
152 if (cell.Value.DataType == Cell.CellType.Formula && cell.Value.Formula !=
null)
154 string originalValue = cell.Value.Value?.ToString() ??
string.Empty;
155 if (ExternalLinkFormulaUtils.DetectExternalLinkId(originalValue))
157 string replacement = ExternalLinkFormulaUtils.ReplaceExternalLinkId(originalValue, externalReferences);
158 if (
object.Equals(cell.Value.Formula.Expression, cell.Value.Value))
160 cell.Value.Value = replacement;
162 cell.Value.Formula.Expression = replacement;
163 cell.Value.Formula.HasExternalReferences =
true;
165 if (replacementMap.Count > 0 && cell.Value.Formula.DefinedNameReference !=
null)
167 string id = GetDefinedNameId(cell.Value.Formula.DefinedNameReference);
168 if (replacementMap.TryGetValue(
id, out DefinedName newValue))
170 cell.Value.Formula.DefinedNameReference = newValue;
178 private void RebuildDefinedNames(Dictionary<string, string> structuralReplacements, Dictionary<string, DefinedName> replacementMap)
180 if (structuralReplacements.Count == 0)
185 List<DefinedName> definedNames = Workbook.GetDefinedNames().ToList();
186 for (
int i = definedNames.Count - 1; i >= 0; i--)
188 DefinedName definedName = definedNames[i];
189 Workbook.RemoveDefinedName(definedName.Name,
false, definedName.LocalSheet);
192 foreach (DefinedName definedName
in definedNames)
194 string id = GetDefinedNameId(definedName);
195 if (structuralReplacements.TryGetValue(
id, out
string expression))
197 DefinedName replacement =
new DefinedName(
199 DefinedName.NameType.Formula,
203 definedName.LocalSheet,
206 Workbook.AddDefinedName(replacement);
207 replacementMap[id] = replacement;
211 definedName.Features.Add(Workbook.Features);
212 Workbook.AddDefinedName(definedName);
217 private static string GetDefinedNameId(DefinedName defiedName)
220 if (defiedName.LocalSheet ==
null)
222 id = defiedName.Name;
226 id = defiedName.Name +
"@" + ParserUtils.ToString(defiedName.LocalSheet.GetHashCode());
234 private static string GetExternalLinkId(
int zeroBasedIndex)
236 return "[" + ParserUtils.ToString(zeroBasedIndex + 1) +
"]";