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
10 changes: 10 additions & 0 deletions README-V2.md
Original file line number Diff line number Diff line change
Expand Up @@ -1757,6 +1757,16 @@ var importer = MiniExcel.Importers.GetOpenXmlImporter();
var dim = importer.GetSheetDimensions(path);
```

#### 8. Retrieve Table Data

It is possible to query arbitrary tables from any worksheet.
You can either keep it dynamic or map it to a strong-typed object like reqular queries:

```csharp
var importer = MiniExcel.Importers.GetOpenXmlImporter();
var rows = importer.QueryTable(stream, "Sheet1", "YourTable").ToList();
```

### FAQ <a name="docs-faq" />

#### Q: Excel header title is not equal to my DTO class property name, how do I map it?
Expand Down
4 changes: 2 additions & 2 deletions src/MiniExcel.Core/Reflection/MiniExcelMapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
{
if (headersDic?.TryGetValue(alias, out var columnId) is true)
{
var columnName = keys[columnId];

Check warning on line 46 in src/MiniExcel.Core/Reflection/MiniExcelMapper.cs

View workflow job for this annotation

GitHub Actions / build

Dereference of a possibly null reference.
item.TryGetValue(columnName, out var aliasItemValue);

if (aliasItemValue is not null)
Expand All @@ -56,13 +56,13 @@

//Q: Why need to check every time? A: it needs to check everytime, because it's dictionary
object? itemValue = null;
if (map.ExcelIndexName is not null && (keys?.Contains(map.ExcelIndexName) is true))
if (map.ExcelIndexName is not null && keys?.Contains(map.ExcelIndexName) is true)
{
item.TryGetValue(map.ExcelIndexName, out itemValue);
}
else if (map.ExcelColumnName is not null && (headersDic?.TryGetValue(map.ExcelColumnName, out var columnId) is true))
else if (map.ExcelColumnName is not null && headersDic?.TryGetValue(map.ExcelColumnName, out var columnId) is true)
{
var columnName = keys[columnId];

Check warning on line 65 in src/MiniExcel.Core/Reflection/MiniExcelMapper.cs

View workflow job for this annotation

GitHub Actions / build

Dereference of a possibly null reference.
item.TryGetValue(columnName, out itemValue);
}

Expand Down
95 changes: 92 additions & 3 deletions src/MiniExcel.OpenXml/Api/OpenXmlImporter.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using MiniExcelLib.OpenXml.Reader;

// ReSharper disable once CheckNamespace
namespace MiniExcelLib.OpenXml;

Expand Down Expand Up @@ -326,7 +328,7 @@ public async Task<List<string>> GetSheetNamesAsync(Stream stream, bool leaveOpen
await using var disposableArchive = archive.ConfigureAwait(false);
using var reader = await OpenXmlReader.CreateAsync(stream, null, leaveOpen, cancellationToken).ConfigureAwait(false);

var rels = await reader.GetWorkbookRelsAsync(archive.EntryCollection, cancellationToken).ConfigureAwait(false);
var rels = await OpenXmlReader.GetWorkbookRelsAsync(archive.EntryCollection, cancellationToken).ConfigureAwait(false);
return rels?.Select(s => s.Name).ToList() ?? [];
}

Expand Down Expand Up @@ -366,7 +368,7 @@ public async Task<List<SheetInfo>> GetSheetInformationsAsync(Stream stream, bool
await using var disposableArchve = archive.ConfigureAwait(false);
using var reader = await OpenXmlReader.CreateAsync(stream, null, leaveOpen, cancellationToken).ConfigureAwait(false);

var rels = await reader.GetWorkbookRelsAsync(archive.EntryCollection, cancellationToken).ConfigureAwait(false);
var rels = await OpenXmlReader.GetWorkbookRelsAsync(archive.EntryCollection, cancellationToken).ConfigureAwait(false);
return rels?.Select((s, i) => s.ToSheetInfo((uint)i)).ToList() ?? [];
}

Expand Down Expand Up @@ -433,7 +435,6 @@ public async Task<ICollection<string>> GetColumnNamesAsync(string path, bool has
return await GetColumnNamesAsync(stream, hasHeaderRow, sheetName, startCell, false, cancellationToken).ConfigureAwait(false);
}


/// <summary>
/// Retrieves the column names from the first row (header row) of an Excel sheet.
/// </summary>
Expand Down Expand Up @@ -497,6 +498,94 @@ public async Task<CommentResultSet> RetrieveCommentsAsync(Stream stream, string?
return await reader.ReadCommentsAsync(sheetName, cancellationToken).ConfigureAwait(false);
}

/// <summary>
/// Queries a named table in an Excel worksheet and returns dynamic objects representing each row.
/// </summary>
/// <param name="path">The path to the Excel document.</param>
/// <param name="sheetName">The name of the worksheet containing the table. Default is "Sheet1".</param>
/// <param name="tableName">The name of the table to query. Default is "Table1".</param>
/// <param name="cancellationToken">A token to cancel the asynchronous operation.</param>
/// <remarks>
/// Named tables in Excel are structured data ranges with defined column headers and a unique name.
/// This method reads from the specified table within a stream and yields rows as dynamic objects with properties based on the table's column names.
/// </remarks>
[CreateSyncVersion]
public async IAsyncEnumerable<dynamic> QueryTableAsync(string path, string sheetName = "Sheet1", string tableName = "Table1", [EnumeratorCancellation] CancellationToken cancellationToken = default)
{
var stream = FileHelper.OpenSharedRead(path);
await using var disposableStream = stream.ConfigureAwait(false);

using var reader = await OpenXmlReader.CreateAsync(stream, null, false, cancellationToken).ConfigureAwait(false);
await foreach (var table in reader.QueryTableAsync(sheetName, tableName, false, cancellationToken).ConfigureAwait(false))
yield return table;
}

/// <summary>
/// Queries a named table in an Excel worksheet and returns dynamic objects representing each row.
/// </summary>
/// <param name="stream">The stream containing the Excel file data. The stream position is not reset after reading.</param>
/// <param name="sheetName">The name of the worksheet containing the table. Default is "Sheet1".</param>
/// <param name="tableName">The name of the table to query. Default is "Table1".</param>
/// <param name="leaveOpen">True to leave the stream open after the query is completed, otherwise false.</param>
/// <param name="cancellationToken">A token to cancel the asynchronous operation.</param>
/// <remarks>
/// Named tables in Excel are structured data ranges with defined column headers and a unique name.
/// This method reads from the specified table within a stream and yields rows as dynamic objects with properties based on the table's column names.
/// </remarks>
[CreateSyncVersion]
public async IAsyncEnumerable<dynamic> QueryTableAsync(Stream stream, string sheetName = "Sheet1", string tableName = "Table1", bool leaveOpen = false, [EnumeratorCancellation] CancellationToken cancellationToken = default)
{
using var reader = await OpenXmlReader.CreateAsync(stream, null, leaveOpen, cancellationToken).ConfigureAwait(false);
await foreach (var table in reader.QueryTableAsync(sheetName, tableName, false, cancellationToken).ConfigureAwait(false))
yield return table;
}

/// <summary>
/// Queries a named table in an Excel worksheet and returns strongly-typed objects representing each row.
/// </summary>
/// <typeparam name="T">The class type to map each row to. Must have a parameterless constructor. Property names should match the table's column names.</typeparam>
/// <param name="path">The path to the Excel document. The stream position is not reset after reading.</param>
/// <param name="sheetName">The name of the worksheet containing the table. Default is "Sheet1".</param>
/// <param name="tableName">The name of the table to query. Default is "Table1".</param>
/// <param name="cancellationToken">A token to cancel the asynchronous operation.</param>
/// <remarks>
/// Named tables in Excel are structured data ranges with defined column headers and a unique name.
/// This method reads from the specified table within a stream and maps each row to an instance of the provided type. The mapping is based on property/field names matching column headers.
/// </remarks>
[CreateSyncVersion]
public async IAsyncEnumerable<T> QueryTableAsync<T>(string path, string sheetName = "Sheet1", string tableName = "Table1", [EnumeratorCancellation] CancellationToken cancellationToken = default)
where T : class, new()
{
var stream = FileHelper.OpenSharedRead(path);
await using var disposableStream = stream.ConfigureAwait(false);

using var reader = await OpenXmlReader.CreateAsync(stream, null, false, cancellationToken).ConfigureAwait(false);
await foreach (var table in reader.QueryTableAsync<T>(sheetName, tableName, cancellationToken).ConfigureAwait(false))
yield return table;
}

/// <summary>
/// Queries a named table in an Excel worksheet and returns strongly-typed objects representing each row.
/// </summary>
/// <typeparam name="T">The class type to map each row to. Must have a parameterless constructor. Property names should match the table's column names.</typeparam>
/// <param name="stream">The stream containing the Excel file data. The stream position is not reset after reading.</param>
/// <param name="sheetName">The name of the worksheet containing the table. Default is "Sheet1".</param>
/// <param name="tableName">The name of the table to query. Default is "Table1".</param>
/// <param name="leaveOpen">True to leave the stream open after the query is completed, otherwise false.</param>
/// <param name="cancellationToken">A token to cancel the asynchronous operation.</param>
/// <remarks>
/// Named tables in Excel are structured data ranges with defined column headers and a unique name.
/// This method reads from the specified table within a stream and maps each row to an instance of the provided type. The mapping is based on property/field names matching column headers.
/// </remarks>
[CreateSyncVersion]
public async IAsyncEnumerable<T> QueryTableAsync<T>(Stream stream, string sheetName = "Sheet1", string tableName = "Table1", bool leaveOpen = false, [EnumeratorCancellation] CancellationToken cancellationToken = default)
where T : class, new()
{
using var reader = await OpenXmlReader.CreateAsync(stream, null, leaveOpen, cancellationToken).ConfigureAwait(false);
await foreach (var table in reader.QueryTableAsync<T>(sheetName, tableName, cancellationToken).ConfigureAwait(false))
yield return table;
}

#endregion

#region DataReader
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
using System.Text.RegularExpressions;

namespace MiniExcelLib.OpenXml.FluentMapping.Configuration;

internal partial class CollectionMappingBuilder<T, TCollection> : ICollectionMappingBuilder<T, TCollection> where TCollection : IEnumerable
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
using System.Text.RegularExpressions;

namespace MiniExcelLib.OpenXml.FluentMapping.Configuration;

internal partial class PropertyMappingBuilder<T, TProperty> : IPropertyMappingBuilder<T, TProperty>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
using System.Collections.Concurrent;
using System.Globalization;
using System.Reflection;

namespace MiniExcelLib.OpenXml.FluentMapping.Helpers;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
using System.Reflection;
using MiniExcelLib.Core.Helpers;
using MiniExcelLib.Core.Reflection;
using MiniExcelLib.OpenXml.FluentMapping.Helpers;

namespace MiniExcelLib.OpenXml.FluentMapping;
Expand Down
2 changes: 0 additions & 2 deletions src/MiniExcel.OpenXml/FluentMapping/MappingCellStream.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
using MiniExcelLib.Core.Abstractions;

namespace MiniExcelLib.OpenXml.FluentMapping;

internal readonly struct MappingCellStream<T>(IEnumerable<T> items, CompiledMapping<T> mapping, string[] columnLetters) : IMappingCellStream
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
using MiniExcelLib.Core.Abstractions;
using MiniExcelLib.Core.Reflection;

namespace MiniExcelLib.OpenXml.FluentMapping;

internal class MappingCellStreamAdapter<T>(MappingCellStream<T> cellStream, string[] columnLetters)
Expand Down
4 changes: 0 additions & 4 deletions src/MiniExcel.OpenXml/FluentMapping/MappingCompiler.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
using System.Reflection;
using MiniExcelLib.Core.Helpers;
using MiniExcelLib.Core.Reflection;
using MiniExcelLib.OpenXml.FluentMapping.Configuration;
using MiniExcelLib.OpenXml.FluentMapping.Helpers;
using MiniExcelLib.OpenXml.Utils;

namespace MiniExcelLib.OpenXml.FluentMapping;

Expand Down
7 changes: 4 additions & 3 deletions src/MiniExcel.OpenXml/FluentMapping/MappingReader.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using MiniExcelLib.OpenXml.Reader;

namespace MiniExcelLib.OpenXml.FluentMapping;

internal static partial class MappingReader<T> where T : class, new()
Expand Down Expand Up @@ -203,8 +205,7 @@ private static Dictionary<int, IList> InitializeCollections(CompiledMapping<T> m
else
{
// This should never happen with properly optimized mappings
throw new InvalidOperationException(
"OptimizedCollectionHelpers is null. Ensure the mapping was properly compiled and optimized.");
throw new InvalidOperationException("OptimizedCollectionHelpers is null. Ensure the mapping was properly compiled and optimized.");
}

return collections;
Expand Down Expand Up @@ -469,4 +470,4 @@ private static bool HasAnyData(T item, CompiledMapping<T> mapping)
bool b => !b,
_ => false
};
}
}
21 changes: 2 additions & 19 deletions src/MiniExcel.OpenXml/FluentMapping/MappingTemplateProcessor.cs
Original file line number Diff line number Diff line change
@@ -1,27 +1,11 @@
using System.Text;
using System.Xml;
using MiniExcelLib.OpenXml.Helpers;
using MiniExcelLib.OpenXml.Utils;
using Zomp.SyncMethodGenerator;

namespace MiniExcelLib.OpenXml.FluentMapping;

internal partial struct MappingTemplateProcessor<T>(CompiledMapping<T> mapping) where T : class
{
[CreateSyncVersion]
public async Task ProcessSheetAsync(
Stream sourceStream,
Stream targetStream,
IEnumerator<T> dataEnumerator,
CancellationToken cancellationToken)
public async Task ProcessSheetAsync(Stream sourceStream, Stream targetStream, IEnumerator<T> dataEnumerator, CancellationToken cancellationToken)
{
var readerSettings = new XmlReaderSettings
{
Async = true,
IgnoreWhitespace = false,
IgnoreComments = false,
CheckCharacters = false
};
var readerSettings = XmlReaderHelper.GetXmlReaderSettings();

var writerSettings = new XmlWriterSettings
{
Expand All @@ -38,7 +22,6 @@ public async Task ProcessSheetAsync(
var currentItem = dataEnumerator.MoveNext() ? dataEnumerator.Current : null;
var currentItemIndex = currentItem is not null ? 0 : -1;


// Track which rows have been written from the template
var writtenRows = new HashSet<int>();

Expand Down
4 changes: 2 additions & 2 deletions src/MiniExcel.OpenXml/Models/ExcelRange.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ internal ExcelRangeElement(int startIndex, int endIndex)

public class ExcelRange(int maxRow, int maxColumn)
{
public string StartCell { get; internal set; }
public string EndCell { get; internal set; }
public string? StartCell { get; internal set; }
public string? EndCell { get; internal set; }

public ExcelRangeElement Rows { get; } = new(1, maxRow);
public ExcelRangeElement Columns { get; } = new(1, maxColumn);
Expand Down
17 changes: 17 additions & 0 deletions src/MiniExcel.OpenXml/Models/TableInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
namespace MiniExcelLib.OpenXml.Models;

public class TableInfo
{
internal TableInfo(string name, IEnumerable<string> columns, string? referenceCells, bool hiddenHeader)
{
Name = name;
Columns = [..columns];
ReferenceCells = referenceCells;
HiddenHeader = hiddenHeader;
}

public string Name { get; private set; }
public string[] Columns { get; private set; }
public string? ReferenceCells { get; private set; }
public bool HiddenHeader { get; private set; }
}
3 changes: 2 additions & 1 deletion src/MiniExcel.OpenXml/Picture/OpenXmlPictureImplement.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Drawing;
using MiniExcelLib.OpenXml.Reader;

namespace MiniExcelLib.OpenXml.Picture;

Expand Down Expand Up @@ -27,7 +28,7 @@ public static async Task AddPictureAsync(Stream excelStream, CancellationToken c
#else
using var archive = new ZipArchive(excelStream, ZipArchiveMode.Update, true);
#endif
var rels = await reader.GetWorkbookRelsAsync(excelArchive.EntryCollection, cancellationToken).ConfigureAwait(false);
var rels = await OpenXmlReader.GetWorkbookRelsAsync(excelArchive.EntryCollection, cancellationToken).ConfigureAwait(false);
var sheetEntries = rels?.ToList() ?? [];

// Group images by sheet
Expand Down
Loading
Loading