diff --git a/PlayGround/PlayGround.csproj b/PlayGround/PlayGround.csproj new file mode 100644 index 0000000..bbdfde2 --- /dev/null +++ b/PlayGround/PlayGround.csproj @@ -0,0 +1,22 @@ + + + + Exe + net6.0 + enable + enable + + + + 10.0 + + + + 10.0 + + + + + + + diff --git a/PlayGround/Program.cs b/PlayGround/Program.cs new file mode 100644 index 0000000..5264535 --- /dev/null +++ b/PlayGround/Program.cs @@ -0,0 +1,17 @@ +// See https://aka.ms/new-console-template for more information +using Shane32.ExcelLinq.Tests.Models; + +Console.WriteLine("Hello, World!"); + + + +using var stream1 = new System.IO.FileStream("test.xlsx", System.IO.FileMode.Open, System.IO.FileAccess.Read, System.IO.FileShare.Read); +var tt = new TestFileContext(stream1); +var pp = tt.GetSheet(); +var xl = new TestFileContext(); +using var stream = new System.IO.FileStream("test.csv", System.IO.FileMode.Open, System.IO.FileAccess.Read, System.IO.FileShare.Read); +var t = xl.ReadCsv(stream); + + +var g = t; + diff --git a/Shane32.ExcelLinq.sln b/Shane32.ExcelLinq.sln index 51962f3..59c53b1 100644 --- a/Shane32.ExcelLinq.sln +++ b/Shane32.ExcelLinq.sln @@ -39,6 +39,10 @@ Global {83E2AC89-22B4-438D-92CF-FDA280B63CFD}.Debug|Any CPU.Build.0 = Debug|Any CPU {83E2AC89-22B4-438D-92CF-FDA280B63CFD}.Release|Any CPU.ActiveCfg = Release|Any CPU {83E2AC89-22B4-438D-92CF-FDA280B63CFD}.Release|Any CPU.Build.0 = Release|Any CPU + {1F2D99BC-84B4-4704-B3AB-C9EB6A2091B7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1F2D99BC-84B4-4704-B3AB-C9EB6A2091B7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1F2D99BC-84B4-4704-B3AB-C9EB6A2091B7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1F2D99BC-84B4-4704-B3AB-C9EB6A2091B7}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/Shane32.ExcelLinq.Tests/General/EndToEnd.cs b/src/Shane32.ExcelLinq.Tests/General/EndToEnd.cs index 9f8faeb..b3dee2b 100644 --- a/src/Shane32.ExcelLinq.Tests/General/EndToEnd.cs +++ b/src/Shane32.ExcelLinq.Tests/General/EndToEnd.cs @@ -21,6 +21,18 @@ public void NullConstructorsThrow() Assert.ThrowsException(() => new TestFileContext((ExcelPackage)null)); } + + [TestMethod] + public void ReadSampleCsvFile() + { + var xl = new TestFileContext(); + using var stream1 = new System.IO.FileStream("test1.csv", System.IO.FileMode.Open, System.IO.FileAccess.Read, System.IO.FileShare.Read); + using var stream2 = new System.IO.FileStream("test2.csv", System.IO.FileMode.Open, System.IO.FileAccess.Read, System.IO.FileShare.Read); + xl.ReadCsv(stream1, "Sheet1"); + xl.ReadCsv(stream2, "Sheet2"); + ReadSample1File_test(xl); + } + [TestMethod] public void ReadSample1File() { @@ -55,7 +67,8 @@ public void ReadAndWrite() Assert.AreEqual(xl.GetSheet().Count, xl2.GetSheet().Count); } - public void ReadSample1File_test(TestFileContext xl) { + public void ReadSample1File_test(TestFileContext xl) + { var sheet1 = xl.GetSheet(); var sheet2 = xl.GetSheet(); Assert.AreEqual(2, sheet1.Count); diff --git a/src/Shane32.ExcelLinq.Tests/Models/TestFileContext.cs b/src/Shane32.ExcelLinq.Tests/Models/TestFileContext.cs index 4e719ec..b770e75 100644 --- a/src/Shane32.ExcelLinq.Tests/Models/TestFileContext.cs +++ b/src/Shane32.ExcelLinq.Tests/Models/TestFileContext.cs @@ -7,6 +7,7 @@ namespace Shane32.ExcelLinq.Tests.Models { public class TestFileContext : ExcelContext { + public TestFileContext() : base() { } public TestFileContext(System.IO.Stream stream) : base(stream) { } public TestFileContext(string filename) : base(filename) { } public TestFileContext(ExcelPackage excelPackage) : base(excelPackage) { } @@ -14,12 +15,15 @@ public TestFileContext(ExcelPackage excelPackage) : base(excelPackage) { } protected override void OnModelCreating(ExcelModelBuilder builder) { Action headerFormatter = range => { + range.Style.HorizontalAlignment = OfficeOpenXml.Style.ExcelHorizontalAlignment.Center; range.Style.Border.Bottom.Style = OfficeOpenXml.Style.ExcelBorderStyle.Thin; }; + builder.ReadCsv(); + Action numberFormatter = range => range.Style.Numberformat.Format = "#,##0.00"; var sheet1 = builder.Sheet(); - sheet1.Column(x => x.Date) + sheet1.Column(x => x.Date).AlternateName("its a date") .HeaderFormatter(headerFormatter) .ColumnFormatter(range => range.Style.Numberformat.Format = "MM/dd/yyyy"); sheet1.Column(x => x.Quantity) @@ -27,7 +31,7 @@ protected override void OnModelCreating(ExcelModelBuilder builder) .ColumnFormatter(range => range.Style.HorizontalAlignment = OfficeOpenXml.Style.ExcelHorizontalAlignment.Center); sheet1.Column(x => x.Description) .HeaderFormatter(headerFormatter); - sheet1.Column(x => x.Amount) + sheet1.Column(x => x.Amount).AlternateName("some amount") .HeaderFormatter(headerFormatter) .ColumnFormatter(numberFormatter); sheet1.Column(x => x.Total) @@ -62,11 +66,11 @@ protected override void OnModelCreating(ExcelModelBuilder builder) sheet1.WriteRangeLocator(worksheet => worksheet.Cells[3, 1]); sheet1.WritePolisher((worksheet, range) => { worksheet.Calculate(); - for (int col = 1; col <= worksheet.Dimension.End.Column; col++) { - var column = worksheet.Column(col); - column.AutoFit(); - column.Width *= 1.2; - } + //for (int col = 1; col <= worksheet.Dimension.End.Column; col++) { + // var column = worksheet.Column(col); + // column.AutoFit(); + // column.Width *= 1.2; + //} worksheet.Cells[1, 1].Value = "This is a test header"; }); @@ -74,7 +78,7 @@ protected override void OnModelCreating(ExcelModelBuilder builder) sheet2.Column(x => x.IntColumn); sheet2.Column(x => x.FloatColumn); sheet2.Column(x => x.DoubleColumn); - sheet2.Column(x => x.StringColumn); + sheet2.Column(x => x.StringColumn).AlternateName("be a string column"); sheet2.Column(x => x.BooleanColumn); sheet2.Column(x => x.DateTimeColumn) .ColumnFormatter(range => range.Style.Numberformat.Format = "MM/dd/yyyy hh:mm AM/PM"); diff --git a/src/Shane32.ExcelLinq.Tests/Shane32.ExcelLinq.Tests.csproj b/src/Shane32.ExcelLinq.Tests/Shane32.ExcelLinq.Tests.csproj index 78dd6b2..db584d8 100644 --- a/src/Shane32.ExcelLinq.Tests/Shane32.ExcelLinq.Tests.csproj +++ b/src/Shane32.ExcelLinq.Tests/Shane32.ExcelLinq.Tests.csproj @@ -7,6 +7,24 @@ netcoreapp3.1;net5.0;net6.0;net462;net48 + + + + + + + + + Always + + + Always + + + Always + + + all diff --git a/src/Shane32.ExcelLinq.Tests/test.csv b/src/Shane32.ExcelLinq.Tests/test.csv new file mode 100644 index 0000000..93d5d03 --- /dev/null +++ b/src/Shane32.ExcelLinq.Tests/test.csv @@ -0,0 +1,10 @@ +IntColumn,FloatColumn,DoubleColumn,StringColumn,BooleanColumn,DateTimeColumn,TimespanColumn,UriColumn,GuidColumn,NullableIntColumn +1,1,1,test,TRUE,2-Aug,02:00 PM,http://localhost/test,f1dc7e7d-d63e-4279-8dfd-cecb6e26cda8,3 +1.1,1.1,1.1,test2,FALSE,8/1/2020,14:00,http://localhost/help,89892480-4179-42c7-9e2f-e0bb1094dd6b,3.1 +1,1.1,1.1,test2,true,8/3/20,14:00,http://localhost/help,89892480-4179-42c7-9e2f-e0bb1094dd6b,3 +1.1,1.1,1.1,test2,yes,8/1/20 2:30 PM,02:34,http://localhost/help,89892480-4179-42c7-9e2f-e0bb1094dd6b, +1.1,1.1,1.1,test2,NO,8/1/20 2:30 PM,02:34,http://localhost/help,89892480-4179-42c7-9e2f-e0bb1094dd6b, +1.1,1.1,1.1,test2,y,8/1/20 2:30 PM,02:34,http://localhost/help,89892480-4179-42c7-9e2f-e0bb1094dd6b, +1.1,1.1,1.1,test2,N,8/1/20 2:30 PM,02:34,http://localhost/help,89892480-4179-42c7-9e2f-e0bb1094dd6b, +1.1,1.1,1.1,test2,1,8/1/20 2:30 PM,02:34,http://localhost/help,89892480-4179-42c7-9e2f-e0bb1094dd6b, +1.1,1.1,1.1,test2,0,8/1/20 2:30 PM,02:34,http://localhost/help,89892480-4179-42c7-9e2f-e0bb1094dd6b, diff --git a/src/Shane32.ExcelLinq.Tests/test1.csv b/src/Shane32.ExcelLinq.Tests/test1.csv new file mode 100644 index 0000000..c04c7dc --- /dev/null +++ b/src/Shane32.ExcelLinq.Tests/test1.csv @@ -0,0 +1,3 @@ +Date,Quantity,Description,Amount, Total ,Notes +7/1/2020,52,Widgets,45.99 ," $2,391.48 ", +7/23/2020,22,Bolts,2.54 , $55.88 ,Each bolt is a set of two diff --git a/src/Shane32.ExcelLinq.Tests/test2.csv b/src/Shane32.ExcelLinq.Tests/test2.csv new file mode 100644 index 0000000..b5d2660 --- /dev/null +++ b/src/Shane32.ExcelLinq.Tests/test2.csv @@ -0,0 +1,10 @@ +IntColumn,FloatColumn,DoubleColumn,StringColumn,BooleanColumn,DateTimeColumn,TimespanColumn,UriColumn,GuidColumn,NullableIntColumn +1,1,1,test,TRUE,2-Aug,2:00 PM,http://localhost/test,f1dc7e7d-d63e-4279-8dfd-cecb6e26cda8,3 +1.1,1.1,1.1,test2,FALSE,8/1/2020,14:00,http://localhost/help,89892480-4179-42c7-9e2f-e0bb1094dd6b,3.1 +1,1.1,1.1,test2,true,8/3/20,14:00,http://localhost/help,89892480-4179-42c7-9e2f-e0bb1094dd6b,3 +1.1,1.1,1.1,test2,yes,8/1/20 2:30 PM,2:34,http://localhost/help,89892480-4179-42c7-9e2f-e0bb1094dd6b, +1.1,1.1,1.1,test2,NO,8/1/20 2:30 PM,2:34,http://localhost/help,89892480-4179-42c7-9e2f-e0bb1094dd6b, +1.1,1.1,1.1,test2,y,8/1/20 2:30 PM,2:34,http://localhost/help,89892480-4179-42c7-9e2f-e0bb1094dd6b, +1.1,1.1,1.1,test2,N,8/1/20 2:30 PM,2:34,http://localhost/help,89892480-4179-42c7-9e2f-e0bb1094dd6b, +1.1,1.1,1.1,test2,1,8/1/20 2:30 PM,2:34,http://localhost/help,89892480-4179-42c7-9e2f-e0bb1094dd6b, +1.1,1.1,1.1,test2,0,8/1/20 2:30 PM,2:34,http://localhost/help,89892480-4179-42c7-9e2f-e0bb1094dd6b, diff --git a/src/Shane32.ExcelLinq/Builders/ExcelModelBuilder.cs b/src/Shane32.ExcelLinq/Builders/ExcelModelBuilder.cs index 4c95833..2b69737 100644 --- a/src/Shane32.ExcelLinq/Builders/ExcelModelBuilder.cs +++ b/src/Shane32.ExcelLinq/Builders/ExcelModelBuilder.cs @@ -11,12 +11,13 @@ public class ExcelModelBuilder : IExcelModel, ISheetModelLookup internal Dictionary _sheetDictionary = new Dictionary(); internal Dictionary _typeDictionary = new Dictionary(); private bool _ignoreSheetNames; + public bool _readCsv; public SheetModelBuilder Sheet() where T : new() { return Sheet(typeof(T).Name); } - + public SheetModelBuilder Sheet(string name) where T : new() { if (name == null) @@ -34,6 +35,8 @@ public void IgnoreSheetNames() { _ignoreSheetNames = true; } + bool IExcelModel.ReadCsv => _readCsv; + public void ReadCsv() => _readCsv = true; IEnumerator IEnumerable.GetEnumerator() => _sheets.GetEnumerator(); diff --git a/src/Shane32.ExcelLinq/Builders/SheetModelBuilder.cs b/src/Shane32.ExcelLinq/Builders/SheetModelBuilder.cs index fc582f4..afb3ba9 100644 --- a/src/Shane32.ExcelLinq/Builders/SheetModelBuilder.cs +++ b/src/Shane32.ExcelLinq/Builders/SheetModelBuilder.cs @@ -32,6 +32,7 @@ public SheetModelBuilder(ExcelModelBuilder excelModelBuilder, string name) excelModelBuilder._typeDictionary.Add(typeof(T), this); } + public SheetModelBuilder AlternateName(string name) { if (string.IsNullOrWhiteSpace(name)) diff --git a/src/Shane32.ExcelLinq/ExcelContext.cs b/src/Shane32.ExcelLinq/ExcelContext.cs index bb12b41..6e68fa1 100644 --- a/src/Shane32.ExcelLinq/ExcelContext.cs +++ b/src/Shane32.ExcelLinq/ExcelContext.cs @@ -3,11 +3,14 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Linq.Expressions; using System.Reflection; using OfficeOpenXml; using Shane32.ExcelLinq.Builders; using Shane32.ExcelLinq.Exceptions; using Shane32.ExcelLinq.Models; +using NotVisualBasic.FileIO; +using System.Globalization; namespace Shane32.ExcelLinq { @@ -107,12 +110,31 @@ protected ExcelContext(ExcelPackage excelPackage) : this() _initialized = true; } - //internal ExcelContext(IExcelModel model, ExcelPackage excelPackage) : this(model) - //{ - // _initialized = false; - // _sheets = InitializeReadFile(excelPackage); - // _initialized = true; - //} + public void ReadCsv(Stream stream, IFormatProvider formatProvider = null) + { + if (!_initialized) + throw new InvalidOperationException(); + var index = _typeLookup[typeof(T)]; + _sheets[index] = (List)OnReadCsv(stream, Model.Sheets[index], formatProvider ?? CultureInfo.CurrentCulture); + } + + public void ReadCsv(Stream stream, string name, IFormatProvider formatProvider = null) + { + if (!_initialized) + throw new InvalidOperationException(); + var index = _sheetNameLookup[name]; + _sheets[index] = (List)OnReadCsv(stream, Model.Sheets[index], formatProvider ?? CultureInfo.CurrentCulture); + } + + public static TContext OpenCsv(Stream stream, IFormatProvider formatProvider = null) + where TContext : ExcelContext, new() + { + var context = new TContext(); + if (context._sheets.Count != 1) + throw new InvalidOperationException("Cannot open CSV file with multiple sheets."); + context.OnReadCsv(stream, context.Model.Sheets[0], formatProvider ?? CultureInfo.CurrentCulture); + return context; + } private List InitializeReadFile(ExcelPackage excelFile) { @@ -146,6 +168,8 @@ protected IList CreateListForSheet(Type type, int capacity) protected abstract void OnModelCreating(ExcelModelBuilder modelBuilder); + + /// /// Parses an and returns all of the data within all the worksheets. ///

@@ -190,6 +214,69 @@ protected virtual List OnReadFile(ExcelWorkbook workbook) return sheets; } + /// + /// Parses a CSV file and returns all of the data within the sheet. + /// + protected virtual IList OnReadCsv(Stream stream, ISheetModel model, IFormatProvider formatProvider) + { + using var parser = new CsvTextFieldParser(stream); + + parser.Delimiters = new string[] { "," }; + parser.HasFieldsEnclosedInQuotes = true; + + var headerRow = 0; + var currentRow = 0; + string[] headers = null; + while (!parser.EndOfData) { + if (currentRow == headerRow) { + headers = parser.ReadFields(); + ++currentRow; + break; + } + ++currentRow; + } + + IList data = CreateListForSheet(model.Type, 0); + var columnMapping = new IColumnModel[model.Columns.Count]; + var columnMapped = new bool[model.Columns.Count]; + if (headers != null) { + var modelColumns = model.Columns.ToList(); + + for (int colIndex = 0; colIndex < headers.Length; colIndex++) { + var cell = headers[colIndex]; + if (cell != null) { + var headerName = cell; + if (model.Columns.TryGetValue(headerName, out var columnModel)) { + var columnModelIndex = modelColumns.IndexOf(columnModel); + if (columnMapped[columnModelIndex]) + throw new DuplicateColumnException(columnModel.Name, model.Name); + columnMapped[columnModelIndex] = true; + columnMapping[colIndex] = columnModel; + } + } + } + } + + for (int i = 0; i < model.Columns.Count; i++) { + if (columnMapped[i] == false && !model.Columns[i].Optional) + throw new ColumnMissingException(model.Columns[i].Name, model.Name); + } + + while (!parser.EndOfData) { + var range = parser.ReadFields(); + if (range.Length != columnMapping.Length) { + Array.Resize(ref range, columnMapping.Length); + } + var obj = OnReadCSVRow(range, model, columnMapping, formatProvider); + if (obj != null) + data.Add(obj); + ++currentRow; + } + + + return data; + } + /// /// Reads a worksheet and returns a set of of the entries. ///

@@ -248,6 +335,63 @@ protected virtual IList OnReadSheet(ExcelWorksheet worksheet, ISheetModel model) return data; } + protected virtual object OnReadCSVRow(string[] range, ISheetModel model, IColumnModel[] columnMapping, IFormatProvider formatProvider) + { + if (range == null) + throw new ArgumentNullException(nameof(range)); + if (model == null) + throw new ArgumentNullException(nameof(range)); + if (columnMapping == null) + throw new ArgumentNullException(nameof(columnMapping)); + var firstCol = 0; + var columns = range.Length; + //if (range.Rows != 1) + // throw new ArgumentOutOfRangeException(nameof(range), "Range must represent a single row of data"); + if (columns != columnMapping.Length) + throw new ArgumentOutOfRangeException(nameof(columnMapping), "Number of columns in range does not match size of columnMapping array"); + var obj = Activator.CreateInstance(model.Type); + if (range.Any(x => x != null)) { + for (int colIndex = 0; colIndex < columns; colIndex++) { + var col = colIndex + firstCol; + var columnModel = columnMapping[colIndex]; + if (columnModel != null) { + var cell = range[col]; // note that range[] resets range.Address to equal the new address + if (string.IsNullOrEmpty(cell)) { + if (!columnModel.Optional) + throw new ColumnDataMissingException(columnModel.Name, model.Name); + } else { + object value; + try { + //if (columnModel.ReadSerializer != null) { + // value = columnModel.ReadSerializer(cell); + //} else { + value = DefaultCsvReadSerializer(cell, cell, columnModel.Type, formatProvider); + //} + } catch (Exception e) { + throw new ParseDataException(cell, columnModel.Name, model.Name, e); + } + if (value != null) { + if (columnModel.Member is PropertyInfo propertyInfo) { + propertyInfo.SetMethod.Invoke(obj, new[] { value }); + } else if (columnModel.Member is FieldInfo fieldInfo) { + fieldInfo.SetValue(obj, value); + } + } + } + } + } + } else { + if (model.SkipEmptyRows) { + obj = null; + } else { + foreach (var columnModel in columnMapping) { + if (!columnModel.Optional) + throw new RowEmptyException(model.Name); + } + } + } + return obj; + } /// /// Parses a row of data or returns null if the row should be skipped /// @@ -324,6 +468,90 @@ protected virtual ExcelRange DefaultReadRangeLocator(ExcelWorksheet worksheet) return worksheet.Cells[dimension.Start.Row, dimension.Start.Column, dimension.End.Row, dimension.End.Column]; } + protected virtual object DefaultCsvReadSerializer(object value, string text, Type dataType, IFormatProvider formatProvider) + { + if (value == null) { + return null; + } + if (dataType.IsGenericType && dataType.GetGenericTypeDefinition() == typeof(Nullable<>)) { + return DefaultCsvReadSerializer(value, text, Nullable.GetUnderlyingType(dataType), formatProvider); + } + if (value.GetType() == dataType) + return value; + if (dataType == typeof(string)) + return text; + if (dataType == typeof(DateTime)) { + if (value is string str) + return DateTime.Parse(str, formatProvider, DateTimeStyles.AllowWhiteSpaces | DateTimeStyles.AssumeLocal); + return DateTime.FromOADate((double)DefaultCsvReadSerializer(value, text, typeof(double), formatProvider)); + } + if (dataType == typeof(TimeSpan)) { + if (value is DateTime dt) + return dt.TimeOfDay; + if (value is string str) + try { + return TimeSpan.Parse(str, formatProvider); + } catch (FormatException) { + return DateTime.Parse(str, formatProvider, DateTimeStyles.AllowWhiteSpaces | DateTimeStyles.AssumeLocal).TimeOfDay; + } + } + if (dataType == typeof(DateTimeOffset)) { + throw new NotSupportedException("DateTimeOffset values are not supported"); + } + if (dataType == typeof(Uri)) { + return new Uri(text); + } + if (dataType == typeof(Guid)) { + return Guid.Parse(text); + } + if (dataType == typeof(bool)) { + if (value is string str) { + switch (str.ToLower()) { + case "y": + case "1": + case "yes": + case "true": + return true; + case "n": + case "0": + case "no": + case "false": + return false; + } + } + } + + if (dataType == typeof(int)) { + if (value is string str) { + var success = int.TryParse(str, NumberStyles.Any, formatProvider, out int result); + if (success) + return result; + + return Convert.ToInt32(Math.Floor(double.Parse(str, NumberStyles.Any, formatProvider))); + } + } + + if (dataType == typeof(double)) { + if (value is string str) { + return double.Parse(str, NumberStyles.Any, formatProvider); + } + } + + if (dataType == typeof(decimal)) { + if (value is string str) { + return decimal.Parse(str, NumberStyles.Any, formatProvider); + } + } + + if (dataType == typeof(float)) { + if (value is string str) { + return float.Parse(str, NumberStyles.Any, formatProvider); + } + } + + return Convert.ChangeType(value, dataType); + } + /// /// Parses the cell and converts it to the requested data type. For nullable types, /// it is acceptable to return the underlying type. diff --git a/src/Shane32.ExcelLinq/Models/CsvRange.cs b/src/Shane32.ExcelLinq/Models/CsvRange.cs new file mode 100644 index 0000000..32cea08 --- /dev/null +++ b/src/Shane32.ExcelLinq/Models/CsvRange.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Shane32.ExcelLinq.Models +{ + public class CsvRange + { + public int StartColumn { get; set; } + public int StartRow { get; set; } + public int? EndColumn { get; set; } + public int? EndRow { get; set; } + } +} diff --git a/src/Shane32.ExcelLinq/Models/IExcelModel.cs b/src/Shane32.ExcelLinq/Models/IExcelModel.cs index ac7b6fe..270f0dc 100644 --- a/src/Shane32.ExcelLinq/Models/IExcelModel.cs +++ b/src/Shane32.ExcelLinq/Models/IExcelModel.cs @@ -4,5 +4,6 @@ public interface IExcelModel { ISheetModelLookup Sheets { get; } bool IgnoreSheetNames { get; } + bool ReadCsv { get; } } } diff --git a/src/Shane32.ExcelLinq/Shane32.ExcelLinq.csproj b/src/Shane32.ExcelLinq/Shane32.ExcelLinq.csproj index 0d23902..1d6944c 100644 --- a/src/Shane32.ExcelLinq/Shane32.ExcelLinq.csproj +++ b/src/Shane32.ExcelLinq/Shane32.ExcelLinq.csproj @@ -7,6 +7,7 @@ +