diff --git a/src/MiniExcel/Csv/CsvReader.cs b/src/MiniExcel/Csv/CsvReader.cs index 31ca4a5718d18cc0f40df6b4e968187dd2f4a3f9..2cb1cfa21afe240a411b053e12603e135b76b377 100644 --- a/src/MiniExcel/Csv/CsvReader.cs +++ b/src/MiniExcel/Csv/CsvReader.cs @@ -91,5 +91,68 @@ public Task> QueryAsync(string sheetName, string startCell,Can public void Dispose() { } + + //2022-09-24 excelReaderRange + #region Range + public IEnumerable> QueryRange(bool useHeaderRow, string sheetName, string startCell, string endCell) + { + if (startCell != "A1") + throw new NotImplementedException("CSV not Implement startCell"); + if (_stream.CanSeek) + _stream.Position = 0; + var reader = _config.StreamReaderFunc(_stream); + { + var row = string.Empty; + string[] read; + var firstRow = true; + Dictionary headRows = new Dictionary(); + while ((row = reader.ReadLine()) != null) + { + read = Split(row); + + //header + if (useHeaderRow) + { + if (firstRow) + { + firstRow = false; + for (int i = 0; i <= read.Length - 1; i++) + headRows.Add(i, read[i]); + continue; + } + + var cell = CustomPropertyHelper.GetEmptyExpandoObject(headRows); + for (int i = 0; i <= read.Length - 1; i++) + cell[headRows[i]] = read[i]; + + yield return cell; + continue; + } + + + //body + { + var cell = CustomPropertyHelper.GetEmptyExpandoObject(read.Length - 1, 0); + for (int i = 0; i <= read.Length - 1; i++) + cell[ColumnHelper.GetAlphabetColumnName(i)] = read[i]; + yield return cell; + } + } + } + } + public IEnumerable QueryRange(string sheetName, string startCell, string endCel) where T : class, new() + { + return ExcelOpenXmlSheetReader.QueryImplRange(QueryRange(false, sheetName, startCell, endCel), startCell, endCel, this._config); + } + public Task>> QueryAsyncRange(bool UseHeaderRow, string sheetName, string startCell, string endCel, CancellationToken cancellationToken = default(CancellationToken)) + { + return Task.Run(() => QueryRange(UseHeaderRow, sheetName, startCell, endCel), cancellationToken); + } + + public Task> QueryAsyncRange(string sheetName, string startCell, string endCel, CancellationToken cancellationToken = default(CancellationToken)) where T : class, new() + { + return Task.Run(() => Query(sheetName, startCell), cancellationToken); + } + #endregion } } diff --git a/src/MiniExcel/IExcelReader.cs b/src/MiniExcel/IExcelReader.cs index 6fa78fbeec5c2492aab100f5ed5d79584c1132b7..94feb3e45fdc6edb2ed0403ab6b9c8a1b7993ec5 100644 --- a/src/MiniExcel/IExcelReader.cs +++ b/src/MiniExcel/IExcelReader.cs @@ -15,5 +15,15 @@ internal interface IExcelReader: IDisposable IEnumerable Query(string sheetName, string startCell) where T : class, new(); Task>> QueryAsync(bool UseHeaderRow, string sheetName, string startCell,CancellationToken cancellationToken = default(CancellationToken)); Task> QueryAsync(string sheetName, string startCell,CancellationToken cancellationToken = default(CancellationToken)) where T : class, new(); + + // + IEnumerable> QueryRange(bool UseHeaderRow, string sheetName, string startCell, string endCell); + IEnumerable QueryRange(string sheetName, string startCell, string endCell) where T : class, new(); + Task>> QueryAsyncRange(bool UseHeaderRow, string sheetName, string startCell, string endCell, CancellationToken cancellationToken = default(CancellationToken)); + Task> QueryAsyncRange(string sheetName, string startCell, string endCell, CancellationToken cancellationToken = default(CancellationToken)) where T : class, new(); + // + + + } } diff --git a/src/MiniExcel/MiniExcel.cs b/src/MiniExcel/MiniExcel.cs index 298fb228f080c126d560f1788efd50bbdd261188..f011c0ebd36253eb877b31f89dce39779387dbfd 100644 --- a/src/MiniExcel/MiniExcel.cs +++ b/src/MiniExcel/MiniExcel.cs @@ -47,7 +47,7 @@ public static void Insert(this Stream stream, object value, string sheetName = " ExcelWriterFactory.GetProvider(stream, v, sheetName, excelType, configuration, false).Insert(); } - public static void SaveAs(string path, object value, bool printHeader = true, string sheetName = "Sheet1", ExcelType excelType = ExcelType.UNKNOWN, IConfiguration configuration = null,bool overwriteFile = false) + public static void SaveAs(string path, object value, bool printHeader = true, string sheetName = "Sheet1", ExcelType excelType = ExcelType.UNKNOWN, IConfiguration configuration = null, bool overwriteFile = false) { if (Path.GetExtension(path).ToLowerInvariant() == ".xlsm") throw new NotSupportedException("MiniExcel SaveAs not support xlsm"); @@ -77,6 +77,7 @@ public static IEnumerable Query(this Stream stream, string sheetName = nul } } + //1 public static IEnumerable Query(string path, bool useHeaderRow = false, string sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IConfiguration configuration = null) { using (var stream = FileHelper.OpenSharedRead(path)) @@ -84,6 +85,7 @@ public static IEnumerable Query(string path, bool useHeaderRow = false, yield return item; } + //2 public static IEnumerable Query(this Stream stream, bool useHeaderRow = false, string sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IConfiguration configuration = null) { using (var excelReader = ExcelReaderFactory.GetProvider(stream, ExcelTypeHelper.GetExcelType(stream, excelType), configuration)) @@ -92,10 +94,42 @@ public static IEnumerable Query(this Stream stream, bool useHeaderRow = (dict, p) => { dict.Add(p); return dict; }); } + #region range + + //3 + /// + /// + /// + /// 路径 + /// 表头 + /// 表名称 + /// excel类型 + /// 开始单元格,支持为空读所有,默认A1,或者B列,或者B2单元格 + /// 结束单元格,支持为空读所有,或者为D别,或者D2单元格 + /// 配置 + /// + public static IEnumerable QueryRange(string path, bool useHeaderRow = false, string sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "a1", string endCell = "", IConfiguration configuration = null) + { + using (var stream = FileHelper.OpenSharedRead(path)) + foreach (var item in QueryRange(stream, useHeaderRow, sheetName, ExcelTypeHelper.GetExcelType(path, excelType), startCell == "" ? "a1" : startCell, endCell, configuration)) + yield return item; + } + + //4 + public static IEnumerable QueryRange(this Stream stream, bool useHeaderRow = false, string sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "a1", string endCell = "", IConfiguration configuration = null) + { + using (var excelReader = ExcelReaderFactory.GetProvider(stream, ExcelTypeHelper.GetExcelType(stream, excelType), configuration)) + foreach (var item in excelReader.QueryRange(useHeaderRow, sheetName, startCell == "" ? "a1" : startCell, endCell)) + yield return item.Aggregate(new ExpandoObject() as IDictionary, + (dict, p) => { dict.Add(p); return dict; }); + } + + #endregion range + public static void SaveAsByTemplate(string path, string templatePath, object value, IConfiguration configuration = null) { using (var stream = File.Create(path)) - SaveAsByTemplate(stream, templatePath, value,configuration); + SaveAsByTemplate(stream, templatePath, value, configuration); } public static void SaveAsByTemplate(string path, byte[] templateBytes, object value, IConfiguration configuration = null) @@ -122,9 +156,10 @@ public static DataTable QueryAsDataTable(string path, bool useHeaderRow = true, { using (var stream = FileHelper.OpenSharedRead(path)) { - return QueryAsDataTable(stream, useHeaderRow, sheetName, excelType:ExcelTypeHelper.GetExcelType(path, excelType), startCell, configuration); + return QueryAsDataTable(stream, useHeaderRow, sheetName, excelType: ExcelTypeHelper.GetExcelType(path, excelType), startCell, configuration); } } + public static DataTable QueryAsDataTable(this Stream stream, bool useHeaderRow = true, string sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IConfiguration configuration = null) { if (sheetName == null && excelType != ExcelType.CSV) /*Issue #279*/ @@ -132,7 +167,7 @@ public static DataTable QueryAsDataTable(this Stream stream, bool useHeaderRow = var dt = new DataTable(sheetName); var first = true; - var rows = ExcelReaderFactory.GetProvider(stream, ExcelTypeHelper.GetExcelType(stream, excelType),configuration).Query(useHeaderRow, sheetName, startCell); + var rows = ExcelReaderFactory.GetProvider(stream, ExcelTypeHelper.GetExcelType(stream, excelType), configuration).Query(useHeaderRow, sheetName, startCell); var keys = new List(); foreach (IDictionary row in rows) @@ -175,7 +210,7 @@ public static List GetSheetNames(string path) public static List GetSheetNames(this Stream stream) { var archive = new ExcelOpenXmlZip(stream); - return new ExcelOpenXmlSheetReader(stream,null).GetWorkbookRels(archive.entries).Select(s => s.Name).ToList(); + return new ExcelOpenXmlSheetReader(stream, null).GetWorkbookRels(archive.entries).Select(s => s.Name).ToList(); } public static ICollection GetColumns(string path, bool useHeaderRow = false, string sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IConfiguration configuration = null) @@ -217,4 +252,4 @@ public static void ConvertXlsxToCsv(Stream xlsx, Stream csv) SaveAs(csv, value, printHeader: false, excelType: ExcelType.CSV); } } -} +} \ No newline at end of file diff --git a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetReader.cs b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetReader.cs index 9d48994903bd00b01d1b231dad7a68d11f5dc7f8..860c6fe2c37a6e0e95837618ebeee423c47ed186 100644 --- a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetReader.cs +++ b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetReader.cs @@ -16,6 +16,8 @@ namespace MiniExcelLibs.OpenXml { internal class ExcelOpenXmlSheetReader : IExcelReader { + #region MyRegion + private bool _disposed = false; private static readonly string[] _ns = { Config.SpreadsheetmlXmlns, Config.SpreadsheetmlXmlStrictns }; private static readonly string[] _relationshiopNs = { Config.SpreadsheetmlXmlRelationshipns, Config.SpreadsheetmlXmlStrictRelationshipns }; @@ -25,6 +27,7 @@ internal class ExcelOpenXmlSheetReader : IExcelReader private ExcelOpenXmlStyles _style; private readonly ExcelOpenXmlZip _archive; private OpenXmlConfiguration _config; + private static readonly XmlReaderSettings _xmlSettings = new XmlReaderSettings { IgnoreComments = true, @@ -43,9 +46,10 @@ public ExcelOpenXmlSheetReader(Stream stream, IConfiguration configuration) { if (!ReferenceHelper.ParseReference(startCell, out var startColumnIndex, out var startRowIndex)) throw new InvalidDataException($"startCell {startCell} is Invalid"); - startColumnIndex--; startRowIndex--; + startColumnIndex--; + startRowIndex--; - // if sheets count > 1 need to read xl/_rels/workbook.xml.rels + // 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) ); @@ -67,8 +71,8 @@ public ExcelOpenXmlSheetReader(Stream stream, IConfiguration configuration) else sheetEntry = sheets.Single(); - #region MergeCells + if (_config.FillMergedCells) { _mergeCells = new MergeCells(); @@ -120,10 +124,8 @@ public ExcelOpenXmlSheetReader(Stream stream, IConfiguration configuration) } } } - #endregion - - + #endregion MergeCells // TODO: need to optimize performance var withoutCR = false; @@ -211,7 +213,6 @@ public ExcelOpenXmlSheetReader(Stream stream, IConfiguration configuration) maxColumnIndex = Math.Max(maxColumnIndex, cellIndex); } - if (!XmlReaderHelper.SkipContent(reader)) break; } @@ -228,13 +229,9 @@ public ExcelOpenXmlSheetReader(Stream stream, IConfiguration configuration) break; } } - } } - - - using (var sheetStream = sheetEntry.Open()) using (XmlReader reader = XmlReader.Create(sheetStream, _xmlSettings)) { @@ -276,7 +273,6 @@ public ExcelOpenXmlSheetReader(Stream stream, IConfiguration configuration) continue; } - // fill empty rows if (!(nextRowIndex < startRowIndex)) { @@ -350,7 +346,6 @@ public ExcelOpenXmlSheetReader(Stream stream, IConfiguration configuration) continue; } - yield return cell; } } @@ -359,7 +354,6 @@ public ExcelOpenXmlSheetReader(Stream stream, IConfiguration configuration) break; } } - } else if (!XmlReaderHelper.SkipContent(reader)) { @@ -420,7 +414,6 @@ public static IEnumerable QueryImpl(IEnumerable new { s,i}).ToDictionary(_=>_.s,_=>_.i); @@ -437,7 +430,7 @@ public static IEnumerable QueryImpl(IEnumerable _.key, _ => _.idx); //TODO: alert don't duplicate column name - props = CustomPropertyHelper.GetExcelCustomPropertyInfos(type, keys,configuration); + props = CustomPropertyHelper.GetExcelCustomPropertyInfos(type, keys, configuration); first = false; continue; } @@ -461,7 +454,6 @@ public static IEnumerable QueryImpl(IEnumerable> QueryAsync(string sheetName, string startCe return await Task.Run(() => Query(sheetName, startCell), cancellationToken).ConfigureAwait(false); } - ~ExcelOpenXmlSheetReader() + ~ExcelOpenXmlSheetReader() { Dispose(false); } @@ -761,5 +756,455 @@ protected virtual void Dispose(bool disposing) _disposed = true; } } + + #endregion MyRegion + + #region ReaderRange + + public IEnumerable> QueryRange(bool useHeaderRow, string sheetName, string startCell, string endCell) + { + //2022-09-27 + if (!ReferenceHelper.ParseReference(startCell, out var startColumnIndex, out var startRowIndex) == false ? true : true) + { + //throw new InvalidDataException($"startCell {startCell} is Invalid"); + startColumnIndex--; + startRowIndex--; + if (startRowIndex < 0) + { + startRowIndex = 0; + } + if (startColumnIndex < 0) + { + startColumnIndex = 0; + } + } + + //2022-09-24 获取结束单元格的,行,列 + if (!ReferenceHelper.ParseReference(endCell, out var endColumnIndex, out var endRowIndex) == false ? true : true) + { + //throw new InvalidDataException($"endCell {endCell} is Invalid"); + endColumnIndex--; + endRowIndex--; + } + + // 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) + ); + ZipArchiveEntry sheetEntry = null; + if (sheetName != null) + { + SetWorkbookRels(_archive.entries); + var s = _sheetRecords.SingleOrDefault(_ => _.Name == sheetName); + if (s == 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}"); + } + else if (sheets.Count() > 1) + { + SetWorkbookRels(_archive.entries); + var s = _sheetRecords[0]; + sheetEntry = sheets.Single(w => w.FullName == $"xl/{s.Path}" || w.FullName == $"/xl/{s.Path}"); + } + else + sheetEntry = sheets.Single(); + + #region MergeCells + + if (_config.FillMergedCells) + { + _mergeCells = new MergeCells(); + using (var sheetStream = sheetEntry.Open()) + using (XmlReader reader = XmlReader.Create(sheetStream, _xmlSettings)) + { + if (!XmlReaderHelper.IsStartElement(reader, "worksheet", _ns)) + yield break; + while (reader.Read()) + { + if (XmlReaderHelper.IsStartElement(reader, "mergeCells", _ns)) + { + if (!XmlReaderHelper.ReadFirstContent(reader)) + yield break; + while (!reader.EOF) + { + if (XmlReaderHelper.IsStartElement(reader, "mergeCell", _ns)) + { + var @ref = reader.GetAttribute("ref"); + var refs = @ref.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); + + _mergeCells.MergesValues.Add(refs[0], null); + + // foreach range + var isFirst = true; + for (int x = x1; x <= x2; x++) + { + for (int y = y1; y <= y2; y++) + { + if (!isFirst) + _mergeCells.MergesMap.Add(ReferenceHelper.ConvertXyToCell(x, y), refs[0]); + isFirst = false; + } + } + + XmlReaderHelper.SkipContent(reader); + } + else if (!XmlReaderHelper.SkipContent(reader)) + { + break; + } + } + } + } + } + } + + #endregion MergeCells + + // TODO: need to optimize performance + var withoutCR = false; + var maxRowIndex = -1; + var maxColumnIndex = -1; + + //Q. why need 3 times openstream merge one open read? A. no, zipstream can't use position = 0 + using (var sheetStream = sheetEntry.Open()) + using (XmlReader reader = XmlReader.Create(sheetStream, _xmlSettings)) + { + while (reader.Read()) + { + if (XmlReaderHelper.IsStartElement(reader, "c", _ns)) + { + var r = reader.GetAttribute("r"); + if (r != null) + { + if (ReferenceHelper.ParseReference(r, out var column, out var row)) + { + column--; + row--; + maxRowIndex = Math.Max(maxRowIndex, row); + maxColumnIndex = Math.Max(maxColumnIndex, column); + } + } + else + { + withoutCR = true; + break; + } + } + //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)) + { + //2022-09-24 Range + //var @ref = reader.GetAttribute("ref"); + var @ref = startCell + ":" + endCell; + if (endCell == "" || startCell == "") + { + @ref = reader.GetAttribute("ref"); + } + if (string.IsNullOrEmpty(@ref)) + throw new InvalidOperationException("Without sheet dimension data"); + var rs = @ref.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 + throw new InvalidOperationException("Invaild sheet dimension start data"); + } + } + } + + if (withoutCR) + { + using (var sheetStream = sheetEntry.Open()) + using (XmlReader reader = XmlReader.Create(sheetStream, _xmlSettings)) + { + if (!XmlReaderHelper.IsStartElement(reader, "worksheet", _ns)) + yield break; + if (!XmlReaderHelper.ReadFirstContent(reader)) + yield break; + while (!reader.EOF) + { + if (XmlReaderHelper.IsStartElement(reader, "sheetData", _ns)) + { + if (!XmlReaderHelper.ReadFirstContent(reader)) + continue; + + while (!reader.EOF) + { + if (XmlReaderHelper.IsStartElement(reader, "row", _ns)) + { + maxRowIndex++; + + if (!XmlReaderHelper.ReadFirstContent(reader)) + continue; + + //Cells + { + var cellIndex = -1; + while (!reader.EOF) + { + if (XmlReaderHelper.IsStartElement(reader, "c", _ns)) + { + cellIndex++; + maxColumnIndex = Math.Max(maxColumnIndex, cellIndex); + } + + if (!XmlReaderHelper.SkipContent(reader)) + break; + } + } + } + else if (!XmlReaderHelper.SkipContent(reader)) + { + break; + } + } + } + else if (!XmlReaderHelper.SkipContent(reader)) + { + break; + } + } + } + } + + using (var sheetStream = sheetEntry.Open()) + using (XmlReader reader = XmlReader.Create(sheetStream, _xmlSettings)) + { + if (!XmlReaderHelper.IsStartElement(reader, "worksheet", _ns)) + yield break; + + if (!XmlReaderHelper.ReadFirstContent(reader)) + yield break; + + while (!reader.EOF) + { + if (XmlReaderHelper.IsStartElement(reader, "sheetData", _ns)) + { + if (!XmlReaderHelper.ReadFirstContent(reader)) + continue; + + Dictionary headRows = new Dictionary(); + int rowIndex = -1; + int nextRowIndex = 0; + bool isFirstRow = true; + while (!reader.EOF) + { + if (XmlReaderHelper.IsStartElement(reader, "row", _ns)) + { + nextRowIndex = rowIndex + 1; + if (int.TryParse(reader.GetAttribute("r"), out int arValue)) + rowIndex = arValue - 1; // The row attribute is 1-based + else + rowIndex++; + + // row -> c + if (!XmlReaderHelper.ReadFirstContent(reader)) + continue; + + //2022-09-24跳过endcell结束单元格所在的行 + if (rowIndex > endRowIndex && endRowIndex > 0) + { + break; + } + // 跳过startcell起始单元格所在的行 + if (rowIndex < startRowIndex) + { + XmlReaderHelper.SkipToNextSameLevelDom(reader); + continue; + } + + // fill empty rows + if (!(nextRowIndex < startRowIndex)) + { + if (nextRowIndex < rowIndex) + { + for (int i = nextRowIndex; i < rowIndex; i++) + { + yield return GetCell(useHeaderRow, maxColumnIndex, headRows, startColumnIndex); + } + } + } + + // Set Cells + { + var cell = GetCell(useHeaderRow, maxColumnIndex, headRows, startColumnIndex); + var columnIndex = withoutCR ? -1 : 0; + while (!reader.EOF) + { + 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); + + if (_config.FillMergedCells) + { + if (_mergeCells.MergesValues.ContainsKey(aR)) + { + _mergeCells.MergesValues[aR] = cellValue; + } + else if (_mergeCells.MergesMap.ContainsKey(aR)) + { + var mergeKey = _mergeCells.MergesMap[aR]; + object mergeValue = null; + if (_mergeCells.MergesValues.ContainsKey(mergeKey)) + mergeValue = _mergeCells.MergesValues[mergeKey]; + cellValue = mergeValue; + } + } + ////2022-09-24跳过endcell结束单元格所以在的列 + + //跳过startcell起始单元格所在的列 + if (columnIndex < startColumnIndex || columnIndex > endColumnIndex && endColumnIndex > 0) + 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; + + // 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); + } + } + else if (!XmlReaderHelper.SkipContent(reader)) + break; + } + + if (isFirstRow) + { + isFirstRow = false; // for startcell logic + if (useHeaderRow) + continue; + } + + yield return cell; + } + } + else if (!XmlReaderHelper.SkipContent(reader)) + { + break; + } + } + } + else if (!XmlReaderHelper.SkipContent(reader)) + { + break; + } + } + } + } + + 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); + } + + public static IEnumerable QueryImplRange(IEnumerable> values, string startCell, string endCell, Configuration configuration) where T : class, new() + { + var type = typeof(T); + + 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); + //TODO: alert don't duplicate column name + props = CustomPropertyHelper.GetExcelCustomPropertyInfos(type, keys, configuration); + first = false; + continue; + } + var v = new T(); + foreach (var pInfo in props) + { + if (pInfo.ExcelColumnAliases != null) + { + foreach (var alias in pInfo.ExcelColumnAliases) + { + if (headersDic.ContainsKey(alias)) + { + object newV = null; + object itemValue = item[keys[headersDic[alias]]]; + + if (itemValue == null) + continue; + + newV = TypeHelper.TypeMapping(v, pInfo, newV, itemValue, rowIndex, startCell, configuration); + } + } + } + + //Q: Why need to check every time? A: it needs to check everytime, because it's dictionary + { + object newV = null; + 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]]]; + + if (itemValue == null) + continue; + + newV = TypeHelper.TypeMapping(v, pInfo, newV, itemValue, rowIndex, startCell, configuration); + } + } + rowIndex++; + yield return v; + } + } + + 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); + } + + public async Task> QueryAsyncRange(string sheetName, string startCell, string endCell, CancellationToken cancellationToken = default(CancellationToken)) where T : class, new() + { + return await Task.Run(() => Query(sheetName, startCell), cancellationToken).ConfigureAwait(false); + } + + #endregion ReaderRange } -} +} \ No newline at end of file diff --git a/src/MiniExcel/Utils/ReferenceHelper.cs b/src/MiniExcel/Utils/ReferenceHelper.cs index 4cd2db7c17ef0325e3bb0dc9957ba1c1026aaaac..acfe5a4837833e2b94739aaa40c2b9d7eb7cbaa7 100644 --- a/src/MiniExcel/Utils/ReferenceHelper.cs +++ b/src/MiniExcel/Utils/ReferenceHelper.cs @@ -72,7 +72,9 @@ internal static partial class ReferenceHelper /// The row, 1-based. public static bool ParseReference(string value, out int column, out int row) { - column = 0; + //2022-09-22 参数统一转换为大写,避免传入小写的参数时出错 + value = value.ToUpper(); + column = 0; var position = 0; const int offset = 'A' - 1;