diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml
index d2382fcf..edb30274 100644
--- a/.github/workflows/benchmark.yml
+++ b/.github/workflows/benchmark.yml
@@ -1,6 +1,7 @@
name: MiniExcel Benchmarks
on:
+ workflow_dispatch:
release:
types: [published]
@@ -29,19 +30,14 @@ jobs:
env:
BenchmarkMode: Automatic
BenchmarkSection: query
- - name: Commit report
- working-directory: ./benchmarks
- run: |
- cp -r ./MiniExcel.Benchmarks/BenchmarkDotNet.Artifacts/results ./
- git config user.name github-actions
- git config user.email github-actions@github.com
- git pull
- cd ./results
- git add '*.md'
- git commit -am "Automated benchmark report - query section"
- git push --force
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ - name: Renaming result file
+ run: mv MiniExcelLibs.Benchmarks.XlsxBenchmark-report-github.md query-benchmark.md
+ working-directory: ./benchmarks/MiniExcel.Benchmarks/BenchMarkDotNet.Artifacts/results
+ - name: Save benchmark results
+ uses: actions/upload-artifact@v4
+ with:
+ name: query-benchmark-result
+ path: ./benchmark/MiniExcel.Benchmarks/BenchMarkDotNet.Artifacts/results/*.md
CreateBenchmark:
runs-on: ubuntu-latest
@@ -64,20 +60,15 @@ jobs:
env:
BenchmarkMode: Automatic
BenchmarkSection: create
- - name: Commit report
- working-directory: ./benchmarks
- run: |
- cp -r ./MiniExcel.Benchmarks/BenchmarkDotNet.Artifacts/results ./
- git config user.name github-actions
- git config user.email github-actions@github.com
- git pull
- cd ./results
- git add '*.md'
- git commit -am "Automated benchmark report - create section"
- git push --force
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-
+ - name: Renaming result file
+ run: mv MiniExcelLibs.Benchmarks.XlsxBenchmark-report-github.md create-benchmark.md
+ working-directory: ./benchmarks/MiniExcel.Benchmarks/BenchMarkDotNet.Artifacts/results
+ - name: Save benchmark results
+ uses: actions/upload-artifact@v4
+ with:
+ name: create-benchmark-result
+ path: ./benchmark/MiniExcel.Benchmarks/BenchMarkDotNet.Artifacts/results/*.md
+
TemplateBenchmark:
runs-on: ubuntu-latest
@@ -99,16 +90,33 @@ jobs:
env:
BenchmarkMode: Automatic
BenchmarkSection: template
- - name: Commit report
- working-directory: ./benchmarks
+ - name: Renaming result file
+ run: mv MiniExcelLibs.Benchmarks.XlsxBenchmark-report-github.md template-benchmark.md
+ working-directory: ./benchmarks/MiniExcel.Benchmarks/BenchMarkDotNet.Artifacts/results
+ - name: Save benchmark results
+ uses: actions/upload-artifact@v4
+ with:
+ name: template-benchmark-result
+ path: ./benchmark/MiniExcel.Benchmarks/BenchMarkDotNet.Artifacts/results/*.md
+
+ PushBenchmarksResults:
+ runs-on: ubuntu-latest
+ needs: [ QueryBenchmark, CreateBenchmark, TemplateBenchmark ]
+
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ - name: Fetch benchmark results
+ uses: actions/download-artifact@v4
+ with:
+ path: ./benchmarks/results
+ merge-multiple: true
+ - name: Commit reports
+ working-directory: ./benchmarks/results
run: |
- cp -r ./MiniExcel.Benchmarks/BenchmarkDotNet.Artifacts/results ./
git config user.name github-actions
git config user.email github-actions@github.com
- git pull
- cd ./results
- git add '*.md'
- git commit -am "Automated benchmark report - template section"
- git push --force
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ git add ./*.md
+ git commit -am "Automated benchmark report - ${{ github.ref_name }}"
+ git push origin master --force-with-lease
\ No newline at end of file
diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml
index 94859672..5cf65e17 100644
--- a/.github/workflows/dotnet.yml
+++ b/.github/workflows/dotnet.yml
@@ -14,11 +14,11 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Setup .NET 8
- uses: actions/setup-dotnet@v1
+ uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x
- name: Setup .NET 9
- uses: actions/setup-dotnet@v1
+ uses: actions/setup-dotnet@v4
with:
dotnet-version: 9.0.x
- name: Restore dependencies
diff --git a/MiniExcel.sln b/MiniExcel.sln
index 89f3ac72..8d3d52f2 100644
--- a/MiniExcel.sln
+++ b/MiniExcel.sln
@@ -16,6 +16,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Docs and setting", "Docs an
README.md = README.md
README.zh-CN.md = README.zh-CN.md
README.zh-Hant.md = README.zh-Hant.md
+ .github\workflows\benchmark.yml = .github\workflows\benchmark.yml
+ .github\workflows\codeql-analysis.yml = .github\workflows\codeql-analysis.yml
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{CC1E0601-AEC9-42D7-8F6A-3FB3939EED16}"
diff --git a/README.md b/README.md
index 2351f4e2..288c5308 100644
--- a/README.md
+++ b/README.md
@@ -1517,7 +1517,7 @@ MiniExcel.AddPicture(path, images);
#### 7. Get Sheets Dimension
```csharp
-var dim = MiniExcel.GetSheetsDimensions(path);
+var dim = MiniExcel.GetSheetDimensions(path);
```
### Examples:
diff --git a/benchmarks/MiniExcel.Benchmarks/MiniExcel.Benchmarks.csproj b/benchmarks/MiniExcel.Benchmarks/MiniExcel.Benchmarks.csproj
index 44eda503..cc7b5fc9 100644
--- a/benchmarks/MiniExcel.Benchmarks/MiniExcel.Benchmarks.csproj
+++ b/benchmarks/MiniExcel.Benchmarks/MiniExcel.Benchmarks.csproj
@@ -10,13 +10,12 @@
-
-
-
-
-
+
+
+
+
+
-
diff --git a/benchmarks/MiniExcel.Benchmarks/Program.cs b/benchmarks/MiniExcel.Benchmarks/Program.cs
index 99b23519..792bcab9 100644
--- a/benchmarks/MiniExcel.Benchmarks/Program.cs
+++ b/benchmarks/MiniExcel.Benchmarks/Program.cs
@@ -1,5 +1,4 @@
-using System.ComponentModel;
-using BenchmarkDotNet.Running;
+using BenchmarkDotNet.Running;
using MiniExcelLibs.Benchmarks;
using MiniExcelLibs.Benchmarks.BenchmarkSections;
@@ -11,12 +10,19 @@
"query" => typeof(QueryXlsxBenchmark),
"create" => typeof(CreateXlsxBenchmark),
"template" => typeof(TemplateXlsxBenchmark),
- _ => throw new InvalidEnumArgumentException($"Benchmark section {section} does not exist")
+ _ => throw new ArgumentException($"Benchmark section {section} does not exist")
};
BenchmarkRunner.Run(benchmark, new Config(), args);
}
else
+{
BenchmarkSwitcher
- .FromTypes([typeof(CreateXlsxBenchmark)])
- .Run(args, new Config());
\ No newline at end of file
+ .FromTypes(
+ [
+ typeof(QueryXlsxBenchmark),
+ typeof(CreateXlsxBenchmark),
+ typeof(TemplateXlsxBenchmark)
+ ])
+ .Run(args, new Config());
+}
\ No newline at end of file
diff --git a/src/MiniExcel/Csv/CsvConfiguration.cs b/src/MiniExcel/Csv/CsvConfiguration.cs
index afee65fa..1d795c40 100644
--- a/src/MiniExcel/Csv/CsvConfiguration.cs
+++ b/src/MiniExcel/Csv/CsvConfiguration.cs
@@ -6,18 +6,19 @@ namespace MiniExcelLibs.Csv
{
public class CsvConfiguration : Configuration
{
- private static Encoding _defaultEncoding = new UTF8Encoding(true);
+ private static readonly Encoding DefaultEncoding = new UTF8Encoding(true);
public char Seperator { get; set; } = ',';
public string NewLine { get; set; } = "\r\n";
public bool ReadLineBreaksWithinQuotes { get; set; } = true;
public bool ReadEmptyStringAsNull { get; set; } = false;
public bool AlwaysQuote { get; set; } = false;
+ public bool QuoteWhitespaces { get; set; } = true;
public Func SplitFn { get; set; }
- public Func StreamReaderFunc { get; set; } = (stream) => new StreamReader(stream, _defaultEncoding);
- public Func StreamWriterFunc { get; set; } = (stream) => new StreamWriter(stream, _defaultEncoding);
+ public Func StreamReaderFunc { get; set; } = (stream) => new StreamReader(stream, DefaultEncoding);
+ public Func StreamWriterFunc { get; set; } = (stream) => new StreamWriter(stream, DefaultEncoding);
- internal readonly static CsvConfiguration DefaultConfiguration = new CsvConfiguration();
+ internal static readonly CsvConfiguration DefaultConfiguration = new CsvConfiguration();
}
}
diff --git a/src/MiniExcel/Csv/CsvHelpers.cs b/src/MiniExcel/Csv/CsvHelpers.cs
index a9b878a9..b6fd5a10 100644
--- a/src/MiniExcel/Csv/CsvHelpers.cs
+++ b/src/MiniExcel/Csv/CsvHelpers.cs
@@ -3,7 +3,7 @@
internal static class CsvHelpers
{
/// If content contains special characters then use "{value}" format
- public static string ConvertToCsvValue(string value, bool alwaysQuote, char separator)
+ public static string ConvertToCsvValue(string value, CsvConfiguration configuration)
{
if (value == null)
return string.Empty;
@@ -13,16 +13,14 @@ public static string ConvertToCsvValue(string value, bool alwaysQuote, char sepa
value = value.Replace("\"", "\"\"");
return $"\"{value}\"";
}
-
- if (value.Contains(separator.ToString()) || value.Contains(" ") || value.Contains("\n") || value.Contains("\r"))
- {
- return $"\"{value}\"";
- }
-
- if (alwaysQuote)
- return $"\"{value}\"";
-
- return value;
+
+ var shouldQuote = configuration.AlwaysQuote ||
+ (configuration.QuoteWhitespaces && value.Contains(" ")) ||
+ value.Contains(configuration.Seperator.ToString()) ||
+ value.Contains("\r") ||
+ value.Contains("\n");
+
+ return shouldQuote ? $"\"{value}\"" : value;
}
}
}
diff --git a/src/MiniExcel/Csv/CsvWriter.cs b/src/MiniExcel/Csv/CsvWriter.cs
index d65daa5e..efa46fd8 100644
--- a/src/MiniExcel/Csv/CsvWriter.cs
+++ b/src/MiniExcel/Csv/CsvWriter.cs
@@ -49,7 +49,7 @@ public int Insert(bool overwriteSheet = false)
private void AppendColumn(StringBuilder rowBuilder, CellWriteInfo column)
{
- rowBuilder.Append(CsvHelpers.ConvertToCsvValue(ToCsvString(column.Value, column.Prop), _configuration.AlwaysQuote, _configuration.Seperator));
+ rowBuilder.Append(CsvHelpers.ConvertToCsvValue(ToCsvString(column.Value, column.Prop), _configuration));
rowBuilder.Append(_configuration.Seperator);
}
@@ -63,7 +63,7 @@ private static void RemoveTrailingSeparator(StringBuilder rowBuilder)
private string GetHeader(List props) => string.Join(
_configuration.Seperator.ToString(),
- props.Select(s => CsvHelpers.ConvertToCsvValue(s?.ExcelColumnName, _configuration.AlwaysQuote, _configuration.Seperator)));
+ props.Select(s => CsvHelpers.ConvertToCsvValue(s?.ExcelColumnName, _configuration)));
private int WriteValues(object values)
{
diff --git a/src/MiniExcel/OpenXml/SharedStringsDiskCache.cs b/src/MiniExcel/OpenXml/SharedStringsDiskCache.cs
index fdb8a37a..34163c2d 100644
--- a/src/MiniExcel/OpenXml/SharedStringsDiskCache.cs
+++ b/src/MiniExcel/OpenXml/SharedStringsDiskCache.cs
@@ -2,7 +2,6 @@
using System.Collections;
using System.Collections.Generic;
using System.IO;
-using System.Runtime.CompilerServices;
using System.Text;
namespace MiniExcelLibs.OpenXml
@@ -37,9 +36,11 @@ internal void Add(int index, string value)
{
if (index > _maxIndx)
_maxIndx = index;
- byte[] valueBs = _encoding.GetBytes(value);
+
+ var valueBs = _encoding.GetBytes(value);
if (value.Length > 32767) //check info length, becasue cell string max length is 47483647
- throw new ArgumentOutOfRangeException("Excel one cell max length is 32,767 characters");
+ throw new ArgumentOutOfRangeException("", "Excel one cell max length is 32,767 characters");
+
_positionFs.Write(BitConverter.GetBytes(_valueFs.Position), 0, 4);
_lengthFs.Write(BitConverter.GetBytes(valueBs.Length), 0, 4);
_valueFs.Write(valueBs, 0, valueBs.Length);
@@ -49,16 +50,19 @@ private string GetValue(int index)
{
_positionFs.Position = index * 4;
var bytes = new byte[4];
- _positionFs.Read(bytes, 0, 4);
+ _ = _positionFs.Read(bytes, 0, 4);
var position = BitConverter.ToInt32(bytes, 0);
+
+ bytes = new byte[4];
_lengthFs.Position = index * 4;
- _lengthFs.Read(bytes, 0, 4);
+ _ = _lengthFs.Read(bytes, 0, 4);
var length = BitConverter.ToInt32(bytes, 0);
- _valueFs.Position = position;
+
bytes = new byte[length];
- _valueFs.Read(bytes, 0, length);
- var v = _encoding.GetString(bytes);
- return v;
+ _valueFs.Position = position;
+ _ = _valueFs.Read(bytes, 0, length);
+
+ return _encoding.GetString(bytes);
}
protected virtual void Dispose(bool disposing)
@@ -69,15 +73,19 @@ protected virtual void Dispose(bool disposing)
{
// TODO: dispose managed state (managed objects)
}
+
_positionFs.Dispose();
if (File.Exists(_positionFs.Name))
File.Delete(_positionFs.Name);
+
_lengthFs.Dispose();
if (File.Exists(_lengthFs.Name))
File.Delete(_lengthFs.Name);
+
_valueFs.Dispose();
if (File.Exists(_valueFs.Name))
File.Delete(_valueFs.Name);
+
_disposedValue = true;
}
}
diff --git a/src/MiniExcel/Utils/ExcelTypeHelper.cs b/src/MiniExcel/Utils/ExcelTypeHelper.cs
index 92063e91..53e4926e 100644
--- a/src/MiniExcel/Utils/ExcelTypeHelper.cs
+++ b/src/MiniExcel/Utils/ExcelTypeHelper.cs
@@ -32,7 +32,10 @@ internal static ExcelType GetExcelType(Stream stream, ExcelType excelType)
var probe = new byte[8];
stream.Seek(0, SeekOrigin.Begin);
- stream.Read(probe, 0, probe.Length);
+ var read = stream.Read(probe, 0, probe.Length);
+ if (read != probe.Length)
+ throw new InvalidDataException("The file/stream does not contain enough data to process");
+
stream.Seek(0, SeekOrigin.Begin);
// New office format (can be any ZIP archive)
@@ -41,7 +44,7 @@ internal static ExcelType GetExcelType(Stream stream, ExcelType excelType)
return ExcelType.XLSX;
}
- throw new NotSupportedException("Stream cannot know the file type, please specify ExcelType manually");
+ throw new InvalidDataException("The file type could not be inferred automatically, please specify ExcelType manually");
}
}
}
diff --git a/tests/MiniExcelTests/MiniExcelCsvAsycTests.cs b/tests/MiniExcelTests/MiniExcelCsvAsycTests.cs
index 295ca904..46dd2c55 100644
--- a/tests/MiniExcelTests/MiniExcelCsvAsycTests.cs
+++ b/tests/MiniExcelTests/MiniExcelCsvAsycTests.cs
@@ -102,25 +102,23 @@ public async Task SaveAsByDictionary()
{ "c", false },
{ "d", new DateTime(2021, 1, 2) }
}
-
];
var rowsWritten = await MiniExcel.SaveAsAsync(path, values);
Assert.Equal(2, rowsWritten[0]);
- using (var reader = new StreamReader(path))
- using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture))
- {
- var records = csv.GetRecords().ToList();
- Assert.Equal(@"""<>+-*//}{\\n", records[0].a);
- Assert.Equal("1234567890", records[0].b);
- Assert.Equal("True", records[0].c);
- Assert.Equal("2021-01-01 00:00:00", records[0].d);
-
- Assert.Equal("Hello World", records[1].a);
- Assert.Equal("-1234567890", records[1].b);
- Assert.Equal("False", records[1].c);
- Assert.Equal("2021-01-02 00:00:00", records[1].d);
- }
+ using var reader = new StreamReader(path);
+ using var csv = new CsvReader(reader, CultureInfo.InvariantCulture);
+ var records = csv.GetRecords().ToList();
+
+ Assert.Equal(@"""<>+-*//}{\\n", records[0].a);
+ Assert.Equal("1234567890", records[0].b);
+ Assert.Equal("True", records[0].c);
+ Assert.Equal("2021-01-01 00:00:00", records[0].d);
+
+ Assert.Equal("Hello World", records[1].a);
+ Assert.Equal("-1234567890", records[1].b);
+ Assert.Equal("False", records[1].c);
+ Assert.Equal("2021-01-02 00:00:00", records[1].d);
}
{
@@ -145,116 +143,104 @@ public async Task SaveAsByDictionary()
{ 4, new DateTime(2021, 1, 2) }
}
];
+
var rowsWritten = await MiniExcel.SaveAsAsync(path, values);
Assert.Equal(2, rowsWritten[0]);
- using (var reader = new StreamReader(path))
- using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture))
- {
- var records = csv.GetRecords().ToList();
- {
- var row = records[0] as IDictionary;
- Assert.Equal(@"""<>+-*//}{\\n", row["1"]);
- Assert.Equal("1234567890", row["2"]);
- Assert.Equal("True", row["3"]);
- Assert.Equal("2021-01-01 00:00:00", row["4"]);
- }
- {
- var row = records[1] as IDictionary;
- Assert.Equal("Hello World", row["1"]);
- Assert.Equal("-1234567890", row["2"]);
- Assert.Equal("False", row["3"]);
- Assert.Equal("2021-01-02 00:00:00", row["4"]);
- }
- }
+ using var reader = new StreamReader(path);
+ using var csv = new CsvReader(reader, CultureInfo.InvariantCulture);
+ var records = csv.GetRecords().ToList();
+
+ var row1 = records[0] as IDictionary;
+ Assert.Equal(@"""<>+-*//}{\\n", row1!["1"]);
+ Assert.Equal("1234567890", row1["2"]);
+ Assert.Equal("True", row1["3"]);
+ Assert.Equal("2021-01-01 00:00:00", row1["4"]);
+
+ var row2 = records[1] as IDictionary;
+ Assert.Equal("Hello World", row2!["1"]);
+ Assert.Equal("-1234567890", row2["2"]);
+ Assert.Equal("False", row2["3"]);
+ Assert.Equal("2021-01-02 00:00:00", row2["4"]);
}
}
[Fact]
public async Task SaveAsByDataTableTest()
{
- {
- using var file = AutoDeletingPath.Create(ExcelType.CSV);
- var path = file.ToString();
+ using var file1 = AutoDeletingPath.Create(ExcelType.CSV);
+ var path1 = file1.ToString();
- var table = new DataTable();
- await MiniExcel.SaveAsAsync(path, table);
+ var emptyTable = new DataTable();
+ await MiniExcel.SaveAsAsync(path1, emptyTable);
- var text = File.ReadAllText(path);
- Assert.Equal("\r\n", text);
- }
+ var text = await File.ReadAllTextAsync(path1);
+ Assert.Equal("\r\n", text);
- {
- var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.csv");
-
- var table = new DataTable();
- {
- table.Columns.Add("a", typeof(string));
- table.Columns.Add("b", typeof(decimal));
- table.Columns.Add("c", typeof(bool));
- table.Columns.Add("d", typeof(DateTime));
- table.Rows.Add(@"""<>+-*//}{\\n", 1234567890, true, new DateTime(2021, 1, 1));
- table.Rows.Add("Hello World", -1234567890, false, new DateTime(2021, 1, 2));
- }
+
+ using var file2= AutoDeletingPath.Create(ExcelType.CSV);
+ var path2 = file2.ToString();
+
+ var table = new DataTable();
+ table.Columns.Add("a", typeof(string));
+ table.Columns.Add("b", typeof(decimal));
+ table.Columns.Add("c", typeof(bool));
+ table.Columns.Add("d", typeof(DateTime));
+ table.Rows.Add(@"""<>+-*//}{\\n", 1234567890, true, new DateTime(2021, 1, 1));
+ table.Rows.Add("Hello World", -1234567890, false, new DateTime(2021, 1, 2));
+
+ var rowsWritten = await MiniExcel.SaveAsAsync(path2, table);
+ Assert.Equal(2, rowsWritten[0]);
- var rowsWritten = await MiniExcel.SaveAsAsync(path, table);
- Assert.Equal(2, rowsWritten[0]);
-
- using (var reader = new StreamReader(path))
- using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture))
- {
- var records = csv.GetRecords().ToList();
- Assert.Equal(@"""<>+-*//}{\\n", records[0].a);
- Assert.Equal("1234567890", records[0].b);
- Assert.Equal("True", records[0].c);
- Assert.Equal("2021-01-01 00:00:00", records[0].d);
-
- Assert.Equal("Hello World", records[1].a);
- Assert.Equal("-1234567890", records[1].b);
- Assert.Equal("False", records[1].c);
- Assert.Equal("2021-01-02 00:00:00", records[1].d);
- }
- }
+ using var reader = new StreamReader(path2);
+ using var csv = new CsvReader(reader, CultureInfo.InvariantCulture);
+ var records = csv.GetRecords().ToList();
+
+ Assert.Equal(@"""<>+-*//}{\\n", records[0].a);
+ Assert.Equal("1234567890", records[0].b);
+ Assert.Equal("True", records[0].c);
+ Assert.Equal("2021-01-01 00:00:00", records[0].d);
+
+ Assert.Equal("Hello World", records[1].a);
+ Assert.Equal("-1234567890", records[1].b);
+ Assert.Equal("False", records[1].c);
+ Assert.Equal("2021-01-02 00:00:00", records[1].d);
}
private class Test
{
- public string c1 { get; set; }
- public string c2 { get; set; }
+ public string? c1 { get; set; }
+ public string? c2 { get; set; }
}
[Fact]
public async Task CsvExcelTypeTest()
{
- {
- using var file = AutoDeletingPath.Create(ExcelType.CSV);
- var path = file.ToString();
+ using var file = AutoDeletingPath.Create(ExcelType.CSV);
+ var path = file.ToString();
- var input = new[] { new { A = "Test1", B = "Test2" } };
- await MiniExcel.SaveAsAsync(path, input);
+ var input = new[] { new { A = "Test1", B = "Test2" } };
+ await MiniExcel.SaveAsAsync(path, input);
- var texts = File.ReadAllLines(path);
- Assert.Equal("A,B", texts[0]);
- Assert.Equal("Test1,Test2", texts[1]);
+ var texts = await File.ReadAllLinesAsync(path);
+ Assert.Equal("A,B", texts[0]);
+ Assert.Equal("Test1,Test2", texts[1]);
- {
- var q = await MiniExcel.QueryAsync(path);
- var rows = q.ToList();
- Assert.Equal("A", rows[0].A);
- Assert.Equal("B", rows[0].B);
- Assert.Equal("Test1", rows[1].A);
- Assert.Equal("Test2", rows[1].B);
- }
+ var q = await MiniExcel.QueryAsync(path);
+ var rows1 = q.ToList();
- using (var reader = new StreamReader(path))
- using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture))
- {
- var rows = csv.GetRecords().ToList();
- Assert.Equal("Test1", rows[0].A);
- Assert.Equal("Test2", rows[0].B);
- }
- }
+ Assert.Equal("A", rows1[0].A);
+ Assert.Equal("B", rows1[0].B);
+ Assert.Equal("Test1", rows1[1].A);
+ Assert.Equal("Test2", rows1[1].B);
+
+ using var reader = new StreamReader(path);
+ using var csv = new CsvReader(reader, CultureInfo.InvariantCulture);
+ var rows2 = csv.GetRecords().ToList();
+
+ Assert.Equal("Test1", rows2[0].A);
+ Assert.Equal("Test2", rows2[0].B);
}
[Fact]
@@ -263,14 +249,15 @@ public async Task Create2x2_Test()
using var file = AutoDeletingPath.Create(ExcelType.CSV);
var path = file.ToString();
- await MiniExcel.SaveAsAsync(path, new[] {
- new { c1 = "A1" ,c2 = "B1"},
- new { c1 = "A2" ,c2 = "B2"},
+ await MiniExcel.SaveAsAsync(path, new[]
+ {
+ new { c1 = "A1", c2 = "B1"},
+ new { c1 = "A2", c2 = "B2"},
});
- using (var stream = File.OpenRead(path))
+ await using (var stream = File.OpenRead(path))
{
- var rows = stream.Query(useHeaderRow: true, excelType: ExcelType.CSV).ToList();
+ var rows = (await stream.QueryAsync(useHeaderRow: true, excelType: ExcelType.CSV)).ToList();
Assert.Equal("A1", rows[0].c1);
Assert.Equal("B1", rows[0].c2);
Assert.Equal("A2", rows[1].c1);
@@ -278,7 +265,7 @@ await MiniExcel.SaveAsAsync(path, new[] {
}
{
- var rows = MiniExcel.Query(path, useHeaderRow: true, excelType: ExcelType.CSV).ToList();
+ var rows = (await MiniExcel.QueryAsync(path, useHeaderRow: true, excelType: ExcelType.CSV)).ToList();
Assert.Equal("A1", rows[0].c1);
Assert.Equal("B1", rows[0].c2);
Assert.Equal("A2", rows[1].c1);
@@ -292,12 +279,13 @@ public async Task CsvTypeMappingTest()
using var file = AutoDeletingPath.Create(ExcelType.CSV);
var path = file.ToString();
- await MiniExcel.SaveAsAsync(path, new[] {
- new { c1 = "A1" ,c2 = "B1"},
- new { c1 = "A2" ,c2 = "B2"},
+ await MiniExcel.SaveAsAsync(path, new[]
+ {
+ new { c1 = "A1", c2 = "B1"},
+ new { c1 = "A2", c2 = "B2"}
});
- using (var stream = File.OpenRead(path))
+ await using (var stream = File.OpenRead(path))
{
var rows = stream.Query(excelType: ExcelType.CSV).ToList();
Assert.Equal("A1", rows[0].c1);
@@ -323,11 +311,11 @@ public async Task CsvReadEmptyStringAsNullTest()
await MiniExcel.SaveAsAsync(path, new[]
{
- new { c1 = "A1", c2 = (string)null},
- new { c1 = (string)null, c2 = (string)null},
+ new { c1 = (string?)"A1", c2 = (string?)null},
+ new { c1 = (string?)null, c2 = (string?)null}
});
- using (var stream = File.OpenRead(path))
+ await using (var stream = File.OpenRead(path))
{
var rows = stream.Query(excelType: ExcelType.CSV).ToList();
Assert.Equal("A1", rows[0].c1);
@@ -345,7 +333,7 @@ await MiniExcel.SaveAsAsync(path, new[]
}
var config = new Csv.CsvConfiguration { ReadEmptyStringAsNull = true };
- using (var stream = File.OpenRead(path))
+ await using (var stream = File.OpenRead(path))
{
var rows = stream.Query(excelType: ExcelType.CSV, configuration: config).ToList();
Assert.Equal("A1", rows[0].c1);
diff --git a/tests/MiniExcelTests/MiniExcelCsvTests.cs b/tests/MiniExcelTests/MiniExcelCsvTests.cs
index 56eb6fde..ad1b8e70 100644
--- a/tests/MiniExcelTests/MiniExcelCsvTests.cs
+++ b/tests/MiniExcelTests/MiniExcelCsvTests.cs
@@ -60,6 +60,43 @@ public void SeperatorTest()
"""";
Assert.Equal(expected, File.ReadAllText(path));
}
+
+ [Fact]
+ public void DontQuoteWhitespacesTest()
+ {
+ using var file = AutoDeletingPath.Create(ExcelType.CSV);
+ var path = file.ToString();
+
+ List> values =
+ [
+ new()
+ {
+ { "a", @"""<>+-*//}{\\n" },
+ { "b", 1234567890 },
+ { "c", true },
+ { "d", new DateTime(2021, 1, 1) }
+ },
+
+ new()
+ {
+ { "a", "Hello World" },
+ { "b", -1234567890 },
+ { "c", false },
+ { "d", new DateTime(2021, 1, 2) }
+ }
+ ];
+ var rowsWritten = MiniExcel.SaveAs(path, values, configuration: new Csv.CsvConfiguration { QuoteWhitespaces = false });
+ Assert.Equal(2, rowsWritten[0]);
+
+ const string expected =
+ """"
+ a,b,c,d
+ """<>+-*//}{\\n",1234567890,True,2021-01-01 00:00:00
+ Hello World,-1234567890,False,2021-01-02 00:00:00
+
+ """";
+ Assert.Equal(expected, File.ReadAllText(path));
+ }
[Fact]
public void AlwaysQuoteTest()
@@ -203,14 +240,14 @@ public void SaveAsByDictionary()
var records = csv.GetRecords().ToList();
{
var row = records[0] as IDictionary;
- Assert.Equal(@"""<>+-*//}{\\n", row["1"]);
+ Assert.Equal(@"""<>+-*//}{\\n", row!["1"]);
Assert.Equal("1234567890", row["2"]);
Assert.Equal("True", row["3"]);
Assert.Equal("2021-01-01 00:00:00", row["4"]);
}
{
var row = records[1] as IDictionary;
- Assert.Equal("Hello World", row["1"]);
+ Assert.Equal("Hello World", row!["1"]);
Assert.Equal("-1234567890", row["2"]);
Assert.Equal("False", row["3"]);
Assert.Equal("2021-01-02 00:00:00", row["4"]);
@@ -283,33 +320,27 @@ private class TestWithAlias
[Fact]
public void CsvExcelTypeTest()
{
- {
- using var file = AutoDeletingPath.Create(ExcelType.CSV);
- var path = file.ToString();
+ using var file = AutoDeletingPath.Create(ExcelType.CSV);
+ var path = file.ToString();
- var input = new[] { new { A = "Test1", B = "Test2" } };
- MiniExcel.SaveAs(path.ToString(), input);
-
- var texts = File.ReadAllLines(path.ToString());
- Assert.Equal("A,B", texts[0]);
- Assert.Equal("Test1,Test2", texts[1]);
-
- {
- var rows = MiniExcel.Query(path.ToString()).ToList();
- Assert.Equal("A", rows[0].A);
- Assert.Equal("B", rows[0].B);
- Assert.Equal("Test1", rows[1].A);
- Assert.Equal("Test2", rows[1].B);
- }
-
- using var reader = new StreamReader(path.ToString());
- using var csv = new CsvReader(reader, CultureInfo.InvariantCulture);
- {
- var rows = csv.GetRecords().ToList();
- Assert.Equal("Test1", rows[0].A);
- Assert.Equal("Test2", rows[0].B);
- }
- }
+ var input = new[] { new { A = "Test1", B = "Test2" } };
+ MiniExcel.SaveAs(path, input);
+
+ var texts = File.ReadAllLines(path);
+ Assert.Equal("A,B", texts[0]);
+ Assert.Equal("Test1,Test2", texts[1]);
+
+ var rows = MiniExcel.Query(path).ToList();
+ Assert.Equal("A", rows[0].A);
+ Assert.Equal("B", rows[0].B);
+ Assert.Equal("Test1", rows[1].A);
+ Assert.Equal("Test2", rows[1].B);
+
+ using var reader = new StreamReader(path);
+ using var csv = new CsvReader(reader, CultureInfo.InvariantCulture);
+ var records = csv.GetRecords().ToList();
+ Assert.Equal("Test1", records[0].A);
+ Assert.Equal("Test2", records[0].B);
}
[Fact]
@@ -388,7 +419,7 @@ public void CsvColumnNotFoundTest()
Assert.Equal(2, exception.RowIndex);
Assert.Null(exception.ColumnIndex);
Assert.True(exception.RowValues is IDictionary);
- Assert.Equal(1, ((IDictionary)exception.RowValues).Count);
+ Assert.Single((IDictionary)exception.RowValues);
}
{
@@ -398,7 +429,7 @@ public void CsvColumnNotFoundTest()
Assert.Equal(2, exception.RowIndex);
Assert.Null(exception.ColumnIndex);
Assert.True(exception.RowValues is IDictionary);
- Assert.Equal(1, ((IDictionary)exception.RowValues).Count);
+ Assert.Single((IDictionary)exception.RowValues);
}
}
@@ -417,7 +448,7 @@ public void CsvColumnNotFoundWithAliasTest()
Assert.Equal(2, exception.RowIndex);
Assert.Null(exception.ColumnIndex);
Assert.True(exception.RowValues is IDictionary);
- Assert.Equal(1, ((IDictionary)exception.RowValues).Count);
+ Assert.Single((IDictionary)exception.RowValues);
}
{
@@ -427,7 +458,7 @@ public void CsvColumnNotFoundWithAliasTest()
Assert.Equal(2, exception.RowIndex);
Assert.Null(exception.ColumnIndex);
Assert.True(exception.RowValues is IDictionary);
- Assert.Equal(1, ((IDictionary)exception.RowValues).Count);
+ Assert.Single((IDictionary)exception.RowValues);
}
}
diff --git a/tests/MiniExcelTests/MiniExcelIssueAsyncTests.cs b/tests/MiniExcelTests/MiniExcelIssueAsyncTests.cs
index be8933c3..62b41a9e 100644
--- a/tests/MiniExcelTests/MiniExcelIssueAsyncTests.cs
+++ b/tests/MiniExcelTests/MiniExcelIssueAsyncTests.cs
@@ -186,7 +186,7 @@ public async Task Issue242()
await Assert.ThrowsAsync(async () => _ = (await MiniExcel.QueryAsync(path)).ToList());
await using var stream = File.OpenRead(path);
- await Assert.ThrowsAsync(async () => _ = (await stream.QueryAsync()).ToList());
+ await Assert.ThrowsAsync(async () => _ = (await stream.QueryAsync()).ToList());
}
///
diff --git a/tests/MiniExcelTests/MiniExcelIssueTests.cs b/tests/MiniExcelTests/MiniExcelIssueTests.cs
index 50efaf67..5e4cd908 100644
--- a/tests/MiniExcelTests/MiniExcelIssueTests.cs
+++ b/tests/MiniExcelTests/MiniExcelIssueTests.cs
@@ -152,8 +152,8 @@ public void TestIssueI4X92G()
{
var value = new[]
{
- new { ID=1,Name ="Jack",InDate=new DateTime(2021,01,03)},
- new { ID=2,Name ="Henry",InDate=new DateTime(2020,05,03)},
+ new { ID = 1, Name = "Jack", InDate = new DateTime(2021,01,03)},
+ new { ID = 2, Name = "Henry", InDate = new DateTime(2020,05,03)}
};
MiniExcel.SaveAs(path, value);
var content = File.ReadAllText(path);
@@ -2012,7 +2012,7 @@ public void Issue242()
Assert.Throws(() => MiniExcel.Query(path).ToList());
using var stream = File.OpenRead(path);
- Assert.Throws(() => stream.Query().ToList());
+ Assert.Throws(() => stream.Query().ToList());
}
///
diff --git a/tests/MiniExcelTests/Utils/Helpers.cs b/tests/MiniExcelTests/Utils/Helpers.cs
index 0ea61e4b..40695ac7 100644
--- a/tests/MiniExcelTests/Utils/Helpers.cs
+++ b/tests/MiniExcelTests/Utils/Helpers.cs
@@ -1,8 +1,4 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.IO.Compression;
-using System.Linq;
+using System.IO.Compression;
using System.Text;
using System.Xml;
using System.Xml.Linq;
@@ -12,50 +8,43 @@ namespace MiniExcelLibs.Tests.Utils;
internal static class Helpers
{
- private const int GENERAL_COLUMN_INDEX = 255;
- private const int MAX_COLUMN_INDEX = 16383;
- private static Dictionary? _IntMappingAlphabet;
- private static Dictionary? _AlphabetMappingInt;
+ private const int GeneralColumnIndex = 255;
+ private const int MaxColumnIndex = 16383;
- static Helpers()
- {
- if (_IntMappingAlphabet != null || _AlphabetMappingInt != null)
- return;
-
- _IntMappingAlphabet = new Dictionary();
- _AlphabetMappingInt = new Dictionary();
- for (int i = 0; i <= GENERAL_COLUMN_INDEX; i++)
- {
- _IntMappingAlphabet.Add(i, IntToLetters(i));
- _AlphabetMappingInt.Add(IntToLetters(i), i);
- }
- }
+ private static readonly Dictionary IntMappingAlphabet = Enumerable
+ .Range(0, GeneralColumnIndex)
+ .ToDictionary(i => i, IntToLetters);
+
+ private static readonly Dictionary AlphabetMappingInt = Enumerable
+ .Range(0, MaxColumnIndex)
+ .ToDictionary(IntToLetters, i => i);
+
public static string GetAlphabetColumnName(int columnIndex)
{
CheckAndSetMaxColumnIndex(columnIndex);
- return _IntMappingAlphabet[columnIndex];
+ return IntMappingAlphabet[columnIndex];
}
public static int GetColumnIndex(string columnName)
{
- var columnIndex = _AlphabetMappingInt[columnName];
+ var columnIndex = AlphabetMappingInt[columnName];
CheckAndSetMaxColumnIndex(columnIndex);
return columnIndex;
}
private static void CheckAndSetMaxColumnIndex(int columnIndex)
{
- if (columnIndex < _IntMappingAlphabet.Count)
+ if (columnIndex < IntMappingAlphabet.Count)
return;
- if (columnIndex > MAX_COLUMN_INDEX)
+ if (columnIndex > MaxColumnIndex)
throw new InvalidDataException($"ColumnIndex {columnIndex} is over Excel vaild max index.");
- for (int i = _IntMappingAlphabet.Count; i <= columnIndex; i++)
+ for (int i = IntMappingAlphabet.Count; i <= columnIndex; i++)
{
- _IntMappingAlphabet.Add(i, IntToLetters(i));
- _AlphabetMappingInt.Add(IntToLetters(i), i);
+ IntMappingAlphabet.Add(i, IntToLetters(i));
+ AlphabetMappingInt.Add(IntToLetters(i), i);
}
}
@@ -87,7 +76,7 @@ internal static string GetZipFileContent(string zipPath, string filePath)
return doc.ToString();
}
- internal static string GetFirstSheetDimensionRefValue(string path)
+ internal static string? GetFirstSheetDimensionRefValue(string path)
{
var ns = new XmlNamespaceManager(new NameTable());
ns.AddNamespace("x", "http://schemas.openxmlformats.org/spreadsheetml/2006/main");
@@ -101,7 +90,7 @@ internal static string GetFirstSheetDimensionRefValue(string path)
using var sheetStream = sheet.Open();
var doc = XDocument.Load(sheetStream);
var dimension = doc.XPathSelectElement("/x:worksheet/x:dimension", ns);
- var refV = dimension.Attribute("ref").Value;
+ var refV = dimension?.Attribute("ref")?.Value;
return refV;
}
diff --git a/tests/MiniExcelTests/Utils/PathHelper.cs b/tests/MiniExcelTests/Utils/PathHelper.cs
index c3181dbc..d09df59e 100644
--- a/tests/MiniExcelTests/Utils/PathHelper.cs
+++ b/tests/MiniExcelTests/Utils/PathHelper.cs
@@ -2,16 +2,13 @@
internal static class PathHelper
{
- public static string GetFile(string fileName)
- {
- return $"../../../../../samples/{fileName}";
- }
+ public static string GetFile(string fileName) => $"../../../../../samples/{fileName}";
public static string GetTempPath(string extension = "xlsx")
{
- var method = new System.Diagnostics.StackTrace().GetFrame(1).GetMethod();
+ var method = new System.Diagnostics.StackTrace().GetFrame(1)?.GetMethod();
- var path = Path.Combine(Path.GetTempPath(), $"{method.DeclaringType.Name}_{method.Name}.{extension}")
+ var path = Path.Combine(Path.GetTempPath(), $"{method?.DeclaringType?.Name}_{method?.Name}.{extension}")
.Replace("<", string.Empty)
.Replace(">", string.Empty);