From 7eb119632dec66201a94f6db81f6686771102313 Mon Sep 17 00:00:00 2001 From: A-nony-mous Date: Sat, 24 Jan 2026 02:39:21 +0800 Subject: [PATCH 1/5] fix: fix template export generating corrupted Excel files - Change SharedString conversion to use inlineStr format with structure - Add SetCellType method to properly handle cell types (inlineStr for strings, remove t attr for numbers) - Fix XML element order per ECMA-376 spec (autoFilter before mergeCells/phoneticPr) - Clean duplicate xmlns declarations from phoneticPr, conditionalFormatting, autoFilter - Remove empty tags that cause invalid XML - Support structure in value node lookup Fixes template SaveAs generating files that Excel cannot open --- .../OpenXml/Templates/OpenXmlTemplate.Impl.cs | 138 +++++++++++++++--- 1 file changed, 117 insertions(+), 21 deletions(-) diff --git a/src/MiniExcel.Core/OpenXml/Templates/OpenXmlTemplate.Impl.cs b/src/MiniExcel.Core/OpenXml/Templates/OpenXmlTemplate.Impl.cs index b4759992..d1e0ede6 100644 --- a/src/MiniExcel.Core/OpenXml/Templates/OpenXmlTemplate.Impl.cs +++ b/src/MiniExcel.Core/OpenXml/Templates/OpenXmlTemplate.Impl.cs @@ -299,6 +299,15 @@ private async Task WriteSheetXmlAsync(Stream outputFileStream, XmlDocument doc, phoneticPr.ParentNode.RemoveChild(phoneticPr); } + // Extract autoFilter - must be written before mergeCells and phoneticPr per ECMA-376 + var autoFilter = doc.SelectSingleNode("/x:worksheet/x:autoFilter", Ns); + var autoFilterXml = string.Empty; + if (autoFilter is not null) + { + autoFilterXml = autoFilter.OuterXml; + autoFilter.ParentNode.RemoveChild(autoFilter); + } + var contents = doc.InnerXml.Split(new[] { $"<{prefix}sheetData>{{{{{{{{{{{{split}}}}}}}}}}}}" }, StringSplitOptions.None); using var writer = new StreamWriter(outputFileStream, Encoding.UTF8); @@ -524,6 +533,19 @@ await writer.WriteAsync($"" #endif ).ConfigureAwait(false); + // ECMA-376 element order: sheetData → autoFilter → mergeCells → phoneticPr → conditionalFormatting + + // 1. autoFilter (must come before mergeCells) + if (!string.IsNullOrEmpty(autoFilterXml)) + { + await writer.WriteAsync(CleanXml(autoFilterXml, endPrefix) +#if NET7_0_OR_GREATER + .AsMemory(), cancellationToken +#endif + ).ConfigureAwait(false); + } + + // 2. mergeCells if (_newXMergeCellInfos.Count != 0) { await writer.WriteAsync($"<{prefix}mergeCells count=\"{_newXMergeCellInfos.Count}\">" @@ -546,18 +568,20 @@ await writer.WriteLineAsync($"" ).ConfigureAwait(false); } + // 3. phoneticPr if (!string.IsNullOrEmpty(phoneticPrXml)) { - await writer.WriteAsync(phoneticPrXml + await writer.WriteAsync(CleanXml(phoneticPrXml, endPrefix) #if NET7_0_OR_GREATER .AsMemory(), cancellationToken #endif ).ConfigureAwait(false); } + // 4. conditionalFormatting if (newConditionalFormatRanges.Count != 0) { - await writer.WriteAsync(string.Join(string.Empty, newConditionalFormatRanges.Select(cf => cf.Node.OuterXml)) + await writer.WriteAsync(CleanXml(string.Join(string.Empty, newConditionalFormatRanges.Select(cf => cf.Node.OuterXml)), endPrefix) #if NET7_0_OR_GREATER .AsMemory(), cancellationToken #endif @@ -762,6 +786,10 @@ private async Task GenerateCellValuesAsync(GenerateCe substXmlRow = rowXml.ToString(); substXmlRow = TemplateRegex.Replace(substXmlRow, MatchDelegate); + + // Cleanup empty tags which defaults to invalid XML + substXmlRow = Regex.Replace(substXmlRow, @"\s*", ""); + substXmlRow = Regex.Replace(substXmlRow, @"\s*", ""); } rowXml.Clear(); @@ -794,9 +822,13 @@ private async Task GenerateCellValuesAsync(GenerateCe var mergeBaseRowIndex = newRowIndex; newRowIndex += rowInfo.IEnumerableMercell?.Height ?? 1; + // Replace {{$rowindex}} in the already-built substXmlRow + rowXml.Replace("{{$rowindex}}", mergeBaseRowIndex.ToString()); + // replace formulas ProcessFormulas(rowXml, newRowIndex); - await writer.WriteAsync(CleanXml(rowXml, endPrefix).ToString() + var finalXml = CleanXml(rowXml, endPrefix).ToString(); + await writer.WriteAsync(finalXml #if NET7_0_OR_GREATER .AsMemory(), cancellationToken #endif @@ -1040,7 +1072,8 @@ private void ProcessFormulas(StringBuilder rowXml, int rowIndex) private static string CleanXml(string xml, string endPrefix) => CleanXml(new StringBuilder(xml), endPrefix).ToString(); private static StringBuilder CleanXml(StringBuilder xml, string endPrefix) => xml .Replace("xmlns:x14ac=\"http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac\"", "") - .Replace($"xmlns{endPrefix}=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\"", ""); + .Replace($"xmlns{endPrefix}=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\"", "") + .Replace("xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\"", ""); private static void ReplaceSharedStringsToStr(IDictionary sharedStrings, XmlNodeList rows) { @@ -1061,10 +1094,67 @@ private static void ReplaceSharedStringsToStr(IDictionary sharedStr if (sharedStrings is null || !sharedStrings.TryGetValue(int.Parse(v.InnerText), out var shared)) continue; - // change type = str and replace its value - //TODO: remove sharedstring? - v.InnerText = shared; - c.SetAttribute("t", "str"); + // change type = inlineStr and replace its value + c.RemoveChild(v); + var isNode = c.OwnerDocument.CreateElement("is", Schemas.SpreadsheetmlXmlns); + var tNode = c.OwnerDocument.CreateElement("t", Schemas.SpreadsheetmlXmlns); + tNode.InnerText = shared; + isNode.AppendChild(tNode); + c.AppendChild(isNode); + + c.RemoveAttribute("t"); + c.SetAttribute("t", "inlineStr"); + } + } + } + + private static void SetCellType(XmlElement c, string type) + { + if (type == "str") type = "inlineStr"; // Force inlineStr for strings + + if (type == "inlineStr") + { + // Ensure ... + c.SetAttribute("t", "inlineStr"); + var v = c.SelectSingleNode("x:v", Ns); + if (v != null) + { + var text = v.InnerText; + c.RemoveChild(v); + var isNode = c.OwnerDocument.CreateElement("is", Schemas.SpreadsheetmlXmlns); + var tNode = c.OwnerDocument.CreateElement("t", Schemas.SpreadsheetmlXmlns); + tNode.InnerText = text; + isNode.AppendChild(tNode); + c.AppendChild(isNode); + } + else if (c.SelectSingleNode("x:is", Ns) == null) + { + // Create empty if neither nor exists + var isNode = c.OwnerDocument.CreateElement("is", Schemas.SpreadsheetmlXmlns); + var tNode = c.OwnerDocument.CreateElement("t", Schemas.SpreadsheetmlXmlns); + isNode.AppendChild(tNode); + c.AppendChild(isNode); + } + } + else + { + // Ensure ... + // For numbers/booleans, we remove 't' attribute to let it be default (number) + // or we could set it to 'n' explicitly, but removing is safer for general number types + if (type == "b") + c.SetAttribute("t", "b"); + else + c.RemoveAttribute("t"); + + var isNode = c.SelectSingleNode("x:is", Ns); + if (isNode != null) + { + var tNode = isNode.SelectSingleNode("x:t", Ns); + var text = tNode?.InnerText; + c.RemoveChild(isNode); + var v = c.OwnerDocument.CreateElement("v", Schemas.SpreadsheetmlXmlns); + v.InnerText = text; + c.AppendChild(v); } } } @@ -1117,7 +1207,7 @@ private void UpdateDimensionAndGetRowsInfo(IDictionary inputMap c.SetAttribute("r", $"{StringHelper.GetLetters(r)}{{{{$rowindex}}}}"); } - var v = c.SelectSingleNode("x:v", Ns); + var v = c.SelectSingleNode("x:v", Ns) ?? c.SelectSingleNode("x:is/x:t", Ns); if (v?.InnerText is null) continue; @@ -1240,19 +1330,19 @@ private void UpdateDimensionAndGetRowsInfo(IDictionary inputMap if (isMultiMatch) { - c.SetAttribute("t", "str"); + SetCellType(c, "str"); } else if (TypeHelper.IsNumericType(type) && !type.IsEnum) { - c.SetAttribute("t", "n"); + SetCellType(c, "n"); } else if (Type.GetTypeCode(type) == TypeCode.Boolean) { - c.SetAttribute("t", "b"); + SetCellType(c, "b"); } else if (Type.GetTypeCode(type) == TypeCode.DateTime) { - c.SetAttribute("t", "str"); + SetCellType(c, "str"); } break; @@ -1292,19 +1382,19 @@ private void UpdateDimensionAndGetRowsInfo(IDictionary inputMap if (isMultiMatch) { - c.SetAttribute("t", "str"); + SetCellType(c, "str"); } else if (TypeHelper.IsNumericType(type) && !type.IsEnum) { - c.SetAttribute("t", "n"); + SetCellType(c, "n"); } else if (Type.GetTypeCode(type) == TypeCode.Boolean) { - c.SetAttribute("t", "b"); + SetCellType(c, "b"); } else if (Type.GetTypeCode(type) == TypeCode.DateTime) { - c.SetAttribute("t", "str"); + SetCellType(c, "str"); } } else @@ -1312,16 +1402,16 @@ private void UpdateDimensionAndGetRowsInfo(IDictionary inputMap var cellValueStr = cellValue?.ToString(); // value did encodexml, so don't duplicate encode value (https://gitee.com/dotnetchina/MiniExcel/issues/I4DQUN) if (isMultiMatch || cellValue is string) // if matchs count over 1 need to set type=str (https://user-images.githubusercontent.com/12729184/114530109-39d46d00-9c7d-11eb-8f6b-52ad8600aca3.png) { - c.SetAttribute("t", "str"); + SetCellType(c, "str"); } else if (decimal.TryParse(cellValueStr, out var outV)) { - c.SetAttribute("t", "n"); + SetCellType(c, "n"); cellValueStr = outV.ToString(CultureInfo.InvariantCulture); } else if (cellValue is bool b) { - c.SetAttribute("t", "b"); + SetCellType(c, "b"); cellValueStr = b ? "1" : "0"; } else if (cellValue is DateTime timestamp) @@ -1330,6 +1420,12 @@ private void UpdateDimensionAndGetRowsInfo(IDictionary inputMap cellValueStr = timestamp.ToString("yyyy-MM-dd HH:mm:ss"); } + if (string.IsNullOrEmpty(cellValueStr) && string.IsNullOrEmpty(c.GetAttribute("t"))) + { + SetCellType(c, "str"); + v = c.SelectSingleNode("x:v", Ns) ?? c.SelectSingleNode("x:is/x:t", Ns); + } + v.InnerText = v.InnerText.Replace($"{{{{{propNames[0]}}}}}", cellValueStr); //TODO: auto check type and set value } } @@ -1396,4 +1492,4 @@ private static bool EvaluateStatement(object tagValue, string comparisonOperator _ => false }; } -} \ No newline at end of file +} From aeb5f0ef7aacb761d07403f5becc3d83da2d761c Mon Sep 17 00:00:00 2001 From: A-nony-mous Date: Mon, 26 Jan 2026 03:54:30 +0800 Subject: [PATCH 2/5] fix: handle inline string cells and preserve namespace prefixes in Excel export --- .../OpenXml/Templates/OpenXmlTemplate.Impl.cs | 46 +++++++++++++++---- 1 file changed, 38 insertions(+), 8 deletions(-) diff --git a/src/MiniExcel.Core/OpenXml/Templates/OpenXmlTemplate.Impl.cs b/src/MiniExcel.Core/OpenXml/Templates/OpenXmlTemplate.Impl.cs index d1e0ede6..5d3664b1 100644 --- a/src/MiniExcel.Core/OpenXml/Templates/OpenXmlTemplate.Impl.cs +++ b/src/MiniExcel.Core/OpenXml/Templates/OpenXmlTemplate.Impl.cs @@ -625,9 +625,19 @@ private async Task GenerateCellValuesAsync(GenerateCe var notFirstRowElement = rowElement.Clone(); foreach (XmlElement c in notFirstRowElement.SelectNodes("x:c", Ns)) { + // Try first (for t="n"/t="b" cells), then (for t="inlineStr" cells) var v = c.SelectSingleNode("x:v", Ns); if (v is not null && !NonTemplateRegex.IsMatch(v.InnerText)) + { v.InnerText = string.Empty; + } + else + { + // Handle inline string cells + var t = c.SelectSingleNode("x:is/x:t", Ns); + if (t is not null && !NonTemplateRegex.IsMatch(t.InnerText)) + t.InnerText = string.Empty; + } } foreach (var item in rowInfo.CellIEnumerableValues) @@ -1095,9 +1105,15 @@ private static void ReplaceSharedStringsToStr(IDictionary sharedStr continue; // change type = inlineStr and replace its value + // Use the same prefix as the source element to handle namespaced documents (e.g., x:v -> x:is, x:t) + var prefix = v.Prefix; c.RemoveChild(v); - var isNode = c.OwnerDocument.CreateElement("is", Schemas.SpreadsheetmlXmlns); - var tNode = c.OwnerDocument.CreateElement("t", Schemas.SpreadsheetmlXmlns); + var isNode = string.IsNullOrEmpty(prefix) + ? c.OwnerDocument.CreateElement("is", Schemas.SpreadsheetmlXmlns) + : c.OwnerDocument.CreateElement(prefix, "is", Schemas.SpreadsheetmlXmlns); + var tNode = string.IsNullOrEmpty(prefix) + ? c.OwnerDocument.CreateElement("t", Schemas.SpreadsheetmlXmlns) + : c.OwnerDocument.CreateElement(prefix, "t", Schemas.SpreadsheetmlXmlns); tNode.InnerText = shared; isNode.AppendChild(tNode); c.AppendChild(isNode); @@ -1112,6 +1128,9 @@ private static void SetCellType(XmlElement c, string type) { if (type == "str") type = "inlineStr"; // Force inlineStr for strings + // Determine the prefix used in this document (e.g., "x" for x:c, x:v, etc.) + var prefix = c.Prefix; + if (type == "inlineStr") { // Ensure ... @@ -1121,8 +1140,12 @@ private static void SetCellType(XmlElement c, string type) { var text = v.InnerText; c.RemoveChild(v); - var isNode = c.OwnerDocument.CreateElement("is", Schemas.SpreadsheetmlXmlns); - var tNode = c.OwnerDocument.CreateElement("t", Schemas.SpreadsheetmlXmlns); + var isNode = string.IsNullOrEmpty(prefix) + ? c.OwnerDocument.CreateElement("is", Schemas.SpreadsheetmlXmlns) + : c.OwnerDocument.CreateElement(prefix, "is", Schemas.SpreadsheetmlXmlns); + var tNode = string.IsNullOrEmpty(prefix) + ? c.OwnerDocument.CreateElement("t", Schemas.SpreadsheetmlXmlns) + : c.OwnerDocument.CreateElement(prefix, "t", Schemas.SpreadsheetmlXmlns); tNode.InnerText = text; isNode.AppendChild(tNode); c.AppendChild(isNode); @@ -1130,8 +1153,12 @@ private static void SetCellType(XmlElement c, string type) else if (c.SelectSingleNode("x:is", Ns) == null) { // Create empty if neither nor exists - var isNode = c.OwnerDocument.CreateElement("is", Schemas.SpreadsheetmlXmlns); - var tNode = c.OwnerDocument.CreateElement("t", Schemas.SpreadsheetmlXmlns); + var isNode = string.IsNullOrEmpty(prefix) + ? c.OwnerDocument.CreateElement("is", Schemas.SpreadsheetmlXmlns) + : c.OwnerDocument.CreateElement(prefix, "is", Schemas.SpreadsheetmlXmlns); + var tNode = string.IsNullOrEmpty(prefix) + ? c.OwnerDocument.CreateElement("t", Schemas.SpreadsheetmlXmlns) + : c.OwnerDocument.CreateElement(prefix, "t", Schemas.SpreadsheetmlXmlns); isNode.AppendChild(tNode); c.AppendChild(isNode); } @@ -1152,7 +1179,9 @@ private static void SetCellType(XmlElement c, string type) var tNode = isNode.SelectSingleNode("x:t", Ns); var text = tNode?.InnerText; c.RemoveChild(isNode); - var v = c.OwnerDocument.CreateElement("v", Schemas.SpreadsheetmlXmlns); + var v = string.IsNullOrEmpty(prefix) + ? c.OwnerDocument.CreateElement("v", Schemas.SpreadsheetmlXmlns) + : c.OwnerDocument.CreateElement(prefix, "v", Schemas.SpreadsheetmlXmlns); v.InnerText = text; c.AppendChild(v); } @@ -1423,9 +1452,10 @@ private void UpdateDimensionAndGetRowsInfo(IDictionary inputMap if (string.IsNullOrEmpty(cellValueStr) && string.IsNullOrEmpty(c.GetAttribute("t"))) { SetCellType(c, "str"); - v = c.SelectSingleNode("x:v", Ns) ?? c.SelectSingleNode("x:is/x:t", Ns); } + // Re-acquire v after SetCellType may have changed DOM structure + v = c.SelectSingleNode("x:v", Ns) ?? c.SelectSingleNode("x:is/x:t", Ns); v.InnerText = v.InnerText.Replace($"{{{{{propNames[0]}}}}}", cellValueStr); //TODO: auto check type and set value } } From 58c35ded665d93f041ce7b55343c4c689a37b466 Mon Sep 17 00:00:00 2001 From: A-nony-mous Date: Mon, 26 Jan 2026 03:59:15 +0800 Subject: [PATCH 3/5] fix: ensure text is not null when processing XML nodes in Excel export --- src/MiniExcel.Core/OpenXml/Templates/OpenXmlTemplate.Impl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MiniExcel.Core/OpenXml/Templates/OpenXmlTemplate.Impl.cs b/src/MiniExcel.Core/OpenXml/Templates/OpenXmlTemplate.Impl.cs index 5d3664b1..dd75b934 100644 --- a/src/MiniExcel.Core/OpenXml/Templates/OpenXmlTemplate.Impl.cs +++ b/src/MiniExcel.Core/OpenXml/Templates/OpenXmlTemplate.Impl.cs @@ -1177,7 +1177,7 @@ private static void SetCellType(XmlElement c, string type) if (isNode != null) { var tNode = isNode.SelectSingleNode("x:t", Ns); - var text = tNode?.InnerText; + var text = tNode?.InnerText ?? string.Empty; c.RemoveChild(isNode); var v = string.IsNullOrEmpty(prefix) ? c.OwnerDocument.CreateElement("v", Schemas.SpreadsheetmlXmlns) From e9c12518c5ed8e472a94221c8a8795abe72f2cf8 Mon Sep 17 00:00:00 2001 From: A-nony-mous Date: Mon, 26 Jan 2026 04:00:42 +0800 Subject: [PATCH 4/5] fix: update template to use inlineStr format for string cells in Excel export --- tests/MiniExcel.Core.Tests/MiniExcelIssueTests.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/MiniExcel.Core.Tests/MiniExcelIssueTests.cs b/tests/MiniExcel.Core.Tests/MiniExcelIssueTests.cs index b9efff6a..0113a093 100644 --- a/tests/MiniExcel.Core.Tests/MiniExcelIssueTests.cs +++ b/tests/MiniExcel.Core.Tests/MiniExcelIssueTests.cs @@ -1042,8 +1042,9 @@ public void TestIssueI4DQUN() _excelTemplater.ApplyTemplate(path.ToString(), templatePath, value); var sheetXml = SheetHelper.GetZipFileContent(path.ToString(), "xl/worksheets/sheet1.xml"); - Assert.Contains("Hello & World < , > , \" , '", sheetXml); - Assert.Contains("Hello & Value < , > , \" , '", sheetXml); + // Template now uses inlineStr format (...) instead of SharedStrings (...) + Assert.Contains("Hello & World < , > , \" , '", sheetXml); + Assert.Contains("Hello & Value < , > , \" , '", sheetXml); } /// From 24eb49a79778ec60df1616284a1b4d24012aa3f0 Mon Sep 17 00:00:00 2001 From: Michele Bastione Date: Sun, 25 Jan 2026 23:58:39 +0100 Subject: [PATCH 5/5] Couple of fixes Fixing potential bug with inline string tag not being correctly identified when tag is not null but also not matching the regex condition, and added GeneratedRegexAttribute for optimizing empty tag regex replace function --- .../OpenXml/Templates/OpenXmlTemplate.Impl.cs | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/src/MiniExcel.Core/OpenXml/Templates/OpenXmlTemplate.Impl.cs b/src/MiniExcel.Core/OpenXml/Templates/OpenXmlTemplate.Impl.cs index dd75b934..b73e6760 100644 --- a/src/MiniExcel.Core/OpenXml/Templates/OpenXmlTemplate.Impl.cs +++ b/src/MiniExcel.Core/OpenXml/Templates/OpenXmlTemplate.Impl.cs @@ -138,10 +138,13 @@ internal partial class OpenXmlTemplate private static readonly Regex TemplateRegex = TemplateRegexImpl(); [GeneratedRegex(@".*?\{\{.*?\}\}.*?")] private static partial Regex NonTemplateRegexImpl(); private static readonly Regex NonTemplateRegex = NonTemplateRegexImpl(); + [GeneratedRegex(@"<(?:x:)?v>\s*")] private static partial Regex EmptyVTagRegexImpl(); + private static readonly Regex EmptyVTagRegex = EmptyVTagRegexImpl(); #else private static readonly Regex CellRegex = new("([A-Z]+)([0-9]+)", RegexOptions.Compiled); private static readonly Regex TemplateRegex = new(@"\{\{(.*?)\}\}", RegexOptions.Compiled); private static readonly Regex NonTemplateRegex = new(@".*?\{\{.*?\}\}.*?", RegexOptions.Compiled); + private static readonly Regex EmptyVTagRegex = new(@"<(?:x:)?v>\s*", RegexOptions.Compiled); #endif [CreateSyncVersion] @@ -540,7 +543,7 @@ await writer.WriteAsync($"" { await writer.WriteAsync(CleanXml(autoFilterXml, endPrefix) #if NET7_0_OR_GREATER - .AsMemory(), cancellationToken + .AsMemory(), cancellationToken #endif ).ConfigureAwait(false); } @@ -550,7 +553,7 @@ await writer.WriteAsync(CleanXml(autoFilterXml, endPrefix) { await writer.WriteAsync($"<{prefix}mergeCells count=\"{_newXMergeCellInfos.Count}\">" #if NET7_0_OR_GREATER - .AsMemory(), cancellationToken + .AsMemory(), cancellationToken #endif ).ConfigureAwait(false); foreach (var cell in _newXMergeCellInfos) @@ -563,7 +566,7 @@ await writer.WriteAsync(cell.ToXmlString(prefix) } await writer.WriteLineAsync($"" #if NET7_0_OR_GREATER - .AsMemory(), cancellationToken + .AsMemory(), cancellationToken #endif ).ConfigureAwait(false); } @@ -573,7 +576,7 @@ await writer.WriteLineAsync($"" { await writer.WriteAsync(CleanXml(phoneticPrXml, endPrefix) #if NET7_0_OR_GREATER - .AsMemory(), cancellationToken + .AsMemory(), cancellationToken #endif ).ConfigureAwait(false); } @@ -583,7 +586,7 @@ await writer.WriteAsync(CleanXml(phoneticPrXml, endPrefix) { await writer.WriteAsync(CleanXml(string.Join(string.Empty, newConditionalFormatRanges.Select(cf => cf.Node.OuterXml)), endPrefix) #if NET7_0_OR_GREATER - .AsMemory(), cancellationToken + .AsMemory(), cancellationToken #endif ).ConfigureAwait(false); } @@ -626,10 +629,11 @@ private async Task GenerateCellValuesAsync(GenerateCe foreach (XmlElement c in notFirstRowElement.SelectNodes("x:c", Ns)) { // Try first (for t="n"/t="b" cells), then (for t="inlineStr" cells) - var v = c.SelectSingleNode("x:v", Ns); - if (v is not null && !NonTemplateRegex.IsMatch(v.InnerText)) + var vTag = c.SelectSingleNode("x:v", Ns); + if (vTag is not null) { - v.InnerText = string.Empty; + if (!NonTemplateRegex.IsMatch(vTag.InnerText)) + vTag.InnerText = string.Empty; } else { @@ -729,7 +733,7 @@ private async Task GenerateCellValuesAsync(GenerateCe { var replacements = new Dictionary(); #if NETCOREAPP3_0_OR_GREATER - string MatchDelegate(Match x) => CollectionExtensions.GetValueOrDefault(replacements, x.Groups[1].Value, ""); + string MatchDelegate(Match x) => CollectionExtensions.GetValueOrDefault(replacements, x.Groups[1].Value, ""); #else string MatchDelegate(Match x) => replacements.TryGetValue(x.Groups[1].Value, out var repl) ? repl : ""; #endif @@ -798,8 +802,7 @@ private async Task GenerateCellValuesAsync(GenerateCe substXmlRow = TemplateRegex.Replace(substXmlRow, MatchDelegate); // Cleanup empty tags which defaults to invalid XML - substXmlRow = Regex.Replace(substXmlRow, @"\s*", ""); - substXmlRow = Regex.Replace(substXmlRow, @"\s*", ""); + substXmlRow = EmptyVTagRegex.Replace(substXmlRow, ""); } rowXml.Clear(); @@ -1169,9 +1172,9 @@ private static void SetCellType(XmlElement c, string type) // For numbers/booleans, we remove 't' attribute to let it be default (number) // or we could set it to 'n' explicitly, but removing is safer for general number types if (type == "b") - c.SetAttribute("t", "b"); + c.SetAttribute("t", "b"); else - c.RemoveAttribute("t"); + c.RemoveAttribute("t"); var isNode = c.SelectSingleNode("x:is", Ns); if (isNode != null)