From 87d6eb65b35952bc4ecc0e0e8eb0f1920b0a6292 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E7=A6=BB=E6=AD=8C=E7=AC=91?= <276397082@qq.com>
Date: Thu, 22 Sep 2022 19:29:41 +0800
Subject: [PATCH 1/2] =?UTF-8?q?//2022-09-22=20=E5=8F=82=E6=95=B0=E7=BB=9F?=
=?UTF-8?q?=E4=B8=80=E8=BD=AC=E6=8D=A2=E4=B8=BA=E5=A4=A7=E5=86=99=EF=BC=8C?=
=?UTF-8?q?=E9=81=BF=E5=85=8D=E4=BC=A0=E5=85=A5=E5=B0=8F=E5=86=99=E7=9A=84?=
=?UTF-8?q?=E5=8F=82=E6=95=B0=E6=97=B6=E5=87=BA=E9=94=99?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/MiniExcel/Utils/ReferenceHelper.cs | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/MiniExcel/Utils/ReferenceHelper.cs b/src/MiniExcel/Utils/ReferenceHelper.cs
index 4cd2db7..acfe5a4 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;
--
Gitee
From 9257aa901abc199f8d074117c39c214a6602da26 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E7=A6=BB=E6=AD=8C=E7=AC=91?= <276397082@qq.com>
Date: Tue, 27 Sep 2022 11:23:50 +0800
Subject: [PATCH 2/2] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=8C=89=E5=BC=80?=
=?UTF-8?q?=E5=A7=8B=E5=8D=95=E5=85=83=E6=A0=BC=E5=92=8C=E7=BB=93=E6=9D=9F?=
=?UTF-8?q?=E5=8D=95=E5=85=83=E6=A0=BC=E8=8C=83=E5=9B=B4=E8=AF=BB=E5=8F=96?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/MiniExcel/Csv/CsvReader.cs | 63 +++
src/MiniExcel/IExcelReader.cs | 10 +
src/MiniExcel/MiniExcel.cs | 47 +-
.../OpenXml/ExcelOpenXmlSheetReader.cs | 489 +++++++++++++++++-
4 files changed, 581 insertions(+), 28 deletions(-)
diff --git a/src/MiniExcel/Csv/CsvReader.cs b/src/MiniExcel/Csv/CsvReader.cs
index 31ca4a5..2cb1cfa 100644
--- a/src/MiniExcel/Csv/CsvReader.cs
+++ b/src/MiniExcel/Csv/CsvReader.cs
@@ -91,5 +91,68 @@ private string[] Split(string row)
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 6fa78fb..94feb3e 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 298fb22..f011c0e 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 void SaveAs(this Stream stream, object value, bool printHeader = t
}
}
+ //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 9d48994..860c6fe 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 IEnumerable> Query(bool useHeaderRow, string
{
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 IEnumerable> Query(bool useHeaderRow, string
else
sheetEntry = sheets.Single();
-
#region MergeCells
+
if (_config.FillMergedCells)
{
_mergeCells = new MergeCells();
@@ -120,10 +124,8 @@ public IEnumerable> Query(bool useHeaderRow, string
}
}
}
- #endregion
-
-
+ #endregion MergeCells
// TODO: need to optimize performance
var withoutCR = false;
@@ -211,7 +213,6 @@ public IEnumerable> Query(bool useHeaderRow, string
maxColumnIndex = Math.Max(maxColumnIndex, cellIndex);
}
-
if (!XmlReaderHelper.SkipContent(reader))
break;
}
@@ -228,13 +229,9 @@ public IEnumerable> Query(bool useHeaderRow, string
break;
}
}
-
}
}
-
-
-
using (var sheetStream = sheetEntry.Open())
using (XmlReader reader = XmlReader.Create(sheetStream, _xmlSettings))
{
@@ -276,7 +273,6 @@ public IEnumerable> Query(bool useHeaderRow, string
continue;
}
-
// fill empty rows
if (!(nextRowIndex < startRowIndex))
{
@@ -350,7 +346,6 @@ public IEnumerable> Query(bool useHeaderRow, string
continue;
}
-
yield return cell;
}
}
@@ -359,7 +354,6 @@ public IEnumerable> Query(bool useHeaderRow, string
break;
}
}
-
}
else if (!XmlReaderHelper.SkipContent(reader))
{
@@ -420,7 +414,6 @@ private void SetCellsValueAndHeaders(object cellValue, bool useHeaderRow, ref Di
var rowIndex = 0;
foreach (var item in values)
{
-
if (first)
{
keys = item.Keys.ToArray();//.Select((s, i) => new { s,i}).ToDictionary(_=>_.s,_=>_.i);
@@ -437,7 +430,7 @@ private void SetCellsValueAndHeaders(object cellValue, bool useHeaderRow, ref Di
)
).ToDictionary(_ => _.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 @@ private void SetCellsValueAndHeaders(object cellValue, bool useHeaderRow, ref Di
}
}
-
//Q: Why need to check every time? A: it needs to check everytime, because it's dictionary
{
object newV = null;
@@ -644,8 +636,6 @@ private object ReadCellAndSetColumnIndex(XmlReader reader, ref int columnIndex,
}
}
-
-
return value;
}
@@ -668,6 +658,7 @@ private void ConvertCellValue(string rawValue, string aT, int xfIndex, out objec
}
value = null;
return;
+
case "inlineStr":
case "str":
//TODO: it will unbox,box
@@ -675,7 +666,7 @@ private void ConvertCellValue(string rawValue, string aT, int xfIndex, out objec
if (_config.EnableConvertByteArray)
{
//if str start with "data:image/png;base64," then convert to byte[] https://github.com/shps951023/MiniExcel/issues/318
- if (v != null && v.StartsWith("@@@fileid@@@,",StringComparison.Ordinal))
+ if (v != null && v.StartsWith("@@@fileid@@@,", StringComparison.Ordinal))
{
var path = v.Substring(13);
var entry = _archive.GetEntry(path);
@@ -697,9 +688,11 @@ private void ConvertCellValue(string rawValue, string aT, int xfIndex, out objec
value = v;
}
return;
+
case "b":
value = rawValue == "1";
return;
+
case "d":
if (DateTime.TryParseExact(rawValue, "yyyy-MM-dd", invariantCulture, DateTimeStyles.AllowLeadingWhite | DateTimeStyles.AllowTrailingWhite, out var date))
{
@@ -709,9 +702,11 @@ private void ConvertCellValue(string rawValue, string aT, int xfIndex, out objec
value = rawValue;
return;
+
case "e":
value = rawValue;
return;
+
default:
if (double.TryParse(rawValue, style, invariantCulture, out var n))
{
@@ -734,7 +729,7 @@ private void ConvertCellValue(string rawValue, string aT, int xfIndex, out objec
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
--
Gitee