diff --git a/samples/xlsx/TestUriMapping.xlsx b/samples/xlsx/TestUriMapping.xlsx new file mode 100644 index 0000000..1dcc72b Binary files /dev/null and b/samples/xlsx/TestUriMapping.xlsx differ diff --git a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetReader.cs b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetReader.cs index 1a56ae6..8e02ebf 100644 --- a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetReader.cs +++ b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetReader.cs @@ -322,12 +322,11 @@ public IEnumerable> Query(bool useHeaderRow, string { _mergeCells.MergesValues[aR] = cellValue; } - else if (_mergeCells.MergesMap.ContainsKey(aR)) + else if (_mergeCells.MergesMap.TryGetValue(aR, out var mergeKey)) { - var mergeKey = _mergeCells.MergesMap[aR]; object mergeValue = null; - if (_mergeCells.MergesValues.ContainsKey(mergeKey)) - mergeValue = _mergeCells.MergesValues[mergeKey]; + if (_mergeCells.MergesValues.TryGetValue(mergeKey, out var value)) + mergeValue = value; cellValue = mergeValue; } } @@ -398,9 +397,8 @@ private void SetCellsValueAndHeaders(object cellValue, bool useHeaderRow, ref Di } else { - if (headRows.ContainsKey(columnIndex)) + if (headRows.TryGetValue(columnIndex, out var key)) { - var key = headRows[columnIndex]; cell[key] = cellValue; } } @@ -467,10 +465,9 @@ private void SetCellsValueAndHeaders(object cellValue, bool useHeaderRow, ref Di { foreach (var alias in pInfo.ExcelColumnAliases) { - if (headersDic.ContainsKey(alias)) + if (headersDic.TryGetValue(alias, out var columnId)) { object newV = null; - var columnId = headersDic[alias]; var columnName = keys[columnId]; item.TryGetValue(columnName, out var itemValue); @@ -490,9 +487,8 @@ private void SetCellsValueAndHeaders(object cellValue, bool useHeaderRow, ref Di { item.TryGetValue(pInfo.ExcelIndexName, out itemValue); } - else if (headersDic.ContainsKey(pInfo.ExcelColumnName)) + else if (headersDic.TryGetValue(pInfo.ExcelColumnName, out var columnId)) { - var columnId = headersDic[pInfo.ExcelColumnName]; var columnName = keys[columnId]; item.TryGetValue(columnName, out itemValue); } @@ -822,7 +818,7 @@ public IEnumerable> QueryRange(bool useHeaderRow, st // if sheets count > 1 need to read xl/_rels/workbook.xml.rels var sheets = _archive.entries.Where(w => w.FullName.StartsWith("xl/worksheets/sheet", StringComparison.OrdinalIgnoreCase) || w.FullName.StartsWith("/xl/worksheets/sheet", StringComparison.OrdinalIgnoreCase) - ); + ).ToList(); ZipArchiveEntry sheetEntry = null; if (sheetName != null) { @@ -832,7 +828,7 @@ public IEnumerable> QueryRange(bool useHeaderRow, st throw new InvalidOperationException("Please check sheetName/Index is correct"); sheetEntry = sheets.Single(w => w.FullName == $"xl/{s.Path}" || w.FullName == $"/xl/{s.Path}" || w.FullName == s.Path || s.Path == $"/{w.FullName}"); } - else if (sheets.Count() > 1) + else if (sheets.Count > 1) { SetWorkbookRels(_archive.entries); var s = _sheetRecords[0]; @@ -1086,13 +1082,9 @@ public IEnumerable> QueryRange(bool useHeaderRow, st { _mergeCells.MergesValues[aR] = cellValue; } - else if (_mergeCells.MergesMap.ContainsKey(aR)) + else if (_mergeCells.MergesMap.TryGetValue(aR, out var mergeKey)) { - var mergeKey = _mergeCells.MergesMap[aR]; - object mergeValue = null; - if (_mergeCells.MergesValues.ContainsKey(mergeKey)) - mergeValue = _mergeCells.MergesValues[mergeKey]; - cellValue = mergeValue; + _mergeCells.MergesValues.TryGetValue(mergeKey, out cellValue); } } ////2022-09-24跳过endcell结束单元格所以在的列 @@ -1194,15 +1186,16 @@ public IEnumerable> QueryRange(bool useHeaderRow, st { foreach (var alias in pInfo.ExcelColumnAliases) { - if (headersDic.ContainsKey(alias)) + if (headersDic.TryGetValue(alias, out var value)) { object newV = null; - object itemValue = item[keys[headersDic[alias]]]; + object itemValue = item[keys[value]]; if (itemValue == null) continue; - newV = TypeHelper.TypeMapping(v, pInfo, newV, itemValue, rowIndex, startCell, configuration); + newV = TypeHelper.TypeMapping(v, pInfo, newV, itemValue, rowIndex, startCell, + configuration); } } } @@ -1213,8 +1206,8 @@ public IEnumerable> QueryRange(bool useHeaderRow, st object itemValue = null; if (pInfo.ExcelIndexName != null && keys.Contains(pInfo.ExcelIndexName)) itemValue = item[pInfo.ExcelIndexName]; - else if (headersDic.ContainsKey(pInfo.ExcelColumnName)) - itemValue = item[keys[headersDic[pInfo.ExcelColumnName]]]; + else if (headersDic.TryGetValue(pInfo.ExcelColumnName, out var value)) + itemValue = item[keys[value]]; if (itemValue == null) continue; diff --git a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.Async.cs b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.Async.cs index 9790fe5..2d16d02 100644 --- a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.Async.cs +++ b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.Async.cs @@ -325,16 +325,7 @@ private async Task WriteCellAsync(MiniExcelAsyncStreamWriter writer, string cell private async Task WriteCellAsync(MiniExcelAsyncStreamWriter writer, int rowIndex, int cellIndex, object value, ExcelColumnInfo p, ExcelWidthCollection widthCollection) { - var columnReference = ExcelOpenXmlUtils.ConvertXyToCell(cellIndex, rowIndex); - var valueIsNull = value is null || value is DBNull; - - if (_configuration.EnableWriteNullValueCell && valueIsNull) - { - await writer.WriteAsync(WorksheetXml.EmptyCell(columnReference, GetCellXfId("2"))); - return; - } - - if (p.CustomFormatter != null) + if (p?.CustomFormatter != null) { try { @@ -346,6 +337,15 @@ private async Task WriteCellAsync(MiniExcelAsyncStreamWriter writer, int rowInde } } + var columnReference = ExcelOpenXmlUtils.ConvertXyToCell(cellIndex, rowIndex); + var valueIsNull = value is null || value is DBNull; + + if (_configuration.EnableWriteNullValueCell && valueIsNull) + { + await writer.WriteAsync(WorksheetXml.EmptyCell(columnReference, GetCellXfId("2"))); + return; + } + var tuple = GetCellValue(rowIndex, cellIndex, value, p, valueIsNull); var styleIndex = tuple.Item1; diff --git a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.cs b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.cs index 96e1695..d290fe3 100644 --- a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.cs +++ b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.cs @@ -325,15 +325,6 @@ private void PrintHeader(MiniExcelStreamWriter writer, List pro private void WriteCell(MiniExcelStreamWriter writer, int rowIndex, int cellIndex, object value, ExcelColumnInfo columnInfo, ExcelWidthCollection widthCollection) { - var columnReference = ExcelOpenXmlUtils.ConvertXyToCell(cellIndex, rowIndex); - var valueIsNull = value is null || value is DBNull; - - if (_configuration.EnableWriteNullValueCell && valueIsNull) - { - writer.Write(WorksheetXml.EmptyCell(columnReference, GetCellXfId("2"))); - return; - } - if (columnInfo?.CustomFormatter != null) { try @@ -346,6 +337,15 @@ private void WriteCell(MiniExcelStreamWriter writer, int rowIndex, int cellIndex } } + var columnReference = ExcelOpenXmlUtils.ConvertXyToCell(cellIndex, rowIndex); + var valueIsNull = value is null || value is DBNull || (_configuration.WriteEmptyStringAsNull && value is String && value == string.Empty); + + if (_configuration.EnableWriteNullValueCell && valueIsNull) + { + writer.Write(WorksheetXml.EmptyCell(columnReference, GetCellXfId("2"))); + return; + } + var tuple = GetCellValue(rowIndex, cellIndex, value, columnInfo, valueIsNull); var styleIndex = tuple.Item1; // https://learn.microsoft.com/en-us/dotnet/api/documentformat.openxml.spreadsheet.cell?view=openxml-3.0.1 diff --git a/src/MiniExcel/OpenXml/OpenXmlConfiguration.cs b/src/MiniExcel/OpenXml/OpenXmlConfiguration.cs index c473903..dc157d4 100644 --- a/src/MiniExcel/OpenXml/OpenXmlConfiguration.cs +++ b/src/MiniExcel/OpenXml/OpenXmlConfiguration.cs @@ -13,6 +13,7 @@ public class OpenXmlConfiguration : Configuration public bool EnableConvertByteArray { get; set; } = true; public bool IgnoreTemplateParameterMissing { get; set; } = true; public bool EnableWriteNullValueCell { get; set; } = true; + public bool WriteEmptyStringAsNull { get; set; } = false; public bool EnableSharedStringCache { get; set; } = true; public long SharedStringCacheSize { get; set; } = 5 * 1024 * 1024; public DynamicExcelSheet[] DynamicSheets { get; set; } diff --git a/src/MiniExcel/SaveByTemplate/ExcelOpenXmlTemplate.Impl.cs b/src/MiniExcel/SaveByTemplate/ExcelOpenXmlTemplate.Impl.cs index efccca4..1a724a5 100644 --- a/src/MiniExcel/SaveByTemplate/ExcelOpenXmlTemplate.Impl.cs +++ b/src/MiniExcel/SaveByTemplate/ExcelOpenXmlTemplate.Impl.cs @@ -482,9 +482,8 @@ private void WriteSheetXml(Stream stream, XmlDocument doc, XmlNode sheetData, bo rowXml.Replace(key, ""); continue; } - if (!dic.ContainsKey(propInfo.Key)) + if (!dic.TryGetValue(propInfo.Key, out var cellValue)) continue; - var cellValue = dic[propInfo.Key]; if (cellValue == null) { rowXml.Replace(key, ""); @@ -899,9 +898,9 @@ private void ReplaceSharedStringsToStr(IDictionary sharedStrings, r if (t == "s") { //need to check sharedstring exist or not - if (sharedStrings.ContainsKey(int.Parse(v.InnerText))) + if (sharedStrings.TryGetValue(int.Parse(v.InnerText), out var shared)) { - v.InnerText = sharedStrings[int.Parse(v.InnerText)]; + v.InnerText = shared; // change type = str and replace its value c.SetAttribute("t", "str"); } @@ -935,11 +934,11 @@ private void UpdateDimensionAndGetRowsInfo(IDictionary inputMaps var r = c.GetAttribute("r"); // ==== mergecells ==== - if (this.XMergeCellInfos.ContainsKey(r)) + if (this.XMergeCellInfos.TryGetValue(r, out var merCell)) { if (xRowInfo.RowMercells == null) xRowInfo.RowMercells = new List(); - xRowInfo.RowMercells.Add(this.XMergeCellInfos[r]); + xRowInfo.RowMercells.Add(merCell); } if (changeRowIndex) @@ -962,7 +961,7 @@ private void UpdateDimensionAndGetRowsInfo(IDictionary inputMaps continue; // TODO: default if not contain property key, clean the template string - if (!inputMaps.ContainsKey(propNames[0])) + if (!inputMaps.TryGetValue(propNames[0], out var cellValue)) { if (_configuration.IgnoreTemplateParameterMissing) { @@ -975,19 +974,21 @@ private void UpdateDimensionAndGetRowsInfo(IDictionary inputMaps } } - var cellValue = inputMaps[propNames[0]]; // 1. From left to right, only the first set is used as the basis for the list - if ((cellValue is IEnumerable || cellValue is IList) && !(cellValue is string)) + //cellValue = inputMaps[propNames[0]] - 1. From left to right, only the first set is used as the basis for the list + if (cellValue is IEnumerable && !(cellValue is string)) { - if (this.XMergeCellInfos.ContainsKey(r)) + if (xRowInfo.IEnumerableMercell == null) { - if (xRowInfo.IEnumerableMercell == null) + if (this.XMergeCellInfos.TryGetValue(r, out var info)) { - xRowInfo.IEnumerableMercell = this.XMergeCellInfos[r]; + xRowInfo.IEnumerableMercell = info; } } xRowInfo.CellIEnumerableValues = cellValue as IEnumerable; - xRowInfo.CellIlListValues = cellValue as IList; + xRowInfo.CellIlListValues = cellValue is IList ? + cellValue as IList : + xRowInfo.CellIEnumerableValues.Cast().ToList(); // get ienumerable runtime type if (xRowInfo.IEnumerableGenricType == null) //avoid duplicate to add rowindexdiff ![image](https://user-images.githubusercontent.com/12729184/114851348-522ac000-9e14-11eb-8244-4730754d6885.png) @@ -1063,14 +1064,13 @@ private void UpdateDimensionAndGetRowsInfo(IDictionary inputMaps v.InnerText = v.InnerText.Replace($"{{{{{propNames[0]}}}}}", propNames[1]); break; } - if (!xRowInfo.PropsMap.ContainsKey(propNames[1])) + if (!xRowInfo.PropsMap.TryGetValue(propNames[1], out var prop)) { v.InnerText = v.InnerText.Replace($"{{{{{propNames[0]}.{propNames[1]}}}}}", ""); continue; throw new InvalidDataException($"{propNames[0]} doesn't have {propNames[1]} property"); } // auto check type https://github.com/shps951023/MiniExcel/issues/177 - var prop = xRowInfo.PropsMap[propNames[1]]; var type = prop.UnderlyingTypePropType; //avoid nullable if (isMultiMatch) @@ -1283,4 +1283,4 @@ private static bool EvaluateStatement(object tagValue, string comparisonOperator return checkStatement; } } -} \ No newline at end of file +} diff --git a/src/MiniExcel/Utils/CustomPropertyHelper.cs b/src/MiniExcel/Utils/CustomPropertyHelper.cs index f0cef4a..b4c41c8 100644 --- a/src/MiniExcel/Utils/CustomPropertyHelper.cs +++ b/src/MiniExcel/Utils/CustomPropertyHelper.cs @@ -88,12 +88,12 @@ internal static List SortCustomProps(List prop // https://github.com/shps951023/MiniExcel/issues/142 //TODO: need optimize performance - var withCustomIndexProps = props.Where(w => w.ExcelColumnIndex != null && w.ExcelColumnIndex > -1); + var withCustomIndexProps = props.Where(w => w.ExcelColumnIndex != null && w.ExcelColumnIndex > -1).ToList(); if (withCustomIndexProps.GroupBy(g => g.ExcelColumnIndex).Any(_ => _.Count() > 1)) - throw new InvalidOperationException($"Duplicate column name"); + throw new InvalidOperationException("Duplicate column name"); var maxColumnIndex = props.Count - 1; - if (withCustomIndexProps.Any()) + if (withCustomIndexProps.Count != 0) maxColumnIndex = Math.Max((int)withCustomIndexProps.Max(w => w.ExcelColumnIndex), maxColumnIndex); var withoutCustomIndexProps = props.Where(w => w.ExcelColumnIndex == null || w.ExcelColumnIndex == -1).ToList(); @@ -136,21 +136,19 @@ internal static List GetExcelCustomPropertyInfos(Type type, str if (props.Count == 0) throw new InvalidOperationException($"{type.Name} un-ignore properties count can't be 0"); + var withCustomIndexProps = props.Where(w => w.ExcelColumnIndex != null && w.ExcelColumnIndex > -1); + if (withCustomIndexProps.GroupBy(g => g.ExcelColumnIndex).Any(_ => _.Count() > 1)) + throw new InvalidOperationException("Duplicate column name"); + var maxkey = keys.Last(); + var maxIndex = ColumnHelper.GetColumnIndex(maxkey); + foreach (var p in props) { - var withCustomIndexProps = props.Where(w => w.ExcelColumnIndex != null && w.ExcelColumnIndex > -1); - if (withCustomIndexProps.GroupBy(g => g.ExcelColumnIndex).Any(_ => _.Count() > 1)) - throw new InvalidOperationException($"Duplicate column name"); - var maxkey = keys.Last(); - var maxIndex = ColumnHelper.GetColumnIndex(maxkey); - foreach (var p in props) + if (p.ExcelColumnIndex != null) { - if (p.ExcelColumnIndex != null) - { - if (p.ExcelColumnIndex > maxIndex) - throw new ArgumentException($"ExcelColumnIndex {p.ExcelColumnIndex} over haeder max index {maxkey}"); - if (p.ExcelColumnName == null) - throw new InvalidOperationException($"{p.Property.Info.DeclaringType.Name} {p.Property.Name}'s ExcelColumnIndex {p.ExcelColumnIndex} can't find excel column name"); - } + if (p.ExcelColumnIndex > maxIndex) + throw new ArgumentException($"ExcelColumnIndex {p.ExcelColumnIndex} over haeder max index {maxkey}"); + if (p.ExcelColumnName == null) + throw new InvalidOperationException($"{p.Property.Info.DeclaringType.Name} {p.Property.Name}'s ExcelColumnIndex {p.ExcelColumnIndex} can't find excel column name"); } } diff --git a/src/MiniExcel/Utils/ListHelper.cs b/src/MiniExcel/Utils/ListHelper.cs index 39074ef..2b68609 100644 --- a/src/MiniExcel/Utils/ListHelper.cs +++ b/src/MiniExcel/Utils/ListHelper.cs @@ -8,14 +8,15 @@ public static class IEnumerableHelper { public static bool StartsWith(this IList span, IList value) where T : IEquatable { - if (value.Count() == 0) + if (value.Count == 0) return true; - var b = span.Take(value.Count()); - if (b.Count() != value.Count()) + var b = span.Take(value.Count); + var bCount = b.Count(); + if (bCount != value.Count) return false; - for (int i = 0; i < b.Count(); i++) + for (int i = 0; i < bCount; i++) if (!span[i].Equals(value[i])) return false; diff --git a/src/MiniExcel/Utils/TypeHelper.cs b/src/MiniExcel/Utils/TypeHelper.cs index b37294d..394599d 100644 --- a/src/MiniExcel/Utils/TypeHelper.cs +++ b/src/MiniExcel/Utils/TypeHelper.cs @@ -151,6 +151,13 @@ public static bool IsNumericType(Type type, bool isNullableUnderlyingType = fals else newValue = Enum.Parse(pInfo.ExcludeNullableType, itemValue?.ToString(), true); } + else if (pInfo.ExcludeNullableType == typeof(Uri)) + { + var rawValue = itemValue?.ToString(); + if (!Uri.TryCreate(rawValue, UriKind.RelativeOrAbsolute, out var uri)) + throw new InvalidCastException($"Value \"{rawValue}\" cannot be converted to Uri"); + newValue = uri; + } else { // Use pInfo.ExcludeNullableType to resolve : https://github.com/shps951023/MiniExcel/issues/138 diff --git a/src/MiniExcel/WriteAdapter/EnumerableWriteAdapter.cs b/src/MiniExcel/WriteAdapter/EnumerableWriteAdapter.cs index 489d1ee..742919b 100644 --- a/src/MiniExcel/WriteAdapter/EnumerableWriteAdapter.cs +++ b/src/MiniExcel/WriteAdapter/EnumerableWriteAdapter.cs @@ -45,8 +45,16 @@ public List GetColumns() _enumerator = _values.GetEnumerator(); if (!_enumerator.MoveNext()) { - _empty = true; - return null; + try + { + _empty = true; + return null; + } + finally + { + (_enumerator as IDisposable)?.Dispose(); + _enumerator = null; + } } return CustomPropertyHelper.GetColumnInfoFromValue(_enumerator.Current, _configuration); } @@ -58,20 +66,28 @@ public IEnumerable> GetRows(List pro yield break; } - if (_enumerator is null) + try { - _enumerator = _values.GetEnumerator(); - if (!_enumerator.MoveNext()) + if (_enumerator is null) { - yield break; + _enumerator = _values.GetEnumerator(); + if (!_enumerator.MoveNext()) + { + yield break; + } } - } - do + do + { + cancellationToken.ThrowIfCancellationRequested(); + yield return GetRowValues(_enumerator.Current, props); + } while (_enumerator.MoveNext()); + } + finally { - cancellationToken.ThrowIfCancellationRequested(); - yield return GetRowValues(_enumerator.Current, props); - } while (_enumerator.MoveNext()); + (_enumerator as IDisposable)?.Dispose(); + _enumerator = null; + } } diff --git a/tests/MiniExcelTests/MiniExcelOpenXmlTests.cs b/tests/MiniExcelTests/MiniExcelOpenXmlTests.cs index ad9e0f6..74c8a5d 100644 --- a/tests/MiniExcelTests/MiniExcelOpenXmlTests.cs +++ b/tests/MiniExcelTests/MiniExcelOpenXmlTests.cs @@ -381,6 +381,27 @@ public void AutoCheckTypeTest() } } + public class ExcelUriDemo + { + public string Name { get; set; } + public int Age { get; set; } + public Uri Url { get; set; } + } + + [Fact] + public void UriMappingTest() + { + var path = "../../../../../samples/xlsx/TestUriMapping.xlsx"; + using (var stream = File.OpenRead(path)) + { + var rows = stream.Query().ToList(); + + Assert.Equal("Felix", rows[1].Name); + Assert.Equal(44, rows[1].Age); + Assert.Equal(new Uri("https://friendly-utilization.net"), rows[1].Url); + } + } + [Fact()] public void TestDatetimeSpanFormat_ClosedXml() { diff --git a/tests/MiniExcelTests/SaveByTemplate/MiniExcelTemplateTests.cs b/tests/MiniExcelTests/SaveByTemplate/MiniExcelTemplateTests.cs index 661b5b3..6407393 100644 --- a/tests/MiniExcelTests/SaveByTemplate/MiniExcelTemplateTests.cs +++ b/tests/MiniExcelTests/SaveByTemplate/MiniExcelTemplateTests.cs @@ -294,6 +294,66 @@ public void DictionaryTemplateTest() } } + private struct Employee + { + public string name { get; set; } + public string department { get; set; } + }; + + [Fact] + public void GroupTemplateTest() + { + var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid().ToString()}.xlsx"); + var templatePath = @"../../../../../samples/xlsx/TestTemplateBasicIEmumerableFillGroup.xlsx"; + var value = new + { + employees = new List() + { + new(){ name = "Jack", department="HR" }, + new(){ name = "Jack", department="IT" }, + new(){ name = "Loan", department="IT" }, + new(){ name = "Eric", department="IT" }, + new(){ name = "Eric", department="HR" }, + new(){ name = "Keaton", department="IT" }, + new(){ name = "Felix", department="HR" }, + }, + }; + MiniExcel.SaveAsByTemplate(path, templatePath, value); + + { + var rows = MiniExcel.Query(path).ToList(); + + Assert.Equal(16, rows.Count); + + Assert.Equal("Jack", rows[1].A); + Assert.Equal("Jack", rows[2].A); + Assert.Equal("HR", rows[2].B); + Assert.Equal("Jack", rows[3].A); + Assert.Equal("IT", rows[3].B); + + Assert.Equal("Loan", rows[4].A); + Assert.Equal("Loan", rows[5].A); + Assert.Equal("IT", rows[5].B); + + Assert.Equal("Eric", rows[6].A); + Assert.Equal("Eric", rows[7].A); + Assert.Equal("IT", rows[7].B); + Assert.Equal("Eric", rows[8].A); + Assert.Equal("HR", rows[8].B); + + Assert.Equal("Keaton", rows[9].A); + Assert.Equal("Keaton", rows[10].A); + Assert.Equal("IT", rows[10].B); + + Assert.Equal("Felix", rows[11].A); + Assert.Equal("Felix", rows[12].A); + Assert.Equal("HR", rows[12].B); + + var demension = Helpers.GetFirstSheetDimensionRefValue(path); + Assert.Equal("A1:B20", demension); + } + } + [Fact] public void TestGithubProject() {