From 96ce4cce485886cec149959a8f2af4e4ec146ca5 Mon Sep 17 00:00:00 2001 From: Michele Bastione Date: Mon, 16 Feb 2026 23:50:51 +0100 Subject: [PATCH 1/3] Mirrors PR 917 from the master branch --- .../ExcelOpenXmlTemplate.Impl.cs | 194 +++++++++++++++--- tests/MiniExcelTests/MiniExcelIssueTests.cs | 7 +- 2 files changed, 168 insertions(+), 33 deletions(-) diff --git a/src/MiniExcel/SaveByTemplate/ExcelOpenXmlTemplate.Impl.cs b/src/MiniExcel/SaveByTemplate/ExcelOpenXmlTemplate.Impl.cs index 7e87a156..cc6df05f 100644 --- a/src/MiniExcel/SaveByTemplate/ExcelOpenXmlTemplate.Impl.cs +++ b/src/MiniExcel/SaveByTemplate/ExcelOpenXmlTemplate.Impl.cs @@ -140,10 +140,13 @@ internal partial class ExcelOpenXmlTemplate private static readonly Regex _templateRegex = TemplateRegex(); [GeneratedRegex(@".*?\{\{.*?\}\}.*?")] private static partial Regex NonTemplateRegex(); private static readonly Regex _nonTemplateRegex = TemplateRegex(); + [GeneratedRegex(@"<(?:x:)?v>\s*")] private static partial Regex EmptyVTagRegexImpl(); + private static readonly Regex _emptyVTagRegex = EmptyVTagRegexImpl(); #else private static readonly Regex _cellRegex = new Regex("([A-Z]+)([0-9]+)", RegexOptions.Compiled); private static readonly Regex _templateRegex = new Regex(@"\{\{(.*?)\}\}", RegexOptions.Compiled); private static readonly Regex _nonTemplateRegex = new Regex(@".*?\{\{.*?\}\}.*?", RegexOptions.Compiled); + private static readonly Regex _emptyVTagRegex = new Regex(@"<(?:x:)?v>\s*", RegexOptions.Compiled); #endif private void GenerateSheetXmlImplByUpdateMode(ZipArchiveEntry sheetZipEntry, Stream stream, Stream sheetStream, IDictionary inputMaps, IDictionary sharedStrings, bool mergeCells = false) @@ -324,6 +327,15 @@ private void WriteSheetXml(Stream outputFileStream, XmlDocument doc, XmlNode she 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 != 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)) @@ -514,6 +526,15 @@ private void WriteSheetXml(Stream outputFileStream, XmlDocument doc, XmlNode she writer.Write($""); + // ECMA-376 element order: sheetData → autoFilter → mergeCells → phoneticPr → conditionalFormatting + + // 1. autoFilter (must come before mergeCells) + if (!string.IsNullOrEmpty(autoFilterXml)) + { + writer.Write(CleanXml(autoFilterXml, endPrefix)); + } + + // 2. mergeCells if (_newXMergeCellInfos.Count != 0) { writer.Write($"<{prefix}mergeCells count=\"{_newXMergeCellInfos.Count}\">"); @@ -524,14 +545,16 @@ private void WriteSheetXml(Stream outputFileStream, XmlDocument doc, XmlNode she writer.Write($""); } + // 3. PhoneticPr if (!string.IsNullOrEmpty(phoneticPrXml)) { - writer.Write(phoneticPrXml); + writer.Write(CleanXml(phoneticPrXml, endPrefix)); } + // 4. conditionalFormatting if (newConditionalFormatRanges.Count != 0) { - writer.Write(string.Join(string.Empty, newConditionalFormatRanges.Select(cf => cf.Node.OuterXml))); + writer.Write(CleanXml(string.Join(string.Empty, newConditionalFormatRanges.Select(cf => cf.Node.OuterXml)), endPrefix)); } writer.Write(contents[1]); @@ -548,12 +571,23 @@ private void GenerateCellValues(string endPrefix, StreamWriter writer, ref int r var cleanOuterXmlOpen = CleanXml(outerXmlOpen, endPrefix); // https://github.com/mini-software/MiniExcel/issues/771 Saving by template introduces unintended value replication in each row #771 - var notFirstRowElement = rowElement.Clone(); + var notFirstRowElement = rowElement.Clone(); foreach (XmlElement c in notFirstRowElement.SelectNodes("x:c", _ns)) { - var v = c.SelectSingleNode("x:v", _ns); - if (v != null && !_nonTemplateRegex.IsMatch(v.InnerText)) - v.InnerText = string.Empty; + // Try first (for t="n"/t="b" cells), then (for t="inlineStr" cells) + var vTag = c.SelectSingleNode("x:v", _ns); + if (vTag != null) + { + if (!_nonTemplateRegex.IsMatch(vTag.InnerText)) + vTag.InnerText = string.Empty; + } + else + { + // Handle inline string cells + var t = c.SelectSingleNode("x:is/x:t", _ns); + if (t != null && !_nonTemplateRegex.IsMatch(t.InnerText)) + t.InnerText = string.Empty; + } } foreach (var item in rowInfo.CellIEnumerableValues) @@ -712,6 +746,9 @@ private void GenerateCellValues(string endPrefix, StreamWriter writer, ref int r substXmlRow = rowXml.ToString(); substXmlRow = _templateRegex.Replace(substXmlRow, MatchDelegate); + + // Cleanup empty tags which defaults to invalid XML + substXmlRow = _emptyVTagRegex.Replace(substXmlRow, ""); } rowXml.Clear(); @@ -744,9 +781,14 @@ private void GenerateCellValues(string endPrefix, StreamWriter writer, ref int r 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); - writer.Write(CleanXml(rowXml, endPrefix)); + + var finalXml = CleanXml(rowXml, endPrefix).ToString(); + writer.Write(finalXml); //mergecells if (rowInfo.RowMercells == null) @@ -936,11 +978,11 @@ private void ProcessFormulas(StringBuilder rowXml, int rowIndex) continue; /* Target: - - SUM(C2:C7) - + + SUM(C2:C7) + */ - var vs = c.SelectNodes("x:v", _ns); + var vs = c.SelectNodes("x:is", _ns); foreach (XmlElement v in vs) { if (!v.InnerText.StartsWith("$=")) @@ -975,7 +1017,8 @@ private static string ConvertToDateTimeString(PropertyInfo propInfo, object cell 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) { @@ -996,14 +1039,100 @@ private static void ReplaceSharedStringsToStr(IDictionary sharedStr if (sharedStrings == 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 + // 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 = string.IsNullOrEmpty(prefix) + ? c.OwnerDocument.CreateElement("is", Config.SpreadsheetmlXmlns) + : c.OwnerDocument.CreateElement(prefix, "is", Config.SpreadsheetmlXmlns); + + var tNode = string.IsNullOrEmpty(prefix) + ? c.OwnerDocument.CreateElement("t", Config.SpreadsheetmlXmlns) + : c.OwnerDocument.CreateElement(prefix, "t", Config.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 + + // Determine the prefix used in this document (e.g., "x" for x:c, x:v, etc.) + var prefix = c.Prefix; + + 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 = string.IsNullOrEmpty(prefix) + ? c.OwnerDocument.CreateElement("is", Config.SpreadsheetmlXmlns) + : c.OwnerDocument.CreateElement(prefix, "is", Config.SpreadsheetmlXmlns); + + var tNode = string.IsNullOrEmpty(prefix) + ? c.OwnerDocument.CreateElement("t", Config.SpreadsheetmlXmlns) + : c.OwnerDocument.CreateElement(prefix, "t", Config.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 = string.IsNullOrEmpty(prefix) + ? c.OwnerDocument.CreateElement("is", Config.SpreadsheetmlXmlns) + : c.OwnerDocument.CreateElement(prefix, "is", Config.SpreadsheetmlXmlns); + + var tNode = string.IsNullOrEmpty(prefix) + ? c.OwnerDocument.CreateElement("t", Config.SpreadsheetmlXmlns) + : c.OwnerDocument.CreateElement(prefix, "t", Config.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 ?? string.Empty; + c.RemoveChild(isNode); + + var v = string.IsNullOrEmpty(prefix) + ? c.OwnerDocument.CreateElement("v", Config.SpreadsheetmlXmlns) + : c.OwnerDocument.CreateElement(prefix, "v", Config.SpreadsheetmlXmlns); + + v.InnerText = text; + c.AppendChild(v); + } + } + } + private void UpdateDimensionAndGetRowsInfo(IDictionary inputMaps, XmlDocument doc, XmlNodeList rows, bool changeRowIndex = true) { string[] refs; @@ -1053,7 +1182,7 @@ private void UpdateDimensionAndGetRowsInfo(IDictionary inputMaps 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 == null) continue; @@ -1176,19 +1305,19 @@ private void UpdateDimensionAndGetRowsInfo(IDictionary inputMaps 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; @@ -1228,19 +1357,19 @@ private void UpdateDimensionAndGetRowsInfo(IDictionary inputMaps 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 @@ -1248,16 +1377,16 @@ private void UpdateDimensionAndGetRowsInfo(IDictionary inputMaps 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) @@ -1266,6 +1395,13 @@ private void UpdateDimensionAndGetRowsInfo(IDictionary inputMaps cellValueStr = timestamp.ToString("yyyy-MM-dd HH:mm:ss"); } + if (string.IsNullOrEmpty(cellValueStr) && string.IsNullOrEmpty(c.GetAttribute("t"))) + { + SetCellType(c, "str"); + } + + // 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 } } diff --git a/tests/MiniExcelTests/MiniExcelIssueTests.cs b/tests/MiniExcelTests/MiniExcelIssueTests.cs index 0cf4a112..6140f5d6 100644 --- a/tests/MiniExcelTests/MiniExcelIssueTests.cs +++ b/tests/MiniExcelTests/MiniExcelIssueTests.cs @@ -1416,9 +1416,8 @@ public void TestIssueI4DQUN() MiniExcel.SaveAsByTemplate(path.ToString(), templatePath, value); var sheetXml = Helpers.GetZipFileContent(path.ToString(), "xl/worksheets/sheet1.xml"); - Assert.Contains("Hello & World < , > , \" , '", sheetXml); - Assert.Contains("Hello & Value < , > , \" , '", sheetXml); - } + Assert.Contains("Hello & World < , > , \" , '", sheetXml); + Assert.Contains("Hello & Value < , > , \" , '", sheetXml); } /// /// [SaveAs default theme support filter mode · Issue #190 · mini-software/MiniExcel](https://github.com/mini-software/MiniExcel/issues/190) @@ -4762,4 +4761,4 @@ public void TestIssue888_ShouldIgnoreFrame() Assert.Equal(dataInSheet, dataRead); } -} \ No newline at end of file +} From 5e0b2ee8a19a9a35b881081159168f5af0ba3446 Mon Sep 17 00:00:00 2001 From: Michele Bastione Date: Mon, 16 Feb 2026 23:21:23 +0100 Subject: [PATCH 2/3] Mirrors PR 923 from the master branch Fixes issue 914 --- src/MiniExcel/Csv/CsvReader.cs | 3 +++ tests/MiniExcelTests/MiniExcelIssueTests.cs | 29 +++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/src/MiniExcel/Csv/CsvReader.cs b/src/MiniExcel/Csv/CsvReader.cs index 39562333..36ce18e5 100644 --- a/src/MiniExcel/Csv/CsvReader.cs +++ b/src/MiniExcel/Csv/CsvReader.cs @@ -37,6 +37,9 @@ public IEnumerable> Query(bool useHeaderRow, string string row; for (var rowIndex = 1; (row = reader.ReadLine()) != null; rowIndex++) { + if (string.IsNullOrWhiteSpace(row)) + continue; + string finalRow = row; if (_config.ReadLineBreaksWithinQuotes) { diff --git a/tests/MiniExcelTests/MiniExcelIssueTests.cs b/tests/MiniExcelTests/MiniExcelIssueTests.cs index 6140f5d6..9a7bb92f 100644 --- a/tests/MiniExcelTests/MiniExcelIssueTests.cs +++ b/tests/MiniExcelTests/MiniExcelIssueTests.cs @@ -4761,4 +4761,33 @@ public void TestIssue888_ShouldIgnoreFrame() Assert.Equal(dataInSheet, dataRead); } + + class NameAgeTuple + { + public string? Name { get; set; } + public int Age { get; set; } + } + + [Fact] + public void Issue914() + { + var csv = + """ + Name,Age + Jack,22 + + + Sam,33 + + Henry,44 + + """u8; + + using var ms = new MemoryStream([..csv]); + var result = ms.Query(excelType: ExcelType.CSV).ToList(); + + Assert.Equal(3, result.Count); + Assert.Equal("Sam", result[1].Name); + Assert.Equal(44, result[2].Age); + } } From 7bed795bb5179aab18348716ed988c8dc7c63712 Mon Sep 17 00:00:00 2001 From: Michele Bastione Date: Mon, 16 Feb 2026 23:40:44 +0100 Subject: [PATCH 3/3] Mirrors PR 924 from the master branch Fixes issue 915 --- samples/xlsx/TestIssue915.xlsx | Bin 0 -> 6209 bytes .../ExcelOpenXmlTemplate.Impl.cs | 2 +- tests/MiniExcelTests/MiniExcelIssueTests.cs | 24 ++++++++++++++++++ 3 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 samples/xlsx/TestIssue915.xlsx diff --git a/samples/xlsx/TestIssue915.xlsx b/samples/xlsx/TestIssue915.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..476fd0efb7d5a587783cf1ffe69a0a175957ca97 GIT binary patch literal 6209 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 AZ2$lO literal 0 HcmV?d00001 diff --git a/src/MiniExcel/SaveByTemplate/ExcelOpenXmlTemplate.Impl.cs b/src/MiniExcel/SaveByTemplate/ExcelOpenXmlTemplate.Impl.cs index cc6df05f..dc6da1d7 100644 --- a/src/MiniExcel/SaveByTemplate/ExcelOpenXmlTemplate.Impl.cs +++ b/src/MiniExcel/SaveByTemplate/ExcelOpenXmlTemplate.Impl.cs @@ -728,7 +728,7 @@ private void GenerateCellValues(string endPrefix, StreamWriter writer, ref int r else { cellValueStr = ExcelOpenXmlUtils.EncodeXML(cellValue?.ToString()); - if (!isDictOrTable && TypeHelper.IsNumericType(type)) + if (TypeHelper.IsNumericType(type)) { if (decimal.TryParse(cellValueStr, out var decimalValue)) cellValueStr = decimalValue.ToString(CultureInfo.InvariantCulture); diff --git a/tests/MiniExcelTests/MiniExcelIssueTests.cs b/tests/MiniExcelTests/MiniExcelIssueTests.cs index 9a7bb92f..fe5a0922 100644 --- a/tests/MiniExcelTests/MiniExcelIssueTests.cs +++ b/tests/MiniExcelTests/MiniExcelIssueTests.cs @@ -4790,4 +4790,28 @@ public void Issue914() Assert.Equal("Sam", result[1].Name); Assert.Equal(44, result[2].Age); } + + [Fact] + public void TestIssue915() + { + var templatePath = PathHelper.GetFile("xlsx/TestIssue915.xlsx"); + var value = new Dictionary + { + ["Data"] = new[] + { + new { Name = "Hill", Altitude = 6m }, + new { Name = "Mount", Altitude = 7.4m }, + new { Name = "Peak", Altitude = 8.6m } + } + }; + + using var path = AutoDeletingPath.Create(); + MiniExcel.SaveAsByTemplate(path.ToString(), templatePath, value); + + var result = MiniExcel.Query(path.ToString(), true).ToList(); + + Assert.Equal(6, result[0].Altitude); + Assert.Equal(7.4, result[1].Altitude); + Assert.Equal(8.6, result[2].Altitude); + } }