Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 36 additions & 6 deletions README-V2.md
Original file line number Diff line number Diff line change
Expand Up @@ -342,17 +342,47 @@ var excelImporter = MiniExcel.Importers.GetOpenXmlImporter();
var sheetNames = excelImporter.GetSheetNames(path);
```

#### 6. Get the columns' names from an Excel sheet
#### 6. Get the columns' names from an Excel worksheet

```csharp
var excelImporter = MiniExcel.Importers.GetOpenXmlImporter();
var columns = excelImporter.GetColumnNames(path);

// columns = [ColumnName1, ColumnName2, ...] when there is a header row
// columns = ["A","B",...] otherwise

```
#### 7. Retrieve Comments from an Excel worksheet

You can extract threaded comments and their replies from a worksheet using the `RetrieveComments` method:

```csharp
var excelImporter = MiniExcel.Importers.GetOpenXmlImporter();
var comments = excelImporter.RetrieveComments(path, sheetName: "Sheet1").Comments;

foreach (var comment in comments)
{
Console.WriteLine($"Cell: {comment.CellAddress}");
Console.WriteLine($"{comment.CreatedAt:yy-MM-dd HH:mm}, {comment.Author.DisplayName}: {comment.Text}");

foreach (var reply in comment.Replies)
{
Console.WriteLine($"{reply.CreatedAt:yy-MM-dd HH:mm}, {reply.Author.DisplayName}: {reply.Text}");
}
}
```

You can similarly retrieve notes as well:
```csharp
var notes = excelImporter.RetrieveComments(path, sheetName: "Sheet1").Notes;
foreach (var note in notes)
{
Console.WriteLine($"Cell: {note.CellAddress}");
Console.WriteLine($"{note.Author.DisplayName}: {note.Text}");
}
```

#### 7. Casting dynamic rows to IDictionary
#### 8. Casting dynamic rows to IDictionary

Under the hood the dynamic objects returned in a query are implemented using `ExpandoObject`,
making it possible to cast them to `IDictionary<string,object>`:
Expand All @@ -370,7 +400,7 @@ foreach(IDictionary<string,object> row in excelImporter.Query(path))
}
```

#### 8. Query Excel sheet as a DataTable
#### 9. Query Excel worksheet as a DataTable

This is not recommended, as `DataTable` will forcibly load all data into memory, effectively losing the advantages MiniExcel offers.

Expand All @@ -379,15 +409,15 @@ var excelImporter = MiniExcel.Importers.GetOpenXmlImporter();
var table = excelImporter.QueryAsDataTable(path);
```

#### 9. Specify what cell to start reading data from
#### 10. Specify what cell to start reading data from

```csharp
var excelImporter = MiniExcel.Importers.GetOpenXmlImporter();
excelImporter.Query(path, startCell: "B3")
```
![image](https://user-images.githubusercontent.com/12729184/117260316-8593c400-ae81-11eb-9877-c087b7ac2b01.png)

#### 10. Fill Merged Cells
#### 11. Fill Merged Cells

If the Excel sheet being queried contains merged cells it is possble to enable the option to fill every row with the merged value.

Expand All @@ -410,7 +440,7 @@ Filling of cells with variable width and height is also supported
>Note: The performance will take a hit when enabling the feature.
>This happens because in the OpenXml standard the `mergeCells` are indicated at the bottom of the file, which leads to the need of reading the whole sheet twice.

#### 11. Big files and disk-based cache
#### 12. Big files and disk-based cache

If the SharedStrings file size exceeds 5 MB, MiniExcel will default to use a local disk cache.
E.g: on the file [10x100000.xlsx](https://github.com/MiniExcel/MiniExcel/files/8403819/NotDuplicateSharedStrings_10x100000.xlsx) (one million rows of data), when disabling the disk cache the maximum memory usage is 195 MB, but with disk cache enabled only 65 MB of memory are used.
Expand Down
2 changes: 1 addition & 1 deletion src/Directory.Packages.props
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project>
<ItemGroup>
<PackageReference Include="Zomp.SyncMethodGenerator" Version="1.6.13" PrivateAssets="all" IncludeAssets="runtime; build; native; contentfiles; analyzers; buildtransitive" />
<PackageReference Include="Zomp.SyncMethodGenerator" Version="[1.6.13]" PrivateAssets="all" IncludeAssets="runtime; build; native; contentfiles; analyzers; buildtransitive" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
<PackageReference Include="Microsoft.Bcl.Memory" Version="10.0.5" />
Expand Down
2 changes: 1 addition & 1 deletion src/MiniExcel.Core/WriteAdapters/DataReaderWriteAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
{
var columnName = _reader.GetName(i);
if (!_configuration.DynamicColumnFirst ||
_configuration.DynamicColumns.Any(d => string.Equals(d.Key, columnName, StringComparison.OrdinalIgnoreCase)))
_configuration.DynamicColumns?.Any(d => string.Equals(d.Key, columnName, StringComparison.OrdinalIgnoreCase)) is true)
{
var map = ColumnMappingsProvider.GetColumnMappingFromDynamicConfiguration(columnName, _configuration);
mappings.Add(map);
Expand Down Expand Up @@ -47,7 +47,7 @@
if (map is not { ExcelIgnoreColumn: true })
{
var columnIndex = _configuration.DynamicColumnFirst
? _reader.GetOrdinal(map.Key.ToString())

Check warning on line 50 in src/MiniExcel.Core/WriteAdapters/DataReaderWriteAdapter.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Possible null reference argument for parameter 'name' in 'int IDataRecord.GetOrdinal(string name)'.
: i;

result.Add(new CellWriteInfo(_reader.GetValue(columnIndex), column, map));
Expand Down
12 changes: 11 additions & 1 deletion src/MiniExcel.OpenXml/Api/OpenXmlExporter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,12 @@ public async Task<int> InsertSheetAsync(string path, object value, string? sheet
return rowsWritten.FirstOrDefault();
}

#if NET8_0_OR_GREATER
var stream = new FileStream(path, FileMode.Open, FileAccess.ReadWrite, FileShare.Read, 4096, FileOptions.SequentialScan);
await using var disposableStream = stream.ConfigureAwait(false);
#else
using var stream = new FileStream(path, FileMode.Open, FileAccess.ReadWrite, FileShare.Read, 4096, FileOptions.SequentialScan);
#endif
return await InsertSheetAsync(stream, value, sheetName, printHeader, overwriteSheet, configuration, progress, cancellationToken).ConfigureAwait(false);
}

Expand Down Expand Up @@ -46,8 +51,13 @@ public async Task<int[]> ExportAsync(string path, object value, bool printHeader
throw new NotSupportedException("MiniExcel's Export does not support the .xlsm format");

var filePath = path.EndsWith(".xlsx", StringComparison.InvariantCultureIgnoreCase) ? path : $"{path}.xlsx" ;


#if NET8_0_OR_GREATER
var stream = overwriteFile ? File.Create(filePath) : new FileStream(filePath, FileMode.CreateNew);
await using var disposableStream = stream.ConfigureAwait(false);
#else
using var stream = overwriteFile ? File.Create(filePath) : new FileStream(filePath, FileMode.CreateNew);
#endif
return await ExportAsync(stream, value, printHeader, sheetName, configuration, progress, cancellationToken).ConfigureAwait(false);
}

Expand Down
69 changes: 65 additions & 4 deletions src/MiniExcel.OpenXml/Api/OpenXmlImporter.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
using System.Dynamic;
using MiniExcelLib.OpenXml;
using OpenXmlReader = MiniExcelLib.OpenXml.OpenXmlReader;

Expand All @@ -16,11 +15,14 @@ public async IAsyncEnumerable<T> QueryAsync<T>(string path, string? sheetName =
string startCell = "A1", bool treatHeaderAsData = false, OpenXmlConfiguration? configuration = null,
[EnumeratorCancellation] CancellationToken cancellationToken = default) where T : class, new()
{
#if NET8_0_OR_GREATER
var stream = FileHelper.OpenSharedRead(path);
await using var disposableStream = stream.ConfigureAwait(false);
#else
using var stream = FileHelper.OpenSharedRead(path);

#endif
var query = QueryAsync<T>(stream, sheetName, startCell, treatHeaderAsData, configuration, cancellationToken);

//Foreach yield return twice reason : https://stackoverflow.com/questions/66791982/ienumerable-extract-code-lazy-loading-show-stream-was-not-readable
await foreach (var item in query.ConfigureAwait(false))
yield return item;
}
Expand All @@ -40,7 +42,13 @@ public async IAsyncEnumerable<dynamic> QueryAsync(string path, bool useHeaderRow
string? sheetName = null, string startCell = "A1", OpenXmlConfiguration? configuration = null,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
#if NET8_0_OR_GREATER
var stream = FileHelper.OpenSharedRead(path);
await using var disposableStream = stream.ConfigureAwait(false);
#else
using var stream = FileHelper.OpenSharedRead(path);
#endif

await foreach (var item in QueryAsync(stream, useHeaderRow, sheetName, startCell, configuration, cancellationToken).ConfigureAwait(false))
yield return item;
}
Expand Down Expand Up @@ -72,7 +80,12 @@ public async IAsyncEnumerable<dynamic> QueryRangeAsync(string path, bool useHead
string? sheetName = null, string startCell = "A1", string endCell = "", OpenXmlConfiguration? configuration = null,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
#if NET8_0_OR_GREATER
var stream = FileHelper.OpenSharedRead(path);
await using var disposableStream = stream.ConfigureAwait(false);
#else
using var stream = FileHelper.OpenSharedRead(path);
#endif
await foreach (var item in QueryRangeAsync(stream, useHeaderRow, sheetName, startCell, endCell, configuration, cancellationToken).ConfigureAwait(false))
yield return item;
}
Expand All @@ -93,7 +106,12 @@ public async IAsyncEnumerable<dynamic> QueryRangeAsync(string path, bool useHead
int? endColumnIndex = null, OpenXmlConfiguration? configuration = null,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
#if NET8_0_OR_GREATER
var stream = FileHelper.OpenSharedRead(path);
await using var disposableStream = stream.ConfigureAwait(false);
#else
using var stream = FileHelper.OpenSharedRead(path);
#endif
await foreach (var item in QueryRangeAsync(stream, useHeaderRow, sheetName, startRowIndex, startColumnIndex, endRowIndex, endColumnIndex, configuration, cancellationToken).ConfigureAwait(false))
yield return item;
}
Expand Down Expand Up @@ -121,7 +139,12 @@ public async Task<DataTable> QueryAsDataTableAsync(string path, bool useHeaderRo
string? sheetName = null, string startCell = "A1", OpenXmlConfiguration? configuration = null,
CancellationToken cancellationToken = default)
{
#if NET8_0_OR_GREATER
var stream = FileHelper.OpenSharedRead(path);
await using var disposableStream = stream.ConfigureAwait(false);
#else
using var stream = FileHelper.OpenSharedRead(path);
#endif
return await QueryAsDataTableAsync(stream, useHeaderRow, sheetName, startCell, configuration, cancellationToken).ConfigureAwait(false);
}

Expand All @@ -133,7 +156,6 @@ public async Task<DataTable> QueryAsDataTableAsync(Stream stream, bool useHeader
string? sheetName = null, string startCell = "A1", OpenXmlConfiguration? configuration = null,
CancellationToken cancellationToken = default)
{
/*Issue #279*/
sheetName ??= (await GetSheetNamesAsync(stream, configuration, cancellationToken).ConfigureAwait(false)).First();

var dt = new DataTable(sheetName);
Expand Down Expand Up @@ -187,7 +209,12 @@ public async Task<DataTable> QueryAsDataTableAsync(Stream stream, bool useHeader
[CreateSyncVersion]
public async Task<List<string>> GetSheetNamesAsync(string path, OpenXmlConfiguration? config = null, CancellationToken cancellationToken = default)
{
#if NET8_0_OR_GREATER
var stream = FileHelper.OpenSharedRead(path);
await using var disposableStream = stream.ConfigureAwait(false);
#else
using var stream = FileHelper.OpenSharedRead(path);
#endif
return await GetSheetNamesAsync(stream, config, cancellationToken).ConfigureAwait(false);
}

Expand All @@ -207,7 +234,12 @@ public async Task<List<string>> GetSheetNamesAsync(Stream stream, OpenXmlConfigu
[CreateSyncVersion]
public async Task<List<SheetInfo>> GetSheetInformationsAsync(string path, OpenXmlConfiguration? config = null, CancellationToken cancellationToken = default)
{
#if NET8_0_OR_GREATER
var stream = FileHelper.OpenSharedRead(path);
await using var disposableStream = stream.ConfigureAwait(false);
#else
using var stream = FileHelper.OpenSharedRead(path);
#endif
return await GetSheetInformationsAsync(stream, config, cancellationToken).ConfigureAwait(false);
}

Expand All @@ -226,7 +258,12 @@ public async Task<List<SheetInfo>> GetSheetInformationsAsync(Stream stream, Open
[CreateSyncVersion]
public async Task<IList<ExcelRange>> GetSheetDimensionsAsync(string path, CancellationToken cancellationToken = default)
{
#if NET8_0_OR_GREATER
var stream = FileHelper.OpenSharedRead(path);
await using var disposableStream = stream.ConfigureAwait(false);
#else
using var stream = FileHelper.OpenSharedRead(path);
#endif
return await GetSheetDimensionsAsync(stream, cancellationToken).ConfigureAwait(false);
}

Expand All @@ -242,7 +279,12 @@ public async Task<ICollection<string>> GetColumnNamesAsync(string path, bool use
string? sheetName = null, string startCell = "A1", OpenXmlConfiguration? configuration = null,
CancellationToken cancellationToken = default)
{
#if NET8_0_OR_GREATER
var stream = FileHelper.OpenSharedRead(path);
await using var disposableStream = stream.ConfigureAwait(false);
#else
using var stream = FileHelper.OpenSharedRead(path);
#endif
return await GetColumnNamesAsync(stream, useHeaderRow, sheetName, startCell, configuration, cancellationToken).ConfigureAwait(false);
}

Expand All @@ -260,6 +302,25 @@ public async Task<ICollection<string>> GetColumnNamesAsync(Stream stream, bool u
return [];
}

[CreateSyncVersion]
public async Task<CommentResultSet> RetrieveCommentsAsync(string path, string? sheetName, CancellationToken cancellationToken = default)
{
#if NET8_0_OR_GREATER
var stream = FileHelper.OpenSharedRead(path);
await using var disposableStream = stream.ConfigureAwait(false);
#else
using var stream = FileHelper.OpenSharedRead(path);
#endif
return await RetrieveCommentsAsync(stream, sheetName, cancellationToken).ConfigureAwait(false);
}

[CreateSyncVersion]
public async Task<CommentResultSet> RetrieveCommentsAsync(Stream stream, string? sheetName, CancellationToken cancellationToken = default)
{
using var reader = await OpenXmlReader.CreateAsync(stream, null, cancellationToken).ConfigureAwait(false);
return await reader.ReadCommentsAsync(sheetName, cancellationToken).ConfigureAwait(false);
}

#endregion

#region DataReader
Expand Down
26 changes: 26 additions & 0 deletions src/MiniExcel.OpenXml/Api/OpenXmlTemplater.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,12 @@ internal OpenXmlTemplater() { }
[CreateSyncVersion]
public async Task AddPictureAsync(string path, CancellationToken cancellationToken = default, params MiniExcelPicture[] images)
{
#if NET8_0_OR_GREATER
var stream = File.Open(path, FileMode.OpenOrCreate);
await using var disposableStream = stream.ConfigureAwait(false);
#else
using var stream = File.Open(path, FileMode.OpenOrCreate);
#endif
await MiniExcelPictureImplement.AddPictureAsync(stream, cancellationToken, images).ConfigureAwait(false);
}

Expand All @@ -27,15 +32,26 @@ public async Task AddPictureAsync(Stream excelStream, CancellationToken cancella
public async Task FillTemplateAsync(string path, string templatePath, object value, bool overwriteFile = false,
OpenXmlConfiguration? configuration = null, CancellationToken cancellationToken = default)
{
#if NET8_0_OR_GREATER
var stream = overwriteFile ? File.Create(path) : File.Open(path, FileMode.CreateNew);
await using var disposableStream = stream.ConfigureAwait(false);
#else
using var stream = overwriteFile ? File.Create(path) : File.Open(path, FileMode.CreateNew);
#endif
await FillTemplateAsync(stream, templatePath, value, configuration, cancellationToken).ConfigureAwait(false);
}

[CreateSyncVersion]
public async Task FillTemplateAsync(string path, Stream templateStream, object value, bool overwriteFile = false,
OpenXmlConfiguration? configuration = null, CancellationToken cancellationToken = default)
{
#if NET8_0_OR_GREATER
var stream = overwriteFile ? File.Create(path) : File.Open(path, FileMode.CreateNew);
await using var disposableStream = stream.ConfigureAwait(false);
#else
using var stream = overwriteFile ? File.Create(path) : File.Open(path, FileMode.CreateNew);
#endif

var template = GetOpenXmlTemplate(stream, configuration);
await template.SaveAsByTemplateAsync(templateStream, value, cancellationToken).ConfigureAwait(false);
}
Expand All @@ -60,7 +76,12 @@ public async Task FillTemplateAsync(Stream stream, Stream templateStream, object
public async Task FillTemplateAsync(string path, byte[] templateBytes, object value, bool overwriteFile = false,
OpenXmlConfiguration? configuration = null, CancellationToken cancellationToken = default)
{
#if NET8_0_OR_GREATER
var stream = overwriteFile ? File.Create(path) : File.Open(path, FileMode.CreateNew);
await using var disposableStream = stream.ConfigureAwait(false);
#else
using var stream = overwriteFile ? File.Create(path) : File.Open(path, FileMode.CreateNew);
#endif
await FillTemplateAsync(stream, templateBytes, value, configuration, cancellationToken).ConfigureAwait(false);
}

Expand All @@ -78,7 +99,12 @@ public async Task FillTemplateAsync(Stream stream, byte[] templateBytes, object
public async Task MergeSameCellsAsync(string mergedFilePath, string path,
OpenXmlConfiguration? configuration = null, CancellationToken cancellationToken = default)
{
#if NET8_0_OR_GREATER
var stream = File.Create(mergedFilePath);
await using var disposableStream = stream.ConfigureAwait(false);
#else
using var stream = File.Create(mergedFilePath);
#endif
await MergeSameCellsAsync(stream, path, configuration, cancellationToken).ConfigureAwait(false);
}

Expand Down
17 changes: 12 additions & 5 deletions src/MiniExcel.OpenXml/Constants/Schemas.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,16 @@

internal static class Schemas
{
public const string SpreadsheetmlXmlns = "http://schemas.openxmlformats.org/spreadsheetml/2006/main";
public const string SpreadsheetmlXmlStrictns = "http://purl.oclc.org/ooxml/spreadsheetml/main";
public const string SpreadsheetmlXmlRelationshipns = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
public const string SpreadsheetmlXmlStrictRelationshipns = "http://purl.oclc.org/ooxml/officeDocument/relationships";
public const string SpreadsheetmlXmlNs = "http://schemas.openxmlformats.org/spreadsheetml/2006/main";
public const string SpreadsheetmlXmlStrictNs = "http://purl.oclc.org/ooxml/spreadsheetml/main";

public const string OpenXmlPackageRelationships = "http://schemas.openxmlformats.org/package/2006/relationships";
public const string SpreadsheetmlXmlRelationships = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
public const string SpreadsheetmlXmlStrictRelationships = "http://purl.oclc.org/ooxml/officeDocument/relationships";
public const string SpreadsheetmlXmlComments = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments";
public const string SpreadsheetmlXmlThreadedComment = "http://schemas.microsoft.com/office/2017/10/relationships/threadedComment";

public const string SpreadsheetmlXmlX14Ac = "http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac";
}
public const string SpreadsheetmlXmlX18Tc = "http://schemas.microsoft.com/office/spreadsheetml/2018/threadedcomments";
public const string SpreadsheetmlXmlX14R = "http://schemas.microsoft.com/office/spreadsheetml/2014/revision";
}
Loading
Loading