diff --git a/samples/xlsx/TestIssue686.xlsx b/samples/xlsx/TestIssue686.xlsx new file mode 100644 index 00000000..5f54ba24 Binary files /dev/null and b/samples/xlsx/TestIssue686.xlsx differ diff --git a/samples/xlsx/TestIssue697.xlsx b/samples/xlsx/TestIssue697.xlsx new file mode 100644 index 00000000..b025228f Binary files /dev/null and b/samples/xlsx/TestIssue697.xlsx differ diff --git a/samples/xlsx/TestTrimColumnNames.xlsx b/samples/xlsx/TestTrimColumnNames.xlsx new file mode 100644 index 00000000..d013200a Binary files /dev/null and b/samples/xlsx/TestTrimColumnNames.xlsx differ diff --git a/src/MiniExcel/ExcelFactory.cs b/src/MiniExcel/ExcelFactory.cs index d63998b7..b9a64711 100644 --- a/src/MiniExcel/ExcelFactory.cs +++ b/src/MiniExcel/ExcelFactory.cs @@ -6,7 +6,7 @@ using System; using System.IO; - internal class ExcelReaderFactory + internal static class ExcelReaderFactory { internal static IExcelReader GetProvider(Stream stream, ExcelType excelType, IConfiguration configuration) { @@ -17,19 +17,21 @@ internal static IExcelReader GetProvider(Stream stream, ExcelType excelType, ICo case ExcelType.XLSX: return new ExcelOpenXmlSheetReader(stream, configuration); default: - throw new NotSupportedException($"Please Issue for me"); + throw new NotSupportedException("Please Issue for me"); } } } - internal class ExcelWriterFactory + internal static class ExcelWriterFactory { internal static IExcelWriter GetProvider(Stream stream, object value, string sheetName, ExcelType excelType, IConfiguration configuration, bool printHeader) { if (string.IsNullOrEmpty(sheetName)) - throw new InvalidDataException("Sheet name can not be empty or null"); + throw new ArgumentException("Sheet names can not be empty or null", nameof(sheetName)); + if (sheetName.Length > 31 && excelType == ExcelType.XLSX) + throw new ArgumentException("Sheet names must be less than 31 characters", nameof(sheetName)); if (excelType == ExcelType.UNKNOWN) - throw new InvalidDataException("Please specify excelType"); + throw new ArgumentException("Excel type cannot be ExcelType.UNKNOWN", nameof(excelType)); switch (excelType) { @@ -38,12 +40,12 @@ internal static IExcelWriter GetProvider(Stream stream, object value, string she case ExcelType.XLSX: return new ExcelOpenXmlSheetWriter(stream, value, sheetName, configuration, printHeader); default: - throw new NotSupportedException($"Please Issue for me"); + throw new NotSupportedException($"The {excelType} Excel format is not supported"); } } } - internal class ExcelTemplateFactory + internal static class ExcelTemplateFactory { internal static IExcelTemplateAsync GetProvider(Stream stream, IConfiguration configuration, ExcelType excelType = ExcelType.XLSX) { @@ -53,7 +55,7 @@ internal static IExcelTemplateAsync GetProvider(Stream stream, IConfiguration co var valueExtractor = new InputValueExtractor(); return new ExcelOpenXmlTemplate(stream, configuration, valueExtractor); default: - throw new NotSupportedException($"Please Issue for me"); + throw new NotSupportedException("Please Issue for me"); } } } diff --git a/src/MiniExcel/MiniExcel.cs b/src/MiniExcel/MiniExcel.cs index 818026b2..844fb215 100644 --- a/src/MiniExcel/MiniExcel.cs +++ b/src/MiniExcel/MiniExcel.cs @@ -50,10 +50,9 @@ public static int Insert(string path, object value, string sheetName = "Sheet1", public static int Insert(this Stream stream, object value, string sheetName = "Sheet1", ExcelType excelType = ExcelType.XLSX, IConfiguration configuration = null, bool printHeader = true, bool overwriteSheet = false) { stream.Seek(0, SeekOrigin.End); - // reuse code if (excelType == ExcelType.CSV) { - var newValue = value is IEnumerable || value is IDataReader ? value : new[]{value}.AsEnumerable(); + var newValue = value is IEnumerable || value is IDataReader ? value : new[]{value}; return ExcelWriterFactory.GetProvider(stream, newValue, sheetName, excelType, configuration, false).Insert(overwriteSheet); } else @@ -88,9 +87,7 @@ public static int[] SaveAs(this Stream stream, object value, bool printHeader = { using (var excelReader = ExcelReaderFactory.GetProvider(stream, ExcelTypeHelper.GetExcelType(stream, excelType), configuration)) foreach (var item in excelReader.Query(sheetName, startCell)) - { yield return item; - } } public static IEnumerable Query(string path, bool useHeaderRow = false, string sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IConfiguration configuration = null) { diff --git a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetReader.cs b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetReader.cs index 3ddd6c9b..8e18aeab 100644 --- a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetReader.cs +++ b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetReader.cs @@ -47,26 +47,30 @@ public IEnumerable> Query(bool useHeaderRow, string startRowIndex--; // 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) - ).ToArray(); + var sheets = _archive.entries + .Where(w => w.FullName.StartsWith("xl/worksheets/sheet", StringComparison.OrdinalIgnoreCase) || + w.FullName.StartsWith("/xl/worksheets/sheet", StringComparison.OrdinalIgnoreCase) + ).ToArray(); ZipArchiveEntry sheetEntry = null; if (sheetName != null) { SetWorkbookRels(_archive.entries); - var sheetRecord = _sheetRecords.SingleOrDefault(_ => _.Name == sheetName); + var sheetRecord = _sheetRecords.SingleOrDefault(s => s.Name == sheetName); if (sheetRecord == null && _config.DynamicSheets != null) { var sheetConfig = _config.DynamicSheets.FirstOrDefault(ds => ds.Key == sheetName); if (sheetConfig != null) { - sheetRecord = _sheetRecords.SingleOrDefault(_ => _.Name == sheetConfig.Name); + sheetRecord = _sheetRecords.SingleOrDefault(s => s.Name == sheetConfig.Name); } } if (sheetRecord == null) throw new InvalidOperationException("Please check sheetName/Index is correct"); - sheetEntry = sheets.Single(w => w.FullName == $"xl/{sheetRecord.Path}" || w.FullName == $"/xl/{sheetRecord.Path}" || w.FullName == sheetRecord.Path || sheetRecord.Path == $"/{w.FullName}"); + sheetEntry = sheets.Single(w => w.FullName == $"xl/{sheetRecord.Path}" + || w.FullName == $"/xl/{sheetRecord.Path}" + || w.FullName == sheetRecord.Path + || $"/{w.FullName}" == sheetRecord.Path); } else if (sheets.Length > 1) { @@ -78,7 +82,9 @@ public IEnumerable> Query(bool useHeaderRow, string // fixed by argo@live.ca // s.Path = "/xl/sheets/sheet1.xml" s.FullName = "/xl/sheets/sheet1.xml" - sheetEntry = sheets.Single(w => w.FullName == $"xl/{s.Path}" || w.FullName == $"/xl/{s.Path}" || w.FullName.TrimStart('/') == s.Path.TrimStart('/')); + sheetEntry = sheets.Single(w => w.FullName == $"xl/{s.Path}" + || w.FullName == $"/xl/{s.Path}" + || w.FullName.TrimStart('/') == s.Path.TrimStart('/')); //#endif } else @@ -96,42 +102,42 @@ public IEnumerable> Query(bool useHeaderRow, string yield break; while (reader.Read()) { - if (XmlReaderHelper.IsStartElement(reader, "mergeCells", _ns)) + if (!XmlReaderHelper.IsStartElement(reader, "mergeCells", _ns)) + continue; + if (!XmlReaderHelper.ReadFirstContent(reader)) + yield break; + + while (!reader.EOF) { - if (!XmlReaderHelper.ReadFirstContent(reader)) - yield break; - while (!reader.EOF) + if (XmlReaderHelper.IsStartElement(reader, "mergeCell", _ns)) { - if (XmlReaderHelper.IsStartElement(reader, "mergeCell", _ns)) - { - var @ref = reader.GetAttribute("ref"); - var refs = @ref.Split(':'); - if (refs.Length == 1) - continue; + var refAttr = reader.GetAttribute("ref"); + var refs = refAttr.Split(':'); + if (refs.Length == 1) + continue; - ReferenceHelper.ParseReference(refs[0], out var x1, out var y1); - ReferenceHelper.ParseReference(refs[1], out var x2, out var y2); + ReferenceHelper.ParseReference(refs[0], out var x1, out var y1); + ReferenceHelper.ParseReference(refs[1], out var x2, out var y2); - _mergeCells.MergesValues.Add(refs[0], null); + _mergeCells.MergesValues.Add(refs[0], null); - // foreach range - var isFirst = true; - for (int x = x1; x <= x2; x++) + // foreach range + var isFirst = true; + for (int x = x1; x <= x2; x++) + { + for (int y = y1; y <= y2; y++) { - for (int y = y1; y <= y2; y++) - { - if (!isFirst) - _mergeCells.MergesMap.Add(ReferenceHelper.ConvertXyToCell(x, y), refs[0]); - isFirst = false; - } + if (!isFirst) + _mergeCells.MergesMap.Add(ReferenceHelper.ConvertXyToCell(x, y), refs[0]); + isFirst = false; } - - XmlReaderHelper.SkipContent(reader); - } - else if (!XmlReaderHelper.SkipContent(reader)) - { - break; } + + XmlReaderHelper.SkipContent(reader); + } + else if (!XmlReaderHelper.SkipContent(reader)) + { + break; } } } @@ -173,19 +179,19 @@ public IEnumerable> Query(bool useHeaderRow, string //this method logic depends on dimension to get maxcolumnIndex, if without dimension then it need to foreach all rows first time to get maxColumn and maxRowColumn else if (XmlReaderHelper.IsStartElement(reader, "dimension", _ns)) { - var @ref = reader.GetAttribute("ref"); - if (string.IsNullOrEmpty(@ref)) + var refAttr = reader.GetAttribute("ref"); + if (string.IsNullOrEmpty(refAttr)) throw new InvalidOperationException("Without sheet dimension data"); - var rs = @ref.Split(':'); + + var rs = refAttr.Split(':'); + // issue : https://github.com/shps951023/MiniExcel/issues/102 - if (ReferenceHelper.ParseReference(rs.Length == 2 ? rs[1] : rs[0], out int cIndex, out int rIndex)) - { - maxColumnIndex = cIndex - 1; - maxRowIndex = rIndex - 1; - break; - } - else + if (!ReferenceHelper.ParseReference(rs.Length == 2 ? rs[1] : rs[0], out int cIndex, out int rIndex)) throw new InvalidOperationException("Invaild sheet dimension start data"); + + maxColumnIndex = cIndex - 1; + maxRowIndex = rIndex - 1; + break; } } } @@ -291,10 +297,10 @@ public IEnumerable> Query(bool useHeaderRow, string } // fill empty rows - var expectedRowIndex = isFirstRow ? startRowIndex : nextRowIndex; - if (!(expectedRowIndex < startRowIndex)) + if (!_config.IgnoreEmptyRows) { - if (expectedRowIndex < rowIndex) + var expectedRowIndex = isFirstRow ? startRowIndex : nextRowIndex; + if (startRowIndex <= expectedRowIndex && expectedRowIndex < rowIndex) { for (int i = expectedRowIndex; i < rowIndex; i++) { @@ -302,69 +308,68 @@ public IEnumerable> Query(bool useHeaderRow, string } } } - - // Set Cells + + #region Set Cells + var cell = GetCell(useHeaderRow, maxColumnIndex, headRows, startColumnIndex); + var columnIndex = withoutCR ? -1 : 0; + + while (!reader.EOF) { - var cell = GetCell(useHeaderRow, maxColumnIndex, headRows, startColumnIndex); - var columnIndex = withoutCR ? -1 : 0; - while (!reader.EOF) + if (XmlReaderHelper.IsStartElement(reader, "c", _ns)) { - if (XmlReaderHelper.IsStartElement(reader, "c", _ns)) - { - var aS = reader.GetAttribute("s"); - var aR = reader.GetAttribute("r"); - var aT = reader.GetAttribute("t"); - var cellValue = ReadCellAndSetColumnIndex(reader, ref columnIndex, withoutCR, startColumnIndex, aR, aT); + var aS = reader.GetAttribute("s"); + var aR = reader.GetAttribute("r"); + var aT = reader.GetAttribute("t"); + var cellValue = ReadCellAndSetColumnIndex(reader, ref columnIndex, withoutCR, startColumnIndex, aR, aT); - if (_config.FillMergedCells) + if (_config.FillMergedCells) + { + if (_mergeCells.MergesValues.ContainsKey(aR)) { - if (_mergeCells.MergesValues.ContainsKey(aR)) - { - _mergeCells.MergesValues[aR] = cellValue; - } - else if (_mergeCells.MergesMap.TryGetValue(aR, out var mergeKey)) - { - object mergeValue = null; - if (_mergeCells.MergesValues.TryGetValue(mergeKey, out var value)) - mergeValue = value; - cellValue = mergeValue; - } + _mergeCells.MergesValues[aR] = cellValue; } + else if (_mergeCells.MergesMap.TryGetValue(aR, out var mergeKey)) + { + object mergeValue = null; + if (_mergeCells.MergesValues.TryGetValue(mergeKey, out var value)) + mergeValue = value; + cellValue = mergeValue; + } + } - if (columnIndex < startColumnIndex) - continue; + if (columnIndex < startColumnIndex) + continue; - if (!string.IsNullOrEmpty(aS)) // if c with s meaning is custom style need to check type by xl/style.xml - { - int xfIndex = -1; - if (int.TryParse(aS, NumberStyles.Any, CultureInfo.InvariantCulture, out var styleIndex)) - xfIndex = styleIndex; + // if c with s meaning is custom style need to check type by xl/style.xml + if (!string.IsNullOrEmpty(aS)) + { + int xfIndex = -1; + if (int.TryParse(aS, NumberStyles.Any, CultureInfo.InvariantCulture, out var styleIndex)) + xfIndex = styleIndex; - // only when have s attribute then load styles xml data - if (_style == null) - _style = new ExcelOpenXmlStyles(_archive); + // only when have s attribute then load styles xml data + if (_style == null) + _style = new ExcelOpenXmlStyles(_archive); - cellValue = _style.ConvertValueByStyleFormat(xfIndex, cellValue); - SetCellsValueAndHeaders(cellValue, useHeaderRow, ref headRows, ref isFirstRow, ref cell, columnIndex); - } - else - { - SetCellsValueAndHeaders(cellValue, useHeaderRow, ref headRows, ref isFirstRow, ref cell, columnIndex); - } + cellValue = _style.ConvertValueByStyleFormat(xfIndex, cellValue); } - else if (!XmlReaderHelper.SkipContent(reader)) - break; + SetCellsValueAndHeaders(cellValue, useHeaderRow, ref headRows, ref isFirstRow, ref cell, columnIndex); } - - if (isFirstRow) + else if (!XmlReaderHelper.SkipContent(reader)) { - isFirstRow = false; // for startcell logic - if (useHeaderRow) - continue; + break; } + } + #endregion - yield return cell; + if (isFirstRow) + { + isFirstRow = false; // for startcell logic + if (useHeaderRow) + continue; } + + yield return cell; } else if (!XmlReaderHelper.SkipContent(reader)) { @@ -380,79 +385,59 @@ public IEnumerable> Query(bool useHeaderRow, string } } - private IDictionary GetCell(bool useHeaderRow, int maxColumnIndex, Dictionary headRows, int startColumnIndex) + private static IDictionary GetCell(bool useHeaderRow, int maxColumnIndex, Dictionary headRows, int startColumnIndex) { return useHeaderRow ? CustomPropertyHelper.GetEmptyExpandoObject(headRows) : CustomPropertyHelper.GetEmptyExpandoObject(maxColumnIndex, startColumnIndex); } - private void SetCellsValueAndHeaders(object cellValue, bool useHeaderRow, ref Dictionary headRows, ref bool isFirstRow, ref IDictionary cell, int columnIndex) + private static void SetCellsValueAndHeaders(object cellValue, bool useHeaderRow, ref Dictionary headRows, ref bool isFirstRow, ref IDictionary cell, int columnIndex) { - if (useHeaderRow) - { - if (isFirstRow) // for startcell logic - { - var cellValueString = cellValue?.ToString(); - if (!string.IsNullOrWhiteSpace(cellValueString)) - headRows.Add(columnIndex, cellValueString); - } - else - { - if (headRows.TryGetValue(columnIndex, out var key)) - { - cell[key] = cellValue; - } - } - } - else + if (!useHeaderRow) { //if not using First Head then using A,B,C as index cell[ColumnHelper.GetAlphabetColumnName(columnIndex)] = cellValue; + return; + } + + if (isFirstRow) // for startcell logic + { + var cellValueString = cellValue?.ToString(); + if (!string.IsNullOrWhiteSpace(cellValueString)) + headRows.Add(columnIndex, cellValueString); + } + else if (headRows.TryGetValue(columnIndex, out var key)) + { + cell[key] = cellValue; } } public IEnumerable Query(string sheetName, string startCell) where T : class, new() { - if (sheetName == null) - { - var sheetInfo = CustomPropertyHelper.GetExcellSheetInfo(typeof(T), this._config); - if (sheetInfo != null) - { - sheetName = sheetInfo.ExcelSheetName; - } - } - return ExcelOpenXmlSheetReader.QueryImpl(Query(false, sheetName, startCell), startCell, this._config); + if (sheetName == null) + sheetName = CustomPropertyHelper.GetExcellSheetInfo(typeof(T), _config)?.ExcelSheetName; + + return QueryImpl(Query(false, sheetName, startCell), startCell, _config); } public static IEnumerable QueryImpl(IEnumerable> values, string startCell, Configuration configuration) where T : class, new() { var type = typeof(T); - List props = null; //TODO:need to optimize - - string[] headers = null; - + List props = null; Dictionary headersDic = null; string[] keys = null; var first = true; var rowIndex = 0; + foreach (var item in values) { if (first) { - keys = item.Keys.ToArray();//.Select((s, i) => new { s,i}).ToDictionary(_=>_.s,_=>_.i); - headers = item?.Values?.Select(s => s?.ToString())?.ToArray(); //TODO:remove - headersDic = headers.Select((o, i) => new { o = (o == null ? "" : o), i }) - .OrderBy(x => x.i) - .GroupBy(x => x.o) - .Select(group => new { Group = group, Count = group.Count() }) - .SelectMany(groupWithCount => - groupWithCount.Group.Select(b => b) - .Zip( - Enumerable.Range(1, groupWithCount.Count), - (j, i) => new { key = (i == 1 ? j.o : $"{j.o}_____{i}"), idx = j.i, RowNumber = i } - ) - ).ToDictionary(_ => _.key, _ => _.idx); + keys = item.Keys.ToArray(); + var trimColumnNames = (configuration as OpenXmlConfiguration)?.TrimColumnNames ?? false; + headersDic = CustomPropertyHelper.GetHeaders(item, trimColumnNames); + //TODO: alert don't duplicate column name props = CustomPropertyHelper.GetExcelCustomPropertyInfos(type, keys, configuration); first = false; @@ -465,17 +450,17 @@ private void SetCellsValueAndHeaders(object cellValue, bool useHeaderRow, ref Di { foreach (var alias in pInfo.ExcelColumnAliases) { - if (headersDic.TryGetValue(alias, out var columnId)) - { - object newV = null; - var columnName = keys[columnId]; - item.TryGetValue(columnName, out var itemValue); + if (!headersDic.TryGetValue(alias, out var columnId)) + continue; + + object newV = null; + var columnName = keys[columnId]; + item.TryGetValue(columnName, out var itemValue); - if (itemValue == null) - continue; + if (itemValue == null) + continue; - newV = TypeHelper.TypeMapping(v, pInfo, newV, itemValue, rowIndex, startCell, configuration); - } + newV = TypeHelper.TypeMapping(v, pInfo, newV, itemValue, rowIndex, startCell, configuration); } } @@ -503,6 +488,7 @@ private void SetCellsValueAndHeaders(object cellValue, bool useHeaderRow, ref Di yield return v; } } + private void SetSharedStrings() { if (_sharedStrings != null) @@ -519,10 +505,9 @@ private void SetSharedStrings() foreach (var sharedString in XmlReaderHelper.GetSharedStrings(stream, _ns)) _sharedStrings[idx++] = sharedString; } - else + else if (_sharedStrings == null) { - if (_sharedStrings == null) - _sharedStrings = XmlReaderHelper.GetSharedStrings(stream, _ns).ToDictionary((x) => idx++, x => x); + _sharedStrings = XmlReaderHelper.GetSharedStrings(stream, _ns).ToDictionary((x) => idx++, x => x); } } } @@ -776,9 +761,9 @@ private void ConvertCellValue(string rawValue, string aT, int xfIndex, out objec } } - public async Task>> QueryAsync(bool UseHeaderRow, string sheetName, string startCell, CancellationToken cancellationToken = default(CancellationToken)) + public async Task>> QueryAsync(bool useHeaderRow, string sheetName, string startCell, CancellationToken cancellationToken = default(CancellationToken)) { - return await Task.Run(() => Query(UseHeaderRow, sheetName, startCell), cancellationToken).ConfigureAwait(false); + return await Task.Run(() => Query(useHeaderRow, sheetName, startCell), cancellationToken).ConfigureAwait(false); } public async Task> QueryAsync(string sheetName, string startCell, CancellationToken cancellationToken = default(CancellationToken)) where T : class, new() @@ -818,7 +803,7 @@ protected virtual void Dispose(bool disposing) public IEnumerable> QueryRange(bool useHeaderRow, string sheetName, string startCell, string endCell) { - if (!ReferenceHelper.ParseReference(startCell, out var startColumnIndex, out var startRowIndex) == false ? true : true) + if (ReferenceHelper.ParseReference(startCell, out var startColumnIndex, out var startRowIndex)) { //throw new InvalidDataException($"startCell {startCell} is Invalid"); startColumnIndex--; @@ -834,7 +819,7 @@ public IEnumerable> QueryRange(bool useHeaderRow, st } //2022-09-24 获取结束单元格的,行,列 - if (!ReferenceHelper.ParseReference(endCell, out var endColumnIndex, out var endRowIndex) == false ? true : true) + if (ReferenceHelper.ParseReference(endCell, out var endColumnIndex, out var endRowIndex)) { //throw new InvalidDataException($"endCell {endCell} is Invalid"); endColumnIndex--; @@ -849,10 +834,10 @@ public IEnumerable> QueryRange(bool useHeaderRow, st if (sheetName != null) { SetWorkbookRels(_archive.entries); - var s = _sheetRecords.SingleOrDefault(_ => _.Name == sheetName); - if (s == null) + var sheet = _sheetRecords.SingleOrDefault(s => s.Name == sheetName); + if (sheet == null) 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}"); + sheetEntry = sheets.Single(w => w.FullName == $"xl/{sheet.Path}" || w.FullName == $"/xl/{sheet.Path}" || w.FullName == sheet.Path || sheet.Path == $"/{w.FullName}"); } else if (sheets.Count > 1) { @@ -883,8 +868,8 @@ public IEnumerable> QueryRange(bool useHeaderRow, st { if (XmlReaderHelper.IsStartElement(reader, "mergeCell", _ns)) { - var @ref = reader.GetAttribute("ref"); - var refs = @ref.Split(':'); + var refAttr = reader.GetAttribute("ref"); + var refs = refAttr.Split(':'); if (refs.Length == 1) continue; @@ -954,24 +939,22 @@ public IEnumerable> QueryRange(bool useHeaderRow, st { //2022-09-24 Range //var @ref = reader.GetAttribute("ref"); - var @ref = startCell + ":" + endCell; + var refAttr = $"{startCell}:{endCell}"; if (endCell == "" || startCell == "") { - @ref = reader.GetAttribute("ref"); + refAttr = reader.GetAttribute("ref"); } - if (string.IsNullOrEmpty(@ref)) + if (string.IsNullOrEmpty(refAttr)) throw new InvalidOperationException("Without sheet dimension data"); - var rs = @ref.Split(':'); + var rs = refAttr.Split(':'); // issue : https://github.com/shps951023/MiniExcel/issues/102 - if (ReferenceHelper.ParseReference(rs.Length == 2 ? rs[1] : rs[0], out int cIndex, out int rIndex) == false ? true : true) - { - maxColumnIndex = cIndex - 1; - maxRowIndex = rIndex - 1; - break; - } - else + if (!ReferenceHelper.ParseReference(rs.Length == 2 ? rs[1] : rs[0], out int cIndex, out int rIndex)) throw new InvalidOperationException("Invaild sheet dimension start data"); + + maxColumnIndex = cIndex - 1; + maxRowIndex = rIndex - 1; + break; } } } @@ -1130,12 +1113,8 @@ public IEnumerable> QueryRange(bool useHeaderRow, st _style = new ExcelOpenXmlStyles(_archive); cellValue = _style.ConvertValueByStyleFormat(xfIndex, cellValue); - SetCellsValueAndHeaders(cellValue, useHeaderRow, ref headRows, ref isFirstRow, ref cell, columnIndex); - } - else - { - SetCellsValueAndHeaders(cellValue, useHeaderRow, ref headRows, ref isFirstRow, ref cell, columnIndex); } + SetCellsValueAndHeaders(cellValue, useHeaderRow, ref headRows, ref isFirstRow, ref cell, columnIndex); } else if (!XmlReaderHelper.SkipContent(reader)) break; @@ -1167,7 +1146,7 @@ public IEnumerable> QueryRange(bool useHeaderRow, st public IEnumerable QueryRange(string sheetName, string startCell, string endCell) where T : class, new() { - return ExcelOpenXmlSheetReader.QueryImplRange(QueryRange(false, sheetName, startCell, endCell), startCell, endCell, this._config); + return QueryImplRange(QueryRange(false, sheetName, startCell, endCell), startCell, endCell, this._config); } public static IEnumerable QueryImplRange(IEnumerable> values, string startCell, string endCell, Configuration configuration) where T : class, new() @@ -1177,29 +1156,19 @@ public IEnumerable> QueryRange(bool useHeaderRow, st List props = null; //TODO:need to optimize - string[] headers = null; - Dictionary headersDic = null; string[] keys = null; var first = true; var rowIndex = 0; + foreach (var item in values) { if (first) { keys = item.Keys.ToArray();//.Select((s, i) => new { s,i}).ToDictionary(_=>_.s,_=>_.i); - headers = item?.Values?.Select(s => s?.ToString())?.ToArray(); //TODO:remove - headersDic = headers.Select((o, i) => new { o = (o == null ? string.Empty : o), i }) - .OrderBy(x => x.i) - .GroupBy(x => x.o) - .Select(group => new { Group = group, Count = group.Count() }) - .SelectMany(groupWithCount => - groupWithCount.Group.Select(b => b) - .Zip( - Enumerable.Range(1, groupWithCount.Count), - (j, i) => new { key = (i == 1 ? j.o : $"{j.o}_____{i}"), idx = j.i, RowNumber = i } - ) - ).ToDictionary(_ => _.key, _ => _.idx); + var trimColumnNames = (configuration as OpenXmlConfiguration)?.TrimColumnNames ?? false; + headersDic = CustomPropertyHelper.GetHeaders(item, trimColumnNames); + //TODO: alert don't duplicate column name props = CustomPropertyHelper.GetExcelCustomPropertyInfos(type, keys, configuration); first = false; @@ -1220,8 +1189,7 @@ public IEnumerable> QueryRange(bool useHeaderRow, st if (itemValue == null) continue; - newV = TypeHelper.TypeMapping(v, pInfo, newV, itemValue, rowIndex, startCell, - configuration); + newV = TypeHelper.TypeMapping(v, pInfo, newV, itemValue, rowIndex, startCell, configuration); } } } @@ -1246,9 +1214,9 @@ public IEnumerable> QueryRange(bool useHeaderRow, st } } - public async Task>> QueryAsyncRange(bool UseHeaderRow, string sheetName, string startCell, string endCell, CancellationToken cancellationToken = default(CancellationToken)) + public async Task>> QueryAsyncRange(bool useHeaderRow, string sheetName, string startCell, string endCell, CancellationToken cancellationToken = default(CancellationToken)) { - return await Task.Run(() => Query(UseHeaderRow, sheetName, startCell), cancellationToken).ConfigureAwait(false); + return await Task.Run(() => Query(useHeaderRow, sheetName, startCell), cancellationToken).ConfigureAwait(false); } public async Task> QueryAsyncRange(string sheetName, string startCell, string endCell, CancellationToken cancellationToken = default(CancellationToken)) where T : class, new() diff --git a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.DefaultOpenXml.cs b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.DefaultOpenXml.cs index d90d3af3..d9904034 100644 --- a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.DefaultOpenXml.cs +++ b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.DefaultOpenXml.cs @@ -308,7 +308,7 @@ private string GetFileValue(int rowIndex, int cellIndex, object value) private Tuple GetDateTimeValue(DateTime value, ExcelColumnInfo columnInfo) { string cellValue = null; - if (!_configuration.Culture.Equals(CultureInfo.InvariantCulture)) + if (!ReferenceEquals(_configuration.Culture, CultureInfo.InvariantCulture)) { cellValue = value.ToString(_configuration.Culture); return Tuple.Create("2", "str", cellValue); @@ -316,11 +316,9 @@ private Tuple GetDateTimeValue(DateTime value, ExcelColu var oaDate = CorrectDateTimeValue(value); cellValue = oaDate.ToString(CultureInfo.InvariantCulture); - - if (columnInfo?.ExcelFormat != null) - return Tuple.Create(columnInfo.ExcelFormatId.ToString(), (string)null, cellValue); - - return Tuple.Create("3", (string)null, cellValue); + var format = columnInfo?.ExcelFormat != null ? columnInfo.ExcelFormatId.ToString() : "3"; + + return Tuple.Create(format, (string)null, cellValue); } private static double CorrectDateTimeValue(DateTime value) diff --git a/src/MiniExcel/OpenXml/OpenXmlConfiguration.cs b/src/MiniExcel/OpenXml/OpenXmlConfiguration.cs index dc157d49..be111b07 100644 --- a/src/MiniExcel/OpenXml/OpenXmlConfiguration.cs +++ b/src/MiniExcel/OpenXml/OpenXmlConfiguration.cs @@ -14,6 +14,8 @@ public class OpenXmlConfiguration : Configuration public bool IgnoreTemplateParameterMissing { get; set; } = true; public bool EnableWriteNullValueCell { get; set; } = true; public bool WriteEmptyStringAsNull { get; set; } = false; + public bool TrimColumnNames { get; set; } = true; + public bool IgnoreEmptyRows { 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/Utils/ColumnHelper.cs b/src/MiniExcel/Utils/ColumnHelper.cs index 195210ff..8c2d4f8f 100644 --- a/src/MiniExcel/Utils/ColumnHelper.cs +++ b/src/MiniExcel/Utils/ColumnHelper.cs @@ -1,17 +1,19 @@ -namespace MiniExcelLibs.Utils +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; + +namespace MiniExcelLibs.Utils { - using System.Collections.Concurrent; - using System.Collections.Generic; - using System.IO; // For Row/Column Index - internal static partial class ColumnHelper + internal static class ColumnHelper { private const int GENERAL_COLUMN_INDEX = 255; private const int MAX_COLUMN_INDEX = 16383; private static int _IntMappingAlphabetCount = 0; private static readonly ConcurrentDictionary _IntMappingAlphabet = new ConcurrentDictionary(); private static readonly ConcurrentDictionary _AlphabetMappingInt = new ConcurrentDictionary(); + static ColumnHelper() { _IntMappingAlphabetCount = _IntMappingAlphabet.Count; @@ -21,9 +23,8 @@ static ColumnHelper() public static string GetAlphabetColumnName(int columnIndex) { CheckAndSetMaxColumnIndex(columnIndex); - if (_IntMappingAlphabet.TryGetValue(columnIndex, out var value)) - return value; - throw new KeyNotFoundException(); + return _IntMappingAlphabet.TryGetValue(columnIndex, out var value) ? value + : throw new KeyNotFoundException(); } public static int GetColumnIndex(string columnName) @@ -35,23 +36,23 @@ public static int GetColumnIndex(string columnName) private static void CheckAndSetMaxColumnIndex(int columnIndex) { - if (columnIndex >= _IntMappingAlphabetCount) + if (columnIndex < _IntMappingAlphabetCount) + return; + if (columnIndex > MAX_COLUMN_INDEX) + throw new InvalidDataException($"ColumnIndex {columnIndex} over excel vaild max index."); + + for (int i = _IntMappingAlphabet.Count; i <= columnIndex; i++) { - if (columnIndex > MAX_COLUMN_INDEX) - throw new InvalidDataException($"ColumnIndex {columnIndex} over excel vaild max index."); - for (int i = _IntMappingAlphabet.Count; i <= columnIndex; i++) - { - _IntMappingAlphabet.AddOrUpdate(i, IntToLetters(i), (a, b) => IntToLetters(i)); - _AlphabetMappingInt.AddOrUpdate(IntToLetters(i), i, (a, b) => i); - } - _IntMappingAlphabetCount = _IntMappingAlphabet.Count; + _IntMappingAlphabet.AddOrUpdate(i, IntToLetters(i), (a, b) => IntToLetters(i)); + _AlphabetMappingInt.AddOrUpdate(IntToLetters(i), i, (a, b) => i); } + _IntMappingAlphabetCount = _IntMappingAlphabet.Count; } internal static string IntToLetters(int value) { - value = value + 1; - string result = string.Empty; + value++; + var result = string.Empty; while (--value >= 0) { result = (char)('A' + value % 26) + result; @@ -60,5 +61,4 @@ internal static string IntToLetters(int value) return result; } } - } diff --git a/src/MiniExcel/Utils/CustomPropertyHelper.cs b/src/MiniExcel/Utils/CustomPropertyHelper.cs index b4c41c86..c4eb2075 100644 --- a/src/MiniExcel/Utils/CustomPropertyHelper.cs +++ b/src/MiniExcel/Utils/CustomPropertyHelper.cs @@ -1,15 +1,15 @@ -namespace MiniExcelLibs.Utils +using MiniExcelLibs.Attributes; +using MiniExcelLibs.OpenXml; +using MiniExcelLibs.OpenXml.Models; +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Reflection; + +namespace MiniExcelLibs.Utils { - using MiniExcelLibs.Attributes; - using MiniExcelLibs.OpenXml; - using MiniExcelLibs.OpenXml.Models; - using System; - using System.Collections; - using System.Collections.Generic; - using System.ComponentModel; - using System.Linq; - using System.Reflection; - internal class ExcelColumnInfo { public object Key { get; set; } @@ -33,14 +33,8 @@ internal class ExcellSheetInfo public object Key { get; set; } public string ExcelSheetName { get; set; } public SheetState ExcelSheetState { get; set; } - - private string ExcelSheetStateAsString - { - get - { - return ExcelSheetState.ToString().ToLower(); - } - } + + private string ExcelSheetStateAsString => ExcelSheetState.ToString().ToLower(); public SheetDto ToDto(int sheetIndex) { @@ -48,7 +42,7 @@ public SheetDto ToDto(int sheetIndex) } } - internal static partial class CustomPropertyHelper + internal static class CustomPropertyHelper { internal static IDictionary GetEmptyExpandoObject(int maxColumnIndex, int startCellIndex) { @@ -73,7 +67,7 @@ internal static IDictionary GetEmptyExpandoObject(Dictionary GetSaveAsProperties(this Type type, Configuration configuration) { - List props = GetExcelPropertyInfo(type, BindingFlags.Public | BindingFlags.Instance, configuration) + var props = GetExcelPropertyInfo(type, BindingFlags.Public | BindingFlags.Instance, configuration) .Where(prop => prop.Property.CanRead) .ToList() /*ignore without set*/; @@ -89,7 +83,7 @@ internal static List SortCustomProps(List prop //TODO: need optimize performance var withCustomIndexProps = props.Where(w => w.ExcelColumnIndex != null && w.ExcelColumnIndex > -1).ToList(); - if (withCustomIndexProps.GroupBy(g => g.ExcelColumnIndex).Any(_ => _.Count() > 1)) + if (withCustomIndexProps.GroupBy(g => g.ExcelColumnIndex).Any(x => x.Count() > 1)) throw new InvalidOperationException("Duplicate column name"); var maxColumnIndex = props.Count - 1; @@ -98,7 +92,7 @@ internal static List SortCustomProps(List prop var withoutCustomIndexProps = props.Where(w => w.ExcelColumnIndex == null || w.ExcelColumnIndex == -1).ToList(); - List newProps = new List(); + var newProps = new List(); var index = 0; for (int i = 0; i <= maxColumnIndex; i++) { @@ -127,7 +121,8 @@ internal static List SortCustomProps(List prop internal static List GetExcelCustomPropertyInfos(Type type, string[] keys, Configuration configuration) { - List props = GetExcelPropertyInfo(type, BindingFlags.SetProperty | BindingFlags.Public | BindingFlags.Instance, configuration) + var flags = BindingFlags.SetProperty | BindingFlags.Public | BindingFlags.Instance; + var props = GetExcelPropertyInfo(type, flags, configuration) .Where(prop => prop.Property.Info.GetSetMethod() != null // why not .Property.CanWrite? because it will use private setter && !prop.Property.Info.GetAttributeValue((ExcelIgnoreAttribute x) => x.ExcelIgnore) && !prop.Property.Info.GetAttributeValue((ExcelColumnAttribute x) => x.Ignore)) @@ -137,19 +132,18 @@ internal static List GetExcelCustomPropertyInfos(Type type, str 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)) + if (withCustomIndexProps.GroupBy(g => g.ExcelColumnIndex).Any(x => x.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 > 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 == null) + continue; + 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"); } return props; @@ -157,24 +151,19 @@ internal static List GetExcelCustomPropertyInfos(Type type, str internal static string DescriptionAttr(Type type, object source) { - FieldInfo fi = type.GetField(source.ToString()); + var fi = type.GetField(source.ToString()); //For some database dirty data, there may be no way to change to the correct enumeration, will return NULL if (fi == null) return source.ToString(); - DescriptionAttribute[] attributes = (DescriptionAttribute[])fi.GetCustomAttributes( - typeof(DescriptionAttribute), false); - - if (attributes != null && attributes.Length > 0) - return attributes[0].Description; - else - return source.ToString(); + var attributes = (DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false); + return attributes.Length > 0 ? attributes[0].Description : source.ToString(); } private static IEnumerable ConvertToExcelCustomPropertyInfo(PropertyInfo[] props, Configuration configuration) { // solve : https://github.com/shps951023/MiniExcel/issues/138 - return props.Select(p => + var columnInfos = props.Select(p => { var gt = Nullable.GetUnderlyingType(p.PropertyType); var excelColumnName = p.GetAttribute(); @@ -185,11 +174,12 @@ private static IEnumerable ConvertToExcelCustomPropertyInfo(Pro if (dynamicColumn != null) excelColumn = dynamicColumn; - var ignore = p.GetAttributeValue((ExcelIgnoreAttribute x) => x.ExcelIgnore) || p.GetAttributeValue((ExcelColumnAttribute x) => x.Ignore) || (excelColumn != null && excelColumn.Ignore); + var ignore = p.GetAttributeValue((ExcelIgnoreAttribute x) => x.ExcelIgnore) || + p.GetAttributeValue((ExcelColumnAttribute x) => x.Ignore) || + (excelColumn?.Ignore ?? false); if (ignore) - { return null; - } + //TODO:or configulation Dynamic var excelColumnIndex = excelColumn?.Index > -1 ? excelColumn.Index : (int?)null; return new ExcelColumnInfo @@ -207,7 +197,9 @@ private static IEnumerable ConvertToExcelCustomPropertyInfo(Pro ExcelColumnType = excelColumn?.Type ?? ColumnType.Value, CustomFormatter = dynamicColumn?.CustomFormatter }; - }).Where(_ => _ != null); + }); + + return columnInfos.Where(x => x != null); } private static IEnumerable GetExcelPropertyInfo(Type type, BindingFlags bindingFlags, Configuration configuration) @@ -219,7 +211,7 @@ private static IEnumerable GetExcelPropertyInfo(Type type, Bind internal static ExcellSheetInfo GetExcellSheetInfo(Type type, Configuration configuration) { // default options - var sheetInfo = new ExcellSheetInfo() + var sheetInfo = new ExcellSheetInfo { Key = type.Name, ExcelSheetName = null, // will be generated automatically as Sheet @@ -227,18 +219,17 @@ internal static ExcellSheetInfo GetExcellSheetInfo(Type type, Configuration conf }; // options from ExcelSheetAttribute - ExcelSheetAttribute excelSheetAttribute = type.GetCustomAttribute(typeof(ExcelSheetAttribute)) as ExcelSheetAttribute; - if (excelSheetAttribute != null) + if (type.GetCustomAttribute(typeof(ExcelSheetAttribute)) is ExcelSheetAttribute excelSheetAttr) { - sheetInfo.ExcelSheetName = excelSheetAttribute.Name ?? type.Name; - sheetInfo.ExcelSheetState = excelSheetAttribute.State; + sheetInfo.ExcelSheetName = excelSheetAttr.Name ?? type.Name; + sheetInfo.ExcelSheetState = excelSheetAttr.State; } // options from DynamicSheets configuration - OpenXmlConfiguration openXmlCOnfiguration = configuration as OpenXmlConfiguration; - if (openXmlCOnfiguration != null && openXmlCOnfiguration.DynamicSheets != null && openXmlCOnfiguration.DynamicSheets.Length > 0) + var openXmlCOnfiguration = configuration as OpenXmlConfiguration; + if (openXmlCOnfiguration?.DynamicSheets?.Length > 0) { - var dynamicSheet = openXmlCOnfiguration.DynamicSheets.SingleOrDefault(_ => _.Key == type.Name); + var dynamicSheet = openXmlCOnfiguration.DynamicSheets.SingleOrDefault(x => x.Key == type.Name); if (dynamicSheet != null) { sheetInfo.ExcelSheetName = dynamicSheet.Name; @@ -251,43 +242,32 @@ internal static ExcellSheetInfo GetExcellSheetInfo(Type type, Configuration conf internal static List GetDictionaryColumnInfo(IDictionary dicString, IDictionary dic, Configuration configuration) { - List props; - var _props = new List(); - if (dicString != null) - { - foreach (var key in dicString.Keys) - { - SetDictionaryColumnInfo(_props, key, configuration); - } - } - else if (dic != null) - { - foreach (var key in dic.Keys) - { - SetDictionaryColumnInfo(_props, key, configuration); - } - } - else + var props = new List(); + var keys = dicString?.Keys.ToList() ?? dic?.Keys + ?? throw new NotSupportedException(); + + foreach (var key in keys) { - throw new NotSupportedException("SetDictionaryColumnInfo Error"); + SetDictionaryColumnInfo(props, key, configuration); } - - props = SortCustomProps(_props); - return props; + return SortCustomProps(props); } - internal static void SetDictionaryColumnInfo(List _props, object key, Configuration configuration) + internal static void SetDictionaryColumnInfo(List props, object key, Configuration configuration) { - var p = new ExcelColumnInfo(); - p.ExcelColumnName = key?.ToString(); - p.Key = key; + var p = new ExcelColumnInfo + { + Key = key, + ExcelColumnName = key?.ToString() + }; + // TODO:Dictionary value type is not fiexed //var _t = //var gt = Nullable.GetUnderlyingType(p.PropertyType); var isIgnore = false; if (configuration.DynamicColumns != null && configuration.DynamicColumns.Length > 0) { - var dynamicColumn = configuration.DynamicColumns.SingleOrDefault(_ => _.Key == key.ToString()); + var dynamicColumn = configuration.DynamicColumns.SingleOrDefault(x => x.Key == key?.ToString()); if (dynamicColumn != null) { p.Nullable = true; @@ -311,7 +291,7 @@ internal static void SetDictionaryColumnInfo(List _props, objec } } if (!isIgnore) - _props.Add(p); + props.Add(p); } internal static bool TryGetTypeColumnInfo(Type type, Configuration configuration, out List props) @@ -324,14 +304,9 @@ internal static bool TryGetTypeColumnInfo(Type type, Configuration configuration } if (type.IsValueType) - { - throw new NotImplementedException($"MiniExcel not support only {type.Name} value generic type"); - } - + throw new NotImplementedException($"MiniExcel does not support only {type.Name} value generic type"); if (type == typeof(string) || type == typeof(DateTime) || type == typeof(Guid)) - { - throw new NotImplementedException($"MiniExcel not support only {type.Name} generic type"); - } + throw new NotImplementedException($"MiniExcel does not support only {type.Name} generic type"); if (ValueIsNeededToDetermineProperties(type)) { @@ -370,11 +345,11 @@ internal static ExcelColumnInfo GetColumnInfosFromDynamicConfiguration(string co if (configuration.DynamicColumns == null || configuration.DynamicColumns.Length <= 0) return prop; - var dynamicColumn = configuration.DynamicColumns.SingleOrDefault(_ => string.Equals(_.Key, columnName, StringComparison.OrdinalIgnoreCase)); + var dynamicColumn = configuration.DynamicColumns + .SingleOrDefault(col => string.Equals(col.Key, columnName, StringComparison.OrdinalIgnoreCase)); + if (dynamicColumn == null || dynamicColumn.Ignore) - { return prop; - } prop.Nullable = true; prop.ExcelIgnore = dynamicColumn.Ignore; @@ -406,5 +381,36 @@ internal static ExcelColumnInfo GetColumnInfosFromDynamicConfiguration(string co return prop; } + + internal static Dictionary GetHeaders(IDictionary item, bool trimNames = false) + { + return DictToNameWithIndex(item) + .GroupBy(x => x.Name) + .SelectMany(GroupToNameWithIndex) + .ToDictionary(kv => trimNames ? kv.Name.Trim() : kv.Name, kv => kv.Index); + } + + private static IEnumerable DictToNameWithIndex(IDictionary dict) + { + return dict.Values.Select((obj, idx) => new NameIndexPair(idx, obj?.ToString() ?? "")); + } + + private static IEnumerable GroupToNameWithIndex(IGrouping group) + { + return group.Select((grp, idx) => + new NameIndexPair(grp.Index, idx == 0 ? grp.Name : $"{grp.Name}_____{idx + 1}")); + } + + private class NameIndexPair + { + public int Index { get; } + public string Name { get; } + + public NameIndexPair(int index, string name) + { + Index = index; + Name = name; + } + } } } \ No newline at end of file diff --git a/src/MiniExcel/Utils/ReferenceHelper.cs b/src/MiniExcel/Utils/ReferenceHelper.cs index 50b2ad1d..b14a61fa 100644 --- a/src/MiniExcel/Utils/ReferenceHelper.cs +++ b/src/MiniExcel/Utils/ReferenceHelper.cs @@ -1,30 +1,23 @@ -namespace MiniExcelLibs.Utils -{ - using System; - using System.Globalization; +using System; +using System.Globalization; +using System.Linq; - internal static partial class ReferenceHelper +namespace MiniExcelLibs.Utils +{ + internal static class ReferenceHelper { public static string GetCellNumber(string cell) { - string cellNumber = string.Empty; - for (int i = 0; i < cell.Length; i++) - { - if (Char.IsDigit(cell[i])) - cellNumber += cell[i]; - } - return cellNumber; + return cell + .Where(char.IsDigit) + .Aggregate(string.Empty, (current, c) => current + c); } public static string GetCellLetter(string cell) { - string GetCellLetter = string.Empty; - for (int i = 0; i < cell.Length; i++) - { - if (Char.IsLetter(cell[i])) - GetCellLetter += cell[i]; - } - return GetCellLetter; + return cell + .Where(char.IsLetter) + .Aggregate(string.Empty, (current, c) => current + c); } /// X=CellLetter,Y=CellNumber,ex:A1=(1,1),B2=(2,2) @@ -33,11 +26,9 @@ public static Tuple ConvertCellToXY(string cell) const string keys = " ABCDEFGHIJKLMNOPQRSTUVWXYZ"; const int mode = 26; - var x = 0; - var cellLetter = GetCellLetter(cell); //AA=27,ZZ=702 - for (int i = 0; i < cellLetter.Length; i++) - x = x * mode + keys.IndexOf(cellLetter[i]); + var cellLetter = GetCellLetter(cell); + var x = cellLetter.Aggregate(0, (idx, chr) => idx * mode + keys.IndexOf(chr)); var cellNumber = GetCellNumber(cell); return Tuple.Create(x, int.Parse(cellNumber)); @@ -47,23 +38,18 @@ public static Tuple ConvertCellToXY(string cell) public static string ConvertXyToCell(int x, int y) { int dividend = x; - string columnName = String.Empty; - int modulo; + string columnName = string.Empty; while (dividend > 0) { - modulo = (dividend - 1) % 26; - columnName = Convert.ToChar(65 + modulo).ToString() + columnName; - dividend = (int)((dividend - modulo) / 26); + var modulo = (dividend - 1) % 26; + columnName = Convert.ToChar(65 + modulo) + columnName; + dividend = (dividend - modulo) / 26; } return $"{columnName}{y}"; } - } - - internal static partial class ReferenceHelper - { - /**Below Code Copy&Modified from ExcelDataReader @MIT License**/ + /**The code below was copied and modified from ExcelDataReader - @MIT License**/ /// /// Logic for the Excel dimensions. Ex: A15 /// @@ -72,45 +58,42 @@ internal static partial class ReferenceHelper /// The row, 1-based. public static bool ParseReference(string value, out int column, out int row) { + row = 0; column = 0; var position = 0; const int offset = 'A' - 1; - if (value != null) + if (string.IsNullOrWhiteSpace(value)) + return false; + + while (position < value.Length) { - while (position < value.Length) + var c = char.ToUpperInvariant(value[position]); + if (c >= 'A' && c <= 'Z') { - var c = value[position]; - if (c >= 'A' && c <= 'Z') - { - position++; - column *= 26; - column += c - offset; - continue; - } + position++; + column *= 26; + column += c - offset; + continue; + } + if (!char.IsLetter(c)) + { if (char.IsDigit(c)) break; - - position = 0; - break; + return false; } - } - if (position == 0) - { - column = 0; row = 0; - return false; + column = 0; + position = 0; + break; } - if (!int.TryParse(value.Substring(position), NumberStyles.None, CultureInfo.InvariantCulture, out row)) - { + if (position == 0) return false; - } - return true; + return int.TryParse(value.Substring(position), NumberStyles.None, CultureInfo.InvariantCulture, out row); } } - } diff --git a/tests/MiniExcelTests/MiniExcelIssueTests.cs b/tests/MiniExcelTests/MiniExcelIssueTests.cs index 76a7a1a2..8270b9a2 100644 --- a/tests/MiniExcelTests/MiniExcelIssueTests.cs +++ b/tests/MiniExcelTests/MiniExcelIssueTests.cs @@ -5,7 +5,6 @@ using MiniExcelLibs.OpenXml; using MiniExcelLibs.Tests.Utils; using Newtonsoft.Json; -using NPOI.SS.UserModel; using NPOI.XSSF.UserModel; using OfficeOpenXml; using System; @@ -26,7 +25,7 @@ namespace MiniExcelLibs.Tests { - public partial class MiniExcelIssueTests + public class MiniExcelIssueTests { private readonly ITestOutputHelper _output; @@ -41,7 +40,8 @@ public MiniExcelIssueTests(ITestOutputHelper output) [Fact] public void TestIssue549() { - var data = new[] { + var data = new[] + { new{id=1,name="jack"}, new{id=2,name="mike"}, }; @@ -51,8 +51,8 @@ public void TestIssue549() { using (FileStream stream = new FileStream(path, FileMode.Open, FileAccess.Read)) { - XSSFWorkbook workbook = new XSSFWorkbook(stream); - ISheet sheet = workbook.GetSheetAt(0); + using var workbook = new XSSFWorkbook(stream); + var sheet = workbook.GetSheetAt(0); var a2 = sheet.GetRow(1).GetCell(0); var b2 = sheet.GetRow(1).GetCell(1); Assert.Equal((string)rows[0].id.ToString(), a2.NumericCellValue.ToString()); @@ -67,14 +67,14 @@ public void TestIssue24020201() { var path = PathHelper.GetTempFilePath(); var templatePath = PathHelper.GetFile("xlsx/TestIssue24020201.xlsx"); - var data = new Dictionary() + var data = new Dictionary { ["title"] = "This's title", - ["B"] = new List>() + ["B"] = new List> { - new Dictionary(){ { "specialMark", 1 } }, - new Dictionary(){ { "specialMark", 2 } }, - new Dictionary(){ { "specialMark", 3 } }, + new() { { "specialMark", 1 } }, + new() { { "specialMark", 2 } }, + new() { { "specialMark", 3 } }, } }; MiniExcel.SaveAsByTemplate(path, templatePath, data); @@ -88,9 +88,12 @@ public void TestIssue553() var templatePath = PathHelper.GetFile("xlsx/TestIssue553.xlsx"); var data = new { - B = new[] { - new{ ITM=1 },new{ ITM=2 }, new{ ITM=3 }, - } + B = new[] + { + new{ ITM=1 }, + new{ ITM=2 }, + new{ ITM=3 } + } }; MiniExcel.SaveAsByTemplate(path, templatePath, data); } @@ -111,11 +114,12 @@ public void TestPR10() public void TestIssue289() { var path = PathHelper.GetTempFilePath(); - var value = new[] { - new Issue289Dto { Name="0001", UserType=Issue289Type.V1 }, - new Issue289Dto { Name="0002", UserType=Issue289Type.V2 }, - new Issue289Dto { Name="0003", UserType=Issue289Type.V3 }, - }; + Issue289Dto[] value = + [ + new() { Name="0001", UserType=Issue289Type.V1 }, + new() { Name="0002", UserType=Issue289Type.V2 }, + new() { Name="0003", UserType=Issue289Type.V3 } + ]; MiniExcel.SaveAs(path, value); var rows = MiniExcel.Query(path).ToList(); @@ -149,13 +153,14 @@ public void TestIssueI4X92G() { var path = PathHelper.GetTempFilePath("csv"); { - var value = new[] { + var value = new[] + { new { ID=1,Name ="Jack",InDate=new DateTime(2021,01,03)}, new { ID=2,Name ="Henry",InDate=new DateTime(2020,05,03)}, }; MiniExcel.SaveAs(path, value); var content = File.ReadAllText(path); - Assert.Contains(""" + Assert.Equal(""" ID,Name,InDate 1,Jack,"2021-01-03 00:00:00" 2,Henry,"2020-05-03 00:00:00" @@ -224,22 +229,20 @@ public class TestIssue430Dto [Fact] public void TestIssue_DataReaderSupportDimension() { - { - DataTable table = new DataTable(); - { - table.Columns.Add("id", typeof(int)); - table.Columns.Add("name", typeof(string)); - table.Rows.Add(1, "Jack"); - table.Rows.Add(2, "Mike"); - } - var path = Path.GetTempPath() + Guid.NewGuid() + ".xlsx"; - DataTableReader reader = table.CreateDataReader(); - var config = new OpenXmlConfiguration() { FastMode = true }; - MiniExcel.SaveAs(path, reader, configuration: config); - var xml = Helpers.GetZipFileContent(path, "xl/worksheets/sheet1.xml"); - Assert.Contains("", xml); - Assert.Contains("", xml); - } + using var table = new DataTable(); + + table.Columns.Add("id", typeof(int)); + table.Columns.Add("name", typeof(string)); + table.Rows.Add(1, "Jack"); + table.Rows.Add(2, "Mike"); + + var path = Path.GetTempPath() + Guid.NewGuid() + ".xlsx"; + using var reader = table.CreateDataReader(); + var config = new OpenXmlConfiguration { FastMode = true }; + MiniExcel.SaveAs(path, reader, configuration: config); + var xml = Helpers.GetZipFileContent(path, "xl/worksheets/sheet1.xml"); + Assert.Contains("", xml); + Assert.Contains("", xml); } /// @@ -252,9 +255,10 @@ public void TestIssue413() var path = PathHelper.GetTempFilePath(); var value = new { - list = new List>{ - new Dictionary{ { "id","001"},{ "time",new DateTime(2022,12,25)} }, - new Dictionary{ { "id","002"},{ "time",new DateTime(2022,9,23)} }, + list = new List> + { + new() { { "id","001"},{ "time",new DateTime(2022,12,25)} }, + new() { { "id","002"},{ "time",new DateTime(2022,9,23)} }, } }; var templatePath = PathHelper.GetFile("xlsx/TestIssue413.xlsx"); @@ -286,17 +290,13 @@ public void TestIssue405() [Fact] public void TestIssueI57WMM() { - var sheets = new[] { - new Dictionary(){ - {"ID","0001"},{"Name","Jack"} - } - }; + Dictionary[] sheets = [ new() { ["ID"] = "0001", ["Name"] = "Jack" } ]; var stream = new MemoryStream(); stream.SaveAs(sheets, excelType: ExcelType.CSV); stream.Position = 0; // convert stream to string - StreamReader reader = new StreamReader(stream); + using var reader = new StreamReader(stream); string text = reader.ReadToEnd(); var expected = "ID,Name\r\n0001,Jack\r\n"; Assert.Equal(expected, text); @@ -359,7 +359,7 @@ public void TestIssue369() public void TestIssueI4ZYUU() { var path = PathHelper.GetTempPath(); - var value = new TestIssueI4ZYUUDto[] { new() { MyProperty = "1", MyProperty2 = new DateTime(2022, 10, 15) } }; + TestIssueI4ZYUUDto[] value = [new() { MyProperty = "1", MyProperty2 = new DateTime(2022, 10, 15) }]; MiniExcel.SaveAs(path, value); var rows = MiniExcel.Query(path).ToList(); Assert.Equal("2022-10", rows[1].B); @@ -416,41 +416,38 @@ public void TestIssue117() public void TestIssue352() { { - DataTable table = new DataTable(); - { - table.Columns.Add("id", typeof(int)); - table.Columns.Add("name", typeof(string)); - table.Rows.Add(1, "Jack"); - table.Rows.Add(2, "Mike"); - } + using var table = new DataTable(); + table.Columns.Add("id", typeof(int)); + table.Columns.Add("name", typeof(string)); + table.Rows.Add(1, "Jack"); + table.Rows.Add(2, "Mike"); + var path = Path.GetTempPath() + Guid.NewGuid() + ".xlsx"; - DataTableReader reader = table.CreateDataReader(); + var reader = table.CreateDataReader(); MiniExcel.SaveAs(path, reader); var xml = Helpers.GetZipFileContent(path, "xl/worksheets/sheet1.xml"); var cnt = Regex.Matches(xml, "").Count; } { - DataTable table = new DataTable(); - { - table.Columns.Add("id", typeof(int)); - table.Columns.Add("name", typeof(string)); - table.Rows.Add(1, "Jack"); - table.Rows.Add(2, "Mike"); - } + using var table = new DataTable(); + table.Columns.Add("id", typeof(int)); + table.Columns.Add("name", typeof(string)); + table.Rows.Add(1, "Jack"); + table.Rows.Add(2, "Mike"); + var path = Path.GetTempPath() + Guid.NewGuid() + ".xlsx"; - DataTableReader reader = table.CreateDataReader(); + var reader = table.CreateDataReader(); MiniExcel.SaveAs(path, reader, false); var xml = Helpers.GetZipFileContent(path, "xl/worksheets/sheet1.xml"); var cnt = Regex.Matches(xml, "").Count; } { - DataTable table = new DataTable(); - { - table.Columns.Add("id", typeof(int)); - table.Columns.Add("name", typeof(string)); - } + using var table = new DataTable(); + table.Columns.Add("id", typeof(int)); + table.Columns.Add("name", typeof(string)); + var path = Path.GetTempPath() + Guid.NewGuid() + ".xlsx"; - DataTableReader reader = table.CreateDataReader(); + var reader = table.CreateDataReader(); MiniExcel.SaveAs(path, reader); var xml = Helpers.GetZipFileContent(path, "xl/worksheets/sheet1.xml"); var cnt = Regex.Matches(xml, "").Count; @@ -1420,10 +1417,13 @@ public void TestIssue292() MiniExcel.ConvertXlsxToCsv(xlsxPath, csvPath); var actualCotent = File.ReadAllText(csvPath); - Assert.Equal(@"Name,Age,Name,Age -Jack,22,Mike,25 -Henry,44,Jerry,44 -", actualCotent); + Assert.Equal( + """ + Name,Age,Name,Age + Jack,22,Mike,25 + Henry,44,Jerry,44 + + """, actualCotent); } { @@ -1463,13 +1463,13 @@ public void TestIssue293() public void TestIssueI49RYZ() { { - var values = new[] - { - new I49RYZDto(){Name="Jack",UserType=I49RYZUserType.V1}, - new I49RYZDto(){Name="Leo",UserType=I49RYZUserType.V2}, - new I49RYZDto(){Name="Henry",UserType=I49RYZUserType.V3}, - new I49RYZDto(){Name="Lisa",UserType=null}, - }; + I49RYZDto[] values = + [ + new() {Name="Jack",UserType=I49RYZUserType.V1}, + new() {Name="Leo",UserType=I49RYZUserType.V2}, + new() {Name="Henry",UserType=I49RYZUserType.V3}, + new() {Name="Lisa",UserType=null} + ]; var path = PathHelper.GetTempPath(); MiniExcel.SaveAs(path, values); var rows = MiniExcel.Query(path, true).ToList(); @@ -1795,7 +1795,7 @@ public class IssueI3X2ZLDTO public void TestIssue261() { var csvPath = PathHelper.GetFile("csv/TestCsvToXlsx.csv"); - var xlsxPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString() + ".xlsx"); + var xlsxPath = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xlsx"); CsvToXlsx(csvPath, xlsxPath); var rows = MiniExcel.Query(xlsxPath).ToList(); Assert.Equal("Name", rows[0].A); @@ -2821,7 +2821,7 @@ public void Issue87() [Fact] public void Issue208() { - var path = @"../../../../../samples/xlsx/TestIssue208.xlsx"; + var path = "../../../../../samples/xlsx/TestIssue208.xlsx"; var columns = MiniExcel.GetColumns(path).ToList(); Assert.Equal(16384, columns.Count); Assert.Equal("XFD", columns[16383]); @@ -2835,7 +2835,7 @@ public void Issue206() { { var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xlsx"); - var templatePath = @"../../../../../samples/xlsx/TestTemplateBasicIEmumerableFill.xlsx"; + var templatePath = "../../../../../samples/xlsx/TestTemplateBasicIEmumerableFill.xlsx"; var dt = new DataTable(); { @@ -3886,6 +3886,53 @@ static IEnumerable GetTestData() } } + [Fact] + public void Issue_686() + { + var path = PathHelper.GetFile("xlsx/TestIssue686.xlsx"); + Assert.Throws(() => + MiniExcel.QueryRange(path, useHeaderRow: false, startCell: "ZZFF10", endCell:"ZZFF11").First()); + + Assert.Throws(() => + MiniExcel.QueryRange(path, useHeaderRow: false, startCell: "ZZFF@@10", endCell:"ZZFF@@11").First()); + } + + [Fact] + public void Test_Issue_693_SaveSheetWithLongName() + { + var path1 = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xlsx"); + var path2 = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xlsx"); + try + { + List> data = [ new() { ["First"] = 1, ["Second"] = 2 }]; + Assert.Throws(() => MiniExcel.SaveAs(path1, data, sheetName:"Some Really Looooooooooong Sheet Name")); + MiniExcel.SaveAs(path2, new List>()); + Assert.Throws(() => MiniExcel.Insert(path1, data, sheetName:"Some Other Very Looooooong Sheet Name")); + } + finally + { + File.Delete(path1); + File.Delete(path2); + } + } + + internal class Issue697 + { + public int First { get; set; } + public int Second { get; set; } + public int Third { get; set; } + public int Fourth { get; set; } + } + [Fact] + public void Test_Issue_697_EmptyRowsStronglyTypedQuery() + { + var path = "../../../../../samples/xlsx/TestIssue697.xlsx"; + var rowsIgnoreEmpty = MiniExcel.Query(path, configuration: new OpenXmlConfiguration{IgnoreEmptyRows = true}).ToList(); + var rowsCountEmpty = MiniExcel.Query(path).ToList(); + Assert.Equal(4, rowsIgnoreEmpty.Count); + Assert.Equal(5, rowsCountEmpty.Count); + } + [Fact] public void Issue_710() { @@ -3893,11 +3940,10 @@ public void Issue_710() using var memoryStream = new MemoryStream(); memoryStream.SaveAs(values, configuration: new OpenXmlConfiguration { - FastMode = true, + FastMode = true }); memoryStream.Position = 0; - var dataReader = memoryStream.GetReader(useHeaderRow: false); dataReader.Read(); @@ -3905,7 +3951,6 @@ public void Issue_710() for (int i = 0; i < dataReader.FieldCount; i++) { var columnName = dataReader.GetName(i); - var ordinal = dataReader.GetOrdinal(columnName); Assert.Equal(i, ordinal); diff --git a/tests/MiniExcelTests/MiniExcelOpenXmlTests.cs b/tests/MiniExcelTests/MiniExcelOpenXmlTests.cs index 8b4add0e..4ff82d00 100644 --- a/tests/MiniExcelTests/MiniExcelOpenXmlTests.cs +++ b/tests/MiniExcelTests/MiniExcelOpenXmlTests.cs @@ -20,7 +20,7 @@ namespace MiniExcelLibs.Tests { - public partial class MiniExcelOpenXmlTests + public class MiniExcelOpenXmlTests { private readonly ITestOutputHelper output; public MiniExcelOpenXmlTests(ITestOutputHelper output) @@ -32,19 +32,19 @@ public MiniExcelOpenXmlTests(ITestOutputHelper output) public void GetColumnsTest() { { - var path = @"../../../../../samples/xlsx/TestTypeMapping.xlsx"; + var path = "../../../../../samples/xlsx/TestTypeMapping.xlsx"; var columns = MiniExcel.GetColumns(path); - Assert.Equal(new[] { "A", "B", "C", "D", "E", "F", "G", "H" }, columns); + Assert.Equal(["A", "B", "C", "D", "E", "F", "G", "H"], columns); } { - var path = @"../../../../../samples/xlsx/TestTypeMapping.xlsx"; + var path = "../../../../../samples/xlsx/TestTypeMapping.xlsx"; var columns = MiniExcel.GetColumns(path); Assert.Equal(8, columns.Count); } { - var path = @"../../../../../samples/xlsx/TestEmpty.xlsx"; + var path = "../../../../../samples/xlsx/TestEmpty.xlsx"; var columns = MiniExcel.GetColumns(path); Assert.Null(columns); } @@ -54,22 +54,21 @@ public void GetColumnsTest() public void SaveAsControlChracter() { string path = GetTempXlsxPath(); - char[] chars = new char[] {'\u0000','\u0001','\u0002','\u0003','\u0004','\u0005','\u0006','\u0007','\u0008', + char[] chars = + [ + '\u0000','\u0001','\u0002','\u0003','\u0004','\u0005','\u0006','\u0007','\u0008', '\u0009', // '\u000A', // '\u000B','\u000C', '\u000D', // '\u000E','\u000F','\u0010','\u0011','\u0012','\u0013','\u0014','\u0015','\u0016', '\u0017','\u0018','\u0019','\u001A','\u001B','\u001C','\u001D','\u001E','\u001F','\u007F' - }; + ]; var input = chars.Select(s => new { Test = s.ToString() }); MiniExcel.SaveAs(path, input); var rows2 = MiniExcel.Query(path, true).Select(s => s.Test).ToArray(); - var rows1 = MiniExcel.Query(path).Select(s => s.Test).ToArray(); - - } public class SaveAsControlChracterVO @@ -96,14 +95,14 @@ public class ExcelAttributeDemo [Fact] public void CustomAttributeWihoutVaildPropertiesTest() { - var path = @"../../../../../samples/xlsx/TestCustomExcelColumnAttribute.xlsx"; + var path = "../../../../../samples/xlsx/TestCustomExcelColumnAttribute.xlsx"; Assert.Throws(() => MiniExcel.Query(path).ToList()); } [Fact] public void QueryCustomAttributesTest() { - var path = @"../../../../../samples/xlsx/TestCustomExcelColumnAttribute.xlsx"; + var path = "../../../../../samples/xlsx/TestCustomExcelColumnAttribute.xlsx"; var rows = MiniExcel.Query(path).ToList(); Assert.Equal("Column1", rows[0].Test1); Assert.Equal("Column2", rows[0].Test2); @@ -157,7 +156,7 @@ public class CustomAttributesWihoutVaildPropertiesTestPoco - [Fact()] + [Fact] public void QueryCastToIDictionary() { var path = @"../../../../../samples/xlsx/TestCenterEmptyRow/TestCenterEmptyRow.xlsx"; @@ -170,9 +169,9 @@ public void QueryCastToIDictionary() [Fact] public void QueryRangeToIDictionary() { - var path = @"../../../../../samples/xlsx/TestCenterEmptyRow/TestCenterEmptyRow.xlsx"; + var path = "../../../../../samples/xlsx/TestCenterEmptyRow/TestCenterEmptyRow.xlsx"; // tips:Only uppercase letters are effective - var rows = MiniExcel.QueryRange(path, startCell: "A2", endCell: "C") + var rows = MiniExcel.QueryRange(path, startCell: "A2", endCell: "C7") .Cast>() .ToList(); Assert.Equal(5, rows.Count); @@ -181,10 +180,10 @@ public void QueryRangeToIDictionary() Assert.Equal(null!, rows[2]["A"]); } - [Fact()] + [Fact] public void CenterEmptyRowsQueryTest() { - var path = @"../../../../../samples/xlsx/TestCenterEmptyRow/TestCenterEmptyRow.xlsx"; + var path = "../../../../../samples/xlsx/TestCenterEmptyRow/TestCenterEmptyRow.xlsx"; using (var stream = File.OpenRead(path)) { var rows = stream.Query().ToList(); @@ -218,7 +217,6 @@ public void CenterEmptyRowsQueryTest() Assert.Equal(2, rows[5].B); Assert.Equal(null, rows[5].C); Assert.Equal(4, rows[5].D); - } using (var stream = File.OpenRead(path)) @@ -251,11 +249,11 @@ public void CenterEmptyRowsQueryTest() Assert.Equal(4, rows[4].d); } } - + [Fact] public void TestEmptyRowsQuerySelfClosingTag() { - var path = @"../../../../../samples/xlsx/TestEmptySelfClosingRow.xlsx"; + var path = "../../../../../samples/xlsx/TestEmptySelfClosingRow.xlsx"; using (var stream = File.OpenRead(path)) { var rows = stream.Query().ToList(); @@ -273,10 +271,10 @@ public void TestEmptyRowsQuerySelfClosingTag() } } - [Fact()] + [Fact] public void TestDynamicQueryBasic_WithoutHead() { - var path = @"../../../../../samples/xlsx/TestDynamicQueryBasic_WithoutHead.xlsx"; + var path = "../../../../../samples/xlsx/TestDynamicQueryBasic_WithoutHead.xlsx"; using (var stream = File.OpenRead(path)) { var rows = stream.Query().ToList(); @@ -288,10 +286,10 @@ public void TestDynamicQueryBasic_WithoutHead() } } - [Fact()] + [Fact] public void TestDynamicQueryBasic_useHeaderRow() { - var path = @"../../../../../samples/xlsx/TestDynamicQueryBasic.xlsx"; + var path = "../../../../../samples/xlsx/TestDynamicQueryBasic.xlsx"; using (var stream = File.OpenRead(path)) { var rows = stream.Query(useHeaderRow: true).ToList(); @@ -325,13 +323,13 @@ public class UserAccount public int Age { get; set; } public bool VIP { get; set; } public decimal Points { get; set; } - public int IgnoredProperty { get { return 1; } } + public int IgnoredProperty => 1; } - [Fact()] + [Fact] public void QueryStrongTypeMapping_Test() { - var path = @"../../../../../samples/xlsx/TestTypeMapping.xlsx"; + var path = "../../../../../samples/xlsx/TestTypeMapping.xlsx"; using (var stream = File.OpenRead(path)) { var rows = stream.Query().ToList(); @@ -365,16 +363,16 @@ public void QueryStrongTypeMapping_Test() public class AutoCheckType { - public Guid? @guid { get; set; } - public bool? @bool { get; set; } - public DateTime? datetime { get; set; } - public string @string { get; set; } + public Guid? Guid { get; set; } + public bool? Bool { get; set; } + public DateTime? Datetime { get; set; } + public string String { get; set; } } - [Fact()] + [Fact] public void AutoCheckTypeTest() { - var path = @"../../../../../samples/xlsx/TestTypeMapping_AutoCheckFormat.xlsx"; + var path = "../../../../../samples/xlsx/TestTypeMapping_AutoCheckFormat.xlsx"; using (var stream = FileHelper.OpenRead(path)) { var rows = stream.Query().ToList(); @@ -401,11 +399,30 @@ public void UriMappingTest() Assert.Equal(new Uri("https://friendly-utilization.net"), rows[1].Url); } } + + public class SimpleAccount + { + public string Name { get; set; } + public int Age { get; set; } + public string Mail { get; set; } + public decimal Points { get; set; } + } + [Fact] + public void TrimColumnNamesTest() + { + var path = "../../../../../samples/xlsx/TestTrimColumnNames.xlsx"; + var rows = MiniExcel.Query(path).ToList(); + + Assert.Equal("Raymond", rows[4].Name); + Assert.Equal(18, rows[4].Age); + Assert.Equal("sagittis.lobortis@leoMorbi.com", rows[4].Mail); + Assert.Equal(8209.76m, rows[4].Points); + } - [Fact()] + [Fact] public void TestDatetimeSpanFormat_ClosedXml() { - var path = @"../../../../../samples/xlsx/TestDatetimeSpanFormat_ClosedXml.xlsx"; + var path = "../../../../../samples/xlsx/TestDatetimeSpanFormat_ClosedXml.xlsx"; using (var stream = FileHelper.OpenRead(path)) { var row = stream.Query().First(); @@ -416,10 +433,10 @@ public void TestDatetimeSpanFormat_ClosedXml() } } - [Fact()] + [Fact] public void LargeFileQueryStrongTypeMapping_Test() { - var path = @"../../../../../benchmarks/MiniExcel.Benchmarks/Test1,000,000x10.xlsx"; + var path = "../../../../../benchmarks/MiniExcel.Benchmarks/Test1,000,000x10.xlsx"; using (var stream = File.OpenRead(path)) { var rows = stream.Query().Take(2).ToList(); @@ -435,9 +452,9 @@ public void LargeFileQueryStrongTypeMapping_Test() } } - [Theory()] - [InlineData(@"../../../../../samples/xlsx/ExcelDataReaderCollections/TestChess.xlsx")] - [InlineData(@"../../../../../samples/xlsx/TestCenterEmptyRow/TestCenterEmptyRow.xlsx")] + [Theory] + [InlineData("../../../../../samples/xlsx/ExcelDataReaderCollections/TestChess.xlsx")] + [InlineData("../../../../../samples/xlsx/TestCenterEmptyRow/TestCenterEmptyRow.xlsx")] public void QueryExcelDataReaderCheckTest(string path) { #if NETCOREAPP3_1 || NET5_0 @@ -446,7 +463,7 @@ public void QueryExcelDataReaderCheckTest(string path) DataSet exceldatareaderResult; using (var stream = File.OpenRead(path)) - using (var reader = ExcelDataReader.ExcelReaderFactory.CreateReader(stream)) + using (var reader = ExcelReaderFactory.CreateReader(stream)) { exceldatareaderResult = reader.AsDataSet(); } @@ -461,7 +478,7 @@ public void QueryExcelDataReaderCheckTest(string path) var keys = row.Keys; foreach (var key in keys) { - var eV = exceldatareaderResult.Tables[0].Rows[rowIndex][MiniExcelLibs.Tests.Utils.Helpers.GetColumnIndex(key)]; + var eV = exceldatareaderResult.Tables[0].Rows[rowIndex][Helpers.GetColumnIndex(key)]; var v = row[key] == null ? DBNull.Value : row[key]; Assert.Equal(eV, v); } @@ -469,24 +486,24 @@ public void QueryExcelDataReaderCheckTest(string path) } } - [Fact()] + [Fact] public void QueryCustomStyle() { - var path = @"../../../../../samples/xlsx/TestWihoutRAttribute.xlsx"; + var path = "../../../../../samples/xlsx/TestWihoutRAttribute.xlsx"; using (var stream = File.OpenRead(path)) { } } - [Fact()] + [Fact] public void QuerySheetWithoutRAttribute() { - var path = @"../../../../../samples/xlsx/TestWihoutRAttribute.xlsx"; + var path = "../../../../../samples/xlsx/TestWihoutRAttribute.xlsx"; using (var stream = File.OpenRead(path)) { var rows = stream.Query().ToList(); - var keys = (rows.First() as IDictionary).Keys; + var keys = (rows.First() as IDictionary)!.Keys; Assert.Equal(2, rows.Count); Assert.Equal(5, keys.Count); @@ -504,15 +521,15 @@ public void QuerySheetWithoutRAttribute() } } - [Fact()] + [Fact] public void FixDimensionJustOneColumnParsingError_Test() { { - var path = @"../../../../../samples/xlsx/TestDimensionC3.xlsx"; + var path = "../../../../../samples/xlsx/TestDimensionC3.xlsx"; using (var stream = File.OpenRead(path)) { var rows = stream.Query().ToList(); - var keys = (rows.First() as IDictionary).Keys; + var keys = (rows.First() as IDictionary)!.Keys; Assert.Equal(3, keys.Count); Assert.Equal(2, rows.Count); } @@ -524,17 +541,17 @@ public class SaveAsFileWithDimensionByICollectionTestType public string A { get; set; } public string B { get; set; } } - [Fact()] + [Fact] public void SaveAsFileWithDimensionByICollection() { //List { var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xlsx"); - var values = new List - { - new SaveAsFileWithDimensionByICollectionTestType{A="A",B="B"}, - new SaveAsFileWithDimensionByICollectionTestType{A="A",B="B"}, - }; + List values = + [ + new() { A = "A", B = "B" }, + new() { A = "A", B = "B" } + ]; MiniExcel.SaveAs(path, values); { using (var stream = File.OpenRead(path)) @@ -635,7 +652,7 @@ public void SaveAsFileWithDimensionByICollection() } } - [Fact()] + [Fact] public void SaveAsFileWithDimension() { { @@ -666,7 +683,7 @@ public void SaveAsFileWithDimension() table.Columns.Add("c", typeof(bool)); table.Columns.Add("d", typeof(DateTime)); table.Rows.Add(@"""<>+-*//}{\\n", 1234567890); - table.Rows.Add(@"Hello World", -1234567890, false, DateTime.Now); + table.Rows.Add("Hello World", -1234567890, false, DateTime.Now); } MiniExcel.SaveAs(path, table); Assert.Equal("A1:D3", Helpers.GetFirstSheetDimensionRefValue(path)); @@ -695,8 +712,6 @@ public void SaveAsFileWithDimension() File.Delete(path); - - MiniExcel.SaveAs(path, table, printHeader: false); Assert.Equal("A1:D2", Helpers.GetFirstSheetDimensionRefValue(path)); File.Delete(path); @@ -709,8 +724,8 @@ public void SaveAsFileWithDimension() var table = new DataTable(); { table.Columns.Add("a", typeof(string)); - table.Rows.Add(@"A"); - table.Rows.Add(@"B"); + table.Rows.Add("A"); + table.Rows.Add("B"); } MiniExcel.SaveAs(path, table); Assert.Equal("A3", Helpers.GetFirstSheetDimensionRefValue(path)); @@ -718,7 +733,7 @@ public void SaveAsFileWithDimension() } } - [Fact()] + [Fact] public void SaveAsByDataTableTest() { { @@ -732,7 +747,7 @@ public void SaveAsByDataTableTest() table.Columns.Add("c", typeof(bool)); table.Columns.Add("d", typeof(DateTime)); table.Rows.Add(@"""<>+-*//}{\\n", 1234567890, true, now); - table.Rows.Add(@"Hello World", -1234567890, false, now.Date); + table.Rows.Add("Hello World", -1234567890, false, now.Date); } MiniExcel.SaveAs(path, table, sheetName: "R&D"); @@ -747,7 +762,7 @@ public void SaveAsByDataTableTest() Assert.True(ws.Cells["D1"].Value.ToString() == "d"); Assert.True(ws.Cells["A2"].Value.ToString() == @"""<>+-*//}{\\n"); - Assert.True(ws.Cells["B2"].Value.ToString() == @"1234567890"); + Assert.True(ws.Cells["B2"].Value.ToString() == "1234567890"); Assert.True(ws.Cells["C2"].Value.ToString() == true.ToString()); Assert.True(ws.Cells["D2"].Value.ToString() == now.ToString()); @@ -772,7 +787,7 @@ public void SaveAsByDataTableTest() } } - [Fact()] + [Fact] public void QueryByLINQExtensionsAvoidLargeFileOOMTest() { var path = "../../../../../benchmarks/MiniExcel.Benchmarks/Test1,000,000x10.xlsx"; @@ -801,7 +816,7 @@ public void EmptyTest() var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xlsx"); using (var connection = Db.GetConnection("Data Source=:memory:")) { - var rows = connection.Query(@"with cte as (select 1 id,2 val) select * from cte where 1=2"); + var rows = connection.Query("with cte as (select 1 id,2 val) select * from cte where 1=2"); MiniExcel.SaveAs(path, rows); } using (var stream = File.OpenRead(path)) @@ -925,7 +940,7 @@ public void SaveAsFrozenRowsAndColumnsTest() table.Columns.Add("c", typeof(bool)); table.Columns.Add("d", typeof(DateTime)); table.Rows.Add("some text", 1234567890, true, DateTime.Now); - table.Rows.Add(@"Hello World", -1234567890, false, DateTime.Now.Date); + table.Rows.Add("Hello World", -1234567890, false, DateTime.Now.Date); } var pathTable = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xlsx"); MiniExcel.SaveAs(pathTable, table, configuration: config); @@ -943,7 +958,7 @@ public void SaveAsFrozenRowsAndColumnsTest() } - [Fact()] + [Fact] public void SaveAsByDapperRows() { var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xlsx"); @@ -952,7 +967,7 @@ public void SaveAsByDapperRows() // Dapper Query using (var connection = Db.GetConnection("Data Source=:memory:")) { - var rows = connection.Query(@"select 'MiniExcel' as Column1,1 as Column2 union all select 'Github',2"); + var rows = connection.Query("select 'MiniExcel' as Column1,1 as Column2 union all select 'Github',2"); MiniExcel.SaveAs(path, rows); } @@ -973,7 +988,7 @@ public void SaveAsByDapperRows() // Empty using (var connection = Db.GetConnection("Data Source=:memory:")) { - var rows = connection.Query(@"with cte as (select 'MiniExcel' as Column1,1 as Column2 union all select 'Github',2)select * from cte where 1=2").ToList(); + var rows = connection.Query("with cte as (select 'MiniExcel' as Column1,1 as Column2 union all select 'Github',2)select * from cte where 1=2").ToList(); MiniExcel.SaveAs(path, rows); } @@ -996,7 +1011,7 @@ public void SaveAsByDapperRows() // ToList using (var connection = Db.GetConnection("Data Source=:memory:")) { - var rows = connection.Query(@"select 'MiniExcel' as Column1,1 as Column2 union all select 'Github',2").ToList(); + var rows = connection.Query("select 'MiniExcel' as Column1,1 as Column2 union all select 'Github',2").ToList(); MiniExcel.SaveAs(path, rows); } @@ -1032,16 +1047,16 @@ public class Demo public string Column1 { get; set; } public decimal Column2 { get; set; } } - [Fact()] + [Fact] public void QueryByStrongTypeParameterTest() { var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xlsx"); - var values = new List() - { - new Demo { Column1= "MiniExcel" ,Column2 = 1 }, - new Demo { Column1 = "Github", Column2 = 2 } - }; + List values = + [ + new() { Column1 = "MiniExcel", Column2 = 1 }, + new() { Column1 = "Github", Column2 = 2 } + ]; MiniExcel.SaveAs(path, values); @@ -1058,16 +1073,16 @@ public void QueryByStrongTypeParameterTest() File.Delete(path); } - [Fact()] + [Fact] public void QueryByDictionaryStringAndObjectParameterTest() { var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xlsx"); - var values = new List>() - { - new Dictionary{{ "Column1", "MiniExcel" }, { "Column2", 1 } }, - new Dictionary{{ "Column1", "Github" }, { "Column2", 2 } } - }; + List> values = + [ + new() { { "Column1", "MiniExcel" }, { "Column2", 1 } }, + new() { { "Column1", "Github" }, { "Column2", 2 } } + ]; MiniExcel.SaveAs(path, values); @@ -1084,7 +1099,7 @@ public void QueryByDictionaryStringAndObjectParameterTest() File.Delete(path); } - [Fact()] + [Fact] public void SQLiteInsertTest() { // Avoid SQL Insert Large Size Xlsx OOM @@ -1094,7 +1109,7 @@ public void SQLiteInsertTest() using (var connection = new SQLiteConnection(connectionString)) { - connection.Execute(@"create table T (A varchar(20),B varchar(20));"); + connection.Execute("create table T (A varchar(20),B varchar(20));"); } using (var connection = new SQLiteConnection(connectionString)) @@ -1277,7 +1292,7 @@ public void SavaAsClosedXmlCanReadTest() Assert.True(ws.Cell("C1").Value.ToString() == "c"); Assert.True(ws.Cell("A2").Value.ToString() == @"""<>+-*//}{\\n"); - Assert.True(ws.Cell("B2").Value.ToString() == @"1234567890"); + Assert.True(ws.Cell("B2").Value.ToString() == "1234567890"); Assert.True(ws.Cell("C2").Value.ToString() == true.ToString()); Assert.True(ws.Cell("D2").Value.ToString() == now.ToString()); @@ -1531,7 +1546,7 @@ public void InsertSheetTest() Assert.True(sheet1.Cells["D1"].Value.ToString() == "d"); Assert.True(sheet1.Cells["A2"].Value.ToString() == @"""<>+-*//}{\\n"); - Assert.True(sheet1.Cells["B2"].Value.ToString() == @"1234567890"); + Assert.True(sheet1.Cells["B2"].Value.ToString() == "1234567890"); Assert.True(sheet1.Cells["C2"].Value.ToString() == true.ToString()); Assert.True(sheet1.Cells["D2"].Value.ToString() == now.ToString()); @@ -1615,8 +1630,8 @@ public void InsertSheetTest() FastMode = true, AutoFilter = false, TableStyles = TableStyles.None, - DynamicColumns = new[] - { + DynamicColumns = + [ new DynamicExcelColumn("Column2") { Name = "Its Date", @@ -1624,7 +1639,7 @@ public void InsertSheetTest() Width = 150, Format = "dd.mm.yyyy hh:mm:ss", } - } + ] }); Assert.Equal(2, rowsWritten);