- [New] Query、GetColumns support startCell #147
- [New] GetColumns support read headers
This commit is contained in:
wei 2021-05-06 15:47:08 +08:00
parent 10faf5d2d5
commit 33c3a191cc
17 changed files with 245 additions and 104 deletions

View File

@ -239,6 +239,14 @@ var table = MiniExcel.QueryAsDataTable(path, useHeaderRow: true);
#### 10. Specify the cell to start reading data
```csharp
MiniExcel.Query(path,useHeaderRow:true,startCell:"B3")
```
![image](https://user-images.githubusercontent.com/12729184/117260316-8593c400-ae81-11eb-9877-c087b7ac2b01.png)

View File

@ -241,6 +241,16 @@ var table = MiniExcel.QueryAsDataTable(path, useHeaderRow: true);
![image](https://user-images.githubusercontent.com/12729184/116673475-07917200-a9d6-11eb-947e-a6f68cce58df.png)
#### 10. 指定单元格开始读取数据
```csharp
MiniExcel.Query(path,useHeaderRow:true,startCell:"B3")
```
![image](https://user-images.githubusercontent.com/12729184/117260316-8593c400-ae81-11eb-9877-c087b7ac2b01.png)
### 写 Excel <a name="getstart2"></a>

View File

@ -243,6 +243,16 @@ var table = MiniExcel.QueryAsDataTable(path, useHeaderRow: true);
![image](https://user-images.githubusercontent.com/12729184/116673475-07917200-a9d6-11eb-947e-a6f68cce58df.png)
#### 10. 指定單元格開始讀取資料
```csharp
MiniExcel.Query(path,useHeaderRow:true,startCell:"B3")
```
![image](https://user-images.githubusercontent.com/12729184/117260316-8593c400-ae81-11eb-9877-c087b7ac2b01.png)
### 寫 Excel <a name="getstart2"></a>

View File

@ -6,6 +6,10 @@
---
### 0.14.0
- [New] Query、GetColumns support startCell #147
- [New] GetColumns support read headers
### 0.13.5
- [New] Support QueryAsDataTable method [#216](https://github.com/shps951023/MiniExcel/issues/216)
- [New] SaveAs support IDataReader value parameter [#211](https://github.com/shps951023/MiniExcel/issues/211)

View File

@ -7,6 +7,10 @@
---
### 0.14.0
- [New] Query、GetColumns 支持 startCell 能指定 cell 开始读取数据 #147
- [New] GetColumns 支持读取表头
### 0.13.5
- [New] 新增 QueryAsDataTable 方法 [#216](https://github.com/shps951023/MiniExcel/issues/216)
- [New] SaveAs 支持 IDataReader value 参数 [#211](https://github.com/shps951023/MiniExcel/issues/211)

View File

@ -7,6 +7,10 @@
---
### 0.14.0
- [New] Query、GetColumns 支持 startCell 能指定 cell 開始讀取資料 #147
- [New] GetColumns 支持讀取表頭
### 0.13.5
- [New] 新增 QueryAsDataTable 方法 [#216](https://github.com/shps951023/MiniExcel/issues/216)
- [New] SaveAs 支持 IDataReader value 參數 [#211](https://github.com/shps951023/MiniExcel/issues/211)

Binary file not shown.

Binary file not shown.

View File

@ -14,8 +14,10 @@ namespace MiniExcelLibs.Csv
{
this._stream = stream;
}
public IEnumerable<IDictionary<string, object>> Query(bool useHeaderRow, string sheetName, IConfiguration configuration)
public IEnumerable<IDictionary<string, object>> Query(bool useHeaderRow, string sheetName, string startCell, IConfiguration configuration)
{
if (startCell != "A1")
throw new NotImplementedException("CSV not Implement startCell");
var cf = configuration == null ? CsvConfiguration.DefaultConfiguration : (CsvConfiguration)configuration;
using (var reader = cf.GetStreamReaderFunc(_stream))
@ -52,7 +54,7 @@ namespace MiniExcelLibs.Csv
//body
{
var cell = Helpers.GetEmptyExpandoObject(read.Length - 1);
var cell = Helpers.GetEmptyExpandoObject(read.Length - 1,0);
for (int i = 0; i <= read.Length - 1; i++)
cell[Helpers.GetAlphabetColumnName(i)] = read[i];
yield return cell;
@ -61,7 +63,7 @@ namespace MiniExcelLibs.Csv
}
}
public IEnumerable<T> Query<T>(string sheetName, IConfiguration configuration) where T : class, new()
public IEnumerable<T> Query<T>(string sheetName, string startCell, IConfiguration configuration) where T : class, new()
{
var cf = configuration == null ? CsvConfiguration.DefaultConfiguration : (CsvConfiguration)configuration;

View File

@ -4,7 +4,7 @@ namespace MiniExcelLibs
{
internal interface IExcelReader
{
IEnumerable<IDictionary<string, object>> Query(bool UseHeaderRow, string sheetName, IConfiguration configuration);
IEnumerable<T> Query<T>(string sheetName, IConfiguration configuration) where T : class, new();
IEnumerable<IDictionary<string, object>> Query(bool UseHeaderRow, string sheetName,string startCell, IConfiguration configuration);
IEnumerable<T> Query<T>(string sheetName, string startCell, IConfiguration configuration) where T : class, new();
}
}

View File

@ -28,28 +28,28 @@
ExcelWriterFactory.GetProvider(stream, excelType).SaveAs(value, sheetName, printHeader, configuration);
}
public static IEnumerable<T> Query<T>(string path, string sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, IConfiguration configuration = null) where T : class, new()
public static IEnumerable<T> Query<T>(string path, string sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IConfiguration configuration = null) where T : class, new()
{
using (var stream = Helpers.OpenSharedRead(path))
foreach (var item in Query<T>(stream, sheetName, GetExcelType(path, excelType), configuration))
foreach (var item in Query<T>(stream, sheetName, GetExcelType(path, excelType), startCell, configuration))
yield return item; //Foreach yield return twice reason : https://stackoverflow.com/questions/66791982/ienumerable-extract-code-lazy-loading-show-stream-was-not-readable
}
public static IEnumerable<T> Query<T>(this Stream stream, string sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, IConfiguration configuration = null) where T : class, new()
public static IEnumerable<T> Query<T>(this Stream stream, string sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IConfiguration configuration = null) where T : class, new()
{
return ExcelReaderFactory.GetProvider(stream, GetExcelType(stream, excelType)).Query<T>(sheetName, configuration);
return ExcelReaderFactory.GetProvider(stream, GetExcelType(stream, excelType)).Query<T>(sheetName, startCell, configuration);
}
public static IEnumerable<dynamic> Query(string path, bool useHeaderRow = false, string sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, IConfiguration configuration = null)
public static IEnumerable<dynamic> Query(string path, bool useHeaderRow = false, string sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IConfiguration configuration = null)
{
using (var stream = Helpers.OpenSharedRead(path))
foreach (var item in Query(stream, useHeaderRow, sheetName, GetExcelType(path, excelType), configuration))
foreach (var item in Query(stream, useHeaderRow, sheetName, GetExcelType(path, excelType), startCell, configuration))
yield return item;
}
public static IEnumerable<dynamic> Query(this Stream stream, bool useHeaderRow = false, string sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, IConfiguration configuration = null)
public static IEnumerable<dynamic> Query(this Stream stream, bool useHeaderRow = false, string sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IConfiguration configuration = null)
{
return ExcelReaderFactory.GetProvider(stream, GetExcelType(stream, excelType)).Query(useHeaderRow, sheetName, configuration);
return ExcelReaderFactory.GetProvider(stream, GetExcelType(stream, excelType)).Query(useHeaderRow, sheetName, startCell, configuration);
}
public static IEnumerable<string> GetSheetNames(string path)
@ -66,15 +66,15 @@
yield return item.Name;
}
public static ICollection<string> GetColumns(string path)
public static ICollection<string> GetColumns(string path, bool useHeaderRow = false, string sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IConfiguration configuration = null)
{
using (var stream = Helpers.OpenSharedRead(path))
return (Query(stream).FirstOrDefault() as IDictionary<string, object>)?.Keys;
return GetColumns(stream, useHeaderRow, sheetName, excelType, startCell, configuration);
}
public static ICollection<string> GetColumns(this Stream stream)
public static ICollection<string> GetColumns(this Stream stream, bool useHeaderRow = false, string sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IConfiguration configuration = null)
{
return (Query(stream).FirstOrDefault() as IDictionary<string, object>)?.Keys;
return (Query(stream, useHeaderRow,sheetName,excelType,startCell,configuration).FirstOrDefault() as IDictionary<string, object>)?.Keys;
}
public static void SaveAsByTemplate(string path, string templatePath, object value)
@ -106,21 +106,21 @@
/// <summary>
/// This method is not recommended, because it'll load all data into memory.
/// </summary>
public static DataTable QueryAsDataTable(string path, bool useHeaderRow = true, string sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, IConfiguration configuration = null)
public static DataTable QueryAsDataTable(string path, bool useHeaderRow = true, string sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IConfiguration configuration = null)
{
using (var stream = Helpers.OpenSharedRead(path))
return QueryAsDataTable(stream, useHeaderRow, sheetName, GetExcelType(path, excelType), configuration);
return QueryAsDataTable(stream, useHeaderRow, sheetName, GetExcelType(path, excelType), startCell, configuration);
}
/// <summary>
/// This method is not recommended, because it'll load all data into memory.
/// </summary>
public static DataTable QueryAsDataTable(this Stream stream, bool useHeaderRow = true, string sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, IConfiguration configuration = null)
public static DataTable QueryAsDataTable(this Stream stream, bool useHeaderRow = true, string sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IConfiguration configuration = null)
{
var dt = new DataTable();
dt.TableName = sheetName;
var first = true;
var rows = ExcelReaderFactory.GetProvider(stream, GetExcelType(stream, excelType)).Query(useHeaderRow, sheetName, configuration);
var rows = ExcelReaderFactory.GetProvider(stream, GetExcelType(stream, excelType)).Query(useHeaderRow, sheetName,startCell, configuration);
foreach (IDictionary<string, object> row in rows)
{
if (first)

View File

@ -31,8 +31,12 @@ namespace MiniExcelLibs.OpenXml
_archive = new ExcelOpenXmlZip(stream);
}
public IEnumerable<IDictionary<string, object>> Query(bool UseHeaderRow, string sheetName, IConfiguration configuration)
public IEnumerable<IDictionary<string, object>> Query(bool useHeaderRow, string sheetName, string startCell, IConfiguration configuration)
{
if (!ReferenceHelper.ParseReference(startCell, out var startColumnIndex, out var startRowIndex))
throw new InvalidDataException($"startCell {startCell} is Invalid");
startColumnIndex--; startRowIndex--;
//TODO:need to optimize
SetSharedStrings();
@ -64,9 +68,9 @@ namespace MiniExcelLibs.OpenXml
var maxRowIndex = -1;
var maxColumnIndex = -1;
//TODO: merge one open read
using (var firstSheetEntryStream = sheetEntry.Open())
using (XmlReader reader = XmlReader.Create(firstSheetEntryStream, _xmlSettings))
//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())
{
@ -111,8 +115,8 @@ namespace MiniExcelLibs.OpenXml
if (withoutCR)
{
using (var firstSheetEntryStream = sheetEntry.Open())
using (XmlReader reader = XmlReader.Create(firstSheetEntryStream, _xmlSettings))
using (var sheetStream = sheetEntry.Open())
using (XmlReader reader = XmlReader.Create(sheetStream, _xmlSettings))
{
if (!reader.IsStartElement("worksheet", _ns))
yield break;
@ -167,8 +171,10 @@ namespace MiniExcelLibs.OpenXml
}
using (var firstSheetEntryStream = sheetEntry.Open())
using (XmlReader reader = XmlReader.Create(firstSheetEntryStream, _xmlSettings))
using (var sheetStream = sheetEntry.Open())
using (XmlReader reader = XmlReader.Create(sheetStream, _xmlSettings))
{
if (!reader.IsStartElement("worksheet", _ns))
yield break;
@ -186,6 +192,7 @@ namespace MiniExcelLibs.OpenXml
Dictionary<int, string> headRows = new Dictionary<int, string>();
int rowIndex = -1;
int nextRowIndex = 0;
bool isFirstRow = true;
while (!reader.EOF)
{
if (reader.IsStartElement("row", _ns))
@ -195,34 +202,45 @@ namespace MiniExcelLibs.OpenXml
rowIndex = arValue - 1; // The row attribute is 1-based
else
rowIndex++;
// row -> c
if (!XmlReaderHelper.ReadFirstContent(reader))
continue;
// startcell pass rows
if (rowIndex < startRowIndex )
{
XmlReaderHelper.SkipToNextSameLevelDom(reader);
continue;
}
// fill empty rows
if(!(nextRowIndex < startRowIndex))
{
if (nextRowIndex < rowIndex)
{
for (int i = nextRowIndex; i < rowIndex; i++)
if (UseHeaderRow)
yield return Helpers.GetEmptyExpandoObject(headRows);
else
yield return Helpers.GetEmptyExpandoObject(maxColumnIndex);
{
yield return GetCell(useHeaderRow, maxColumnIndex, headRows,startColumnIndex);
}
}
}
// Set Cells
{
var cell = UseHeaderRow ? Helpers.GetEmptyExpandoObject(headRows) : Helpers.GetEmptyExpandoObject(maxColumnIndex);
var cell = GetCell(useHeaderRow, maxColumnIndex, headRows, startColumnIndex);
var columnIndex = withoutCR ? -1 : 0;
while (!reader.EOF)
{
if (reader.IsStartElement("c", _ns))
{
var aS = reader.GetAttribute("s");
var cellValue = ReadCell(reader, columnIndex, withoutCR, out var _columnIndex);
columnIndex = _columnIndex;
var cellValue = ReadCellAndSetColumnIndex(reader, ref columnIndex, withoutCR,startColumnIndex);
if (columnIndex < startColumnIndex)
continue;
// TODO: bad code smell
if (!string.IsNullOrEmpty(aS)) // if c with s meaning is custom style need to check type by xl/style.xml
{
int xfIndex = -1;
@ -232,59 +250,27 @@ namespace MiniExcelLibs.OpenXml
// only when have s attribute then load styles xml data
if (_style == null)
_style = new ExcelOpenXmlStyles(_archive);
//if not using First Head then using 1,2,3 as index
if (UseHeaderRow)
{
if (rowIndex == 0)
{
var customStyleCellValue = _style.ConvertValueByStyleFormat(xfIndex, cellValue)?.ToString();
if (!string.IsNullOrWhiteSpace(customStyleCellValue))
headRows.Add(columnIndex, customStyleCellValue);
}
else
{
if (headRows.ContainsKey(columnIndex))
{
var key = headRows[columnIndex];
cell[key] = _style.ConvertValueByStyleFormat(xfIndex, cellValue);
}
}
}
else
{
//if not using First Head then using A,B,C as index
cell[Helpers.GetAlphabetColumnName(columnIndex)] = _style.ConvertValueByStyleFormat(xfIndex, cellValue);
}
var customStyleCellValue = _style.ConvertValueByStyleFormat(xfIndex, cellValue)?.ToString();
cellValue = _style.ConvertValueByStyleFormat(xfIndex, cellValue);
SetCellsValueAndHeaders(customStyleCellValue, cellValue, useHeaderRow, ref headRows, ref isFirstRow, ref cell, columnIndex);
}
else
{
if (UseHeaderRow)
{
if (rowIndex == 0)
{
var valueString = cellValue?.ToString();
if (!string.IsNullOrWhiteSpace(valueString))
headRows.Add(columnIndex, valueString);
}
else
{
if (headRows.ContainsKey(columnIndex))
cell[headRows[columnIndex]] = cellValue;
}
}
else
{
//if not using First Head then using A,B,C as index
cell[Helpers.GetAlphabetColumnName(columnIndex)] = cellValue;
}
SetCellsValueAndHeaders(cellValue?.ToString(), cellValue, useHeaderRow, ref headRows, ref isFirstRow, ref cell, columnIndex);
}
}
else if (!XmlReaderHelper.SkipContent(reader))
break;
}
if (UseHeaderRow && rowIndex == 0)
continue;
if (isFirstRow)
{
isFirstRow = false; // for startcell logic
if (useHeaderRow)
continue;
}
yield return cell;
}
@ -304,15 +290,44 @@ namespace MiniExcelLibs.OpenXml
}
}
private static IDictionary<string, object> GetCell(bool useHeaderRow, int maxColumnIndex, Dictionary<int, string> headRows, int startColumnIndex)
{
return useHeaderRow ? Helpers.GetEmptyExpandoObject(headRows) : Helpers.GetEmptyExpandoObject(maxColumnIndex, startColumnIndex);
}
public IEnumerable<T> Query<T>(string sheetName, IConfiguration configuration) where T : class, new()
private void SetCellsValueAndHeaders(string cellValueString,object cellValue, bool useHeaderRow, ref Dictionary<int, string> headRows, ref bool isFirstRow, ref IDictionary<string, object> cell, int columnIndex)
{
if (useHeaderRow)
{
if (isFirstRow) // for startcell logic
{
if (!string.IsNullOrWhiteSpace(cellValueString))
headRows.Add(columnIndex, cellValueString);
}
else
{
if (headRows.ContainsKey(columnIndex))
{
var key = headRows[columnIndex];
cell[key] = cellValue;
}
}
}
else
{
//if not using First Head then using A,B,C as index
cell[Helpers.GetAlphabetColumnName(columnIndex)] = cellValue;
}
}
public IEnumerable<T> Query<T>(string sheetName, string startCell, IConfiguration configuration) where T : class, new()
{
var type = typeof(T);
var first = true;
List<ExcelCustomPropertyInfo> props = null;
var headers = Query(false, sheetName, configuration).FirstOrDefault()?.Values?.Select(s => s?.ToString())?.ToArray(); //TODO:need to optimize
foreach (var item in Query(true, sheetName, configuration))
var headers = Query(false, sheetName, startCell, configuration).FirstOrDefault()?.Values?.Select(s => s?.ToString())?.ToArray(); //TODO:need to optimize
foreach (var item in Query(true, sheetName, startCell, configuration))
{
if (first)
{
@ -510,19 +525,32 @@ namespace MiniExcelLibs.OpenXml
return sheetRecords;
}
private object ReadCell(XmlReader reader, int nextColumnIndex, bool withoutCR, out int columnIndex)
private object ReadCellAndSetColumnIndex(XmlReader reader,ref int columnIndex, bool withoutCR,int startColumnIndex)
{
var newColumnIndex = 0;
int xfIndex = -1;
var aT = reader.GetAttribute("t");
var aR = reader.GetAttribute("r");
if (withoutCR)
columnIndex = nextColumnIndex + 1;
newColumnIndex = columnIndex + 1;
//TODO:need to check only need nextColumnIndex or columnIndex
else if (ReferenceHelper.ParseReference(aR, out int referenceColumn, out _))
columnIndex = referenceColumn - 1; // ParseReference is 1-based
newColumnIndex = referenceColumn - 1; // ParseReference is 1-based
else
columnIndex = nextColumnIndex;
newColumnIndex = columnIndex;
columnIndex = newColumnIndex;
if (columnIndex < startColumnIndex)
{
if (!XmlReaderHelper.ReadFirstContent(reader))
return null;
while (!reader.EOF)
if (!XmlReaderHelper.SkipContent(reader))
break;
return null;
}
if (!XmlReaderHelper.ReadFirstContent(reader))
return null;
@ -548,7 +576,9 @@ namespace MiniExcelLibs.OpenXml
}
}
return value;
return value;
}
private void ConvertCellValue(string rawValue, string aT, int xfIndex, out object value)

View File

@ -72,12 +72,12 @@
return result;
}
internal static IDictionary<string, object> GetEmptyExpandoObject(int maxColumnIndex)
internal static IDictionary<string, object> GetEmptyExpandoObject(int maxColumnIndex,int startCellIndex)
{
// TODO: strong type mapping can ignore this
// TODO: it can recode better performance
var cell = (IDictionary<string, object>)new ExpandoObject();
for (int i = 0; i <= maxColumnIndex; i++)
for (int i = startCellIndex; i <= maxColumnIndex; i++)
{
var key = GetAlphabetColumnName(i);
if (!cell.ContainsKey(key))

View File

@ -1,34 +1,56 @@
/**
This Class Modified from ExcelDataReader : https://github.com/ExcelDataReader/ExcelDataReader
**/
namespace MiniExcelLibs.Utils
namespace MiniExcelLibs.Utils
{
using System.Xml;
internal static class XmlReaderHelper
{
public static bool ReadFirstContent(XmlReader xmlReader)
/// <summary>
/// Pass <?xml> and <worksheet>
/// </summary>
/// <param name="reader"></param>
public static void PassXmlDeclartionAndWorksheet(this XmlReader reader)
{
if (xmlReader.IsEmptyElement)
reader.MoveToContent();
reader.Read();
}
/// <summary>
/// e.g skip row 1 to row 2
/// </summary>
/// <param name="reader"></param>
public static void SkipToNextSameLevelDom(XmlReader reader)
{
while (!reader.EOF)
{
xmlReader.Read();
if (!XmlReaderHelper.SkipContent(reader))
break;
}
}
//Method from ExcelDataReader : https://github.com/ExcelDataReader/ExcelDataReader
public static bool ReadFirstContent(XmlReader reader)
{
if (reader.IsEmptyElement)
{
reader.Read();
return false;
}
xmlReader.MoveToContent();
xmlReader.Read();
reader.MoveToContent();
reader.Read();
return true;
}
public static bool SkipContent(XmlReader xmlReader)
//Method from ExcelDataReader : https://github.com/ExcelDataReader/ExcelDataReader
public static bool SkipContent(XmlReader reader)
{
if (xmlReader.NodeType == XmlNodeType.EndElement)
if (reader.NodeType == XmlNodeType.EndElement)
{
xmlReader.Read();
reader.Read();
return false;
}
xmlReader.Skip();
reader.Skip();
return true;
}
}

View File

@ -25,6 +25,53 @@ namespace MiniExcelLibs.Tests
this.output = output;
}
/// <summary>
/// Query Support StartCell #147
/// https://github.com/shps951023/MiniExcel/issues/147
/// </summary>
[Fact]
public void Issue147()
{
{
var path = PathHelper.GetSamplePath("xlsx/TestIssue147.xlsx");
var rows = MiniExcel.Query(path, useHeaderRow: false, startCell: "C3", sheetName: "Sheet1").ToList();
Assert.Equal(new[] { "C", "D", "E" }, (rows[0] as IDictionary<string, object>).Keys);
Assert.Equal(new[] { "Column1", "Column2", "Column3" }, new[] { rows[0].C as string, rows[0].D as string, rows[0].E as string });
Assert.Equal(new[] { "C4", "D4", "E4" }, new[] { rows[1].C as string, rows[1].D as string, rows[1].E as string });
Assert.Equal(new[] { "C9", "D9", "E9" }, new[] { rows[6].C as string, rows[6].D as string, rows[6].E as string });
Assert.Equal(new[] { "C12", "D12", "E12" }, new[] { rows[9].C as string, rows[9].D as string, rows[9].E as string });
Assert.Equal(new[] { "C13", "D13", "E13" }, new[] { rows[10].C as string, rows[10].D as string, rows[10].E as string });
foreach (var i in new[] { 4, 5, 7, 8 })
Assert.Equal(new[] { default(string), default(string), default(string) }, new[] { rows[i].C as string, rows[i].D as string, rows[i].E as string });
Assert.Equal(11, rows.Count);
var columns = MiniExcel.GetColumns(path, startCell: "C3");
Assert.Equal(new[] { "C", "D", "E" }, columns);
}
{
var path = PathHelper.GetSamplePath("xlsx/TestIssue147.xlsx");
var rows = MiniExcel.Query(path, useHeaderRow: true, startCell: "C3", sheetName: "Sheet1").ToList();
Assert.Equal(new[] { "Column1", "Column2", "Column3" }, (rows[0] as IDictionary<string, object>).Keys);
Assert.Equal(new[] { "C4", "D4", "E4" }, new[] { rows[0].Column1 as string, rows[0].Column2 as string, rows[0].Column3 as string });
Assert.Equal(new[] { "C9", "D9", "E9" }, new[] { rows[5].Column1 as string, rows[5].Column2 as string, rows[5].Column3 as string });
Assert.Equal(new[] { "C12", "D12", "E12" }, new[] { rows[8].Column1 as string, rows[8].Column2 as string, rows[8].Column3 as string });
Assert.Equal(new[] { "C13", "D13", "E13" }, new[] { rows[9].Column1 as string, rows[9].Column2 as string, rows[9].Column3 as string });
foreach (var i in new[] { 3, 4, 6, 7 })
Assert.Equal(new[] { default(string), default(string), default(string) }, new[] { rows[i].Column1 as string, rows[i].Column2 as string, rows[i].Column3 as string });
Assert.Equal(10, rows.Count);
var columns = MiniExcel.GetColumns(path, useHeaderRow: true, startCell: "C3");
Assert.Equal(new[] { "Column1", "Column2", "Column3" }, columns);
}
}
/// <summary>
/// [Can SaveAs support iDataReader export to avoid the dataTable consuming too much memory · Issue #211 · shps951023/MiniExcel]