Skip to content

Commit

Permalink
Add functionality that allows you to read and write the visibility of…
Browse files Browse the repository at this point in the history
… excel sheets (Issue #531) (#563)

* Read and write excel documents using DynamicExcelSheet and ExcelSheetAttribute

* Fix in generating the default sheet name

---------

Co-authored-by: Paweł Szybiak <pawel.szybiak@vsoft.pl>
  • Loading branch information
pszybiak and Paweł Szybiak authored Feb 3, 2024
1 parent 71f9775 commit e6a4813
Show file tree
Hide file tree
Showing 12 changed files with 453 additions and 34 deletions.
50 changes: 50 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1121,7 +1121,42 @@ Since V1.26.0, we can set the attributes of Column dynamically
```
![image](https://user-images.githubusercontent.com/12729184/164510353-5aecbc4e-c3ce-41e8-b6cf-afd55eb23b68.png)

#### 8. DynamicSheetAttribute

Since V1.31.4 we can set the attributes of Sheet dynamically. We can set sheet name and state (visibility).
```csharp
var configuration = new OpenXmlConfiguration
{
DynamicSheets = new DynamicExcelSheet[] {
new DynamicExcelSheet("usersSheet") { Name = "Users", State = SheetState.Visible },
new DynamicExcelSheet("departmentSheet") { Name = "Departments", State = SheetState.Hidden }
}
};

var users = new[] { new { Name = "Jack", Age = 25 }, new { Name = "Mike", Age = 44 } };
var department = new[] { new { ID = "01", Name = "HR" }, new { ID = "02", Name = "IT" } };
var sheets = new Dictionary<string, object>
{
["usersSheet"] = users,
["departmentSheet"] = department
};

var path = PathHelper.GetTempPath();
MiniExcel.SaveAs(path, sheets, configuration: configuration);
```

We can also use new attribute ExcelSheetAttribute:

```C#
[ExcelSheet(Name = "Departments", State = SheetState.Hidden)]
private class DepartmentDto
{
[ExcelColumn(Name = "ID",Index = 0)]
public string ID { get; set; }
[ExcelColumn(Name = "Name",Index = 1)]
public string Name { get; set; }
}
```

### Add, Delete, Update

Expand Down Expand Up @@ -1683,6 +1718,21 @@ foreach (var sheet in sheets)

![image](https://user-images.githubusercontent.com/12729184/116199841-2a1f5300-a76a-11eb-90a3-6710561cf6db.png)

#### Q. How to query or export information about sheet visibility?

A. `GetSheetInformations` method.



```csharp
var sheets = MiniExcel.GetSheetInformations(path);
foreach (var sheetInfo in sheets)
{
Console.WriteLine($"sheet index : {sheetInfo.Index} "); // next sheet index - numbered from 0
Console.WriteLine($"sheet name : {sheetInfo.Name} "); // sheet name
Console.WriteLine($"sheet state : {sheetInfo.State} "); // sheet visibility state - visible / hidden
}
```


#### Q. Whether to use Count will load all data into the memory?
Expand Down
Binary file added samples/xlsx/TestDynamicSheet.xlsx
Binary file not shown.
Binary file added samples/xlsx/TestMultiSheetWithHiddenSheet.xlsx
Binary file not shown.
21 changes: 21 additions & 0 deletions src/MiniExcel/Attributes/ExcelSheetAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using MiniExcelLibs.OpenXml;
using System;

namespace MiniExcelLibs.Attributes
{
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class ExcelSheetAttribute : Attribute
{
public string Name { get; set; }
public SheetState State { get; set; } = SheetState.Visible;
}

public class DynamicExcelSheet : ExcelSheetAttribute
{
public string Key { get; set; }
public DynamicExcelSheet(string key)
{
Key = key;
}
}
}
20 changes: 17 additions & 3 deletions src/MiniExcel/MiniExcel.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
namespace MiniExcelLibs
{
using OpenXml;
using Utils;
using Zip;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Dynamic;
using System.IO;
using System.Linq;
using Utils;
using Zip;

public static partial class MiniExcel
{
Expand Down Expand Up @@ -147,7 +147,7 @@ public static void MergeSameCells(this Stream stream, byte[] filePath, ExcelType
{
ExcelTemplateFactory.GetProvider(stream, configuration, excelType).MergeSameCells(filePath);
}

#endregion

/// <summary>
Expand Down Expand Up @@ -217,6 +217,20 @@ public static List<string> GetSheetNames(this Stream stream, OpenXmlConfiguratio
return new ExcelOpenXmlSheetReader(stream, config).GetWorkbookRels(archive.entries).Select(s => s.Name).ToList();
}

public static List<SheetInfo> GetSheetInformations(string path, OpenXmlConfiguration config = null)
{
using (var stream = FileHelper.OpenSharedRead(path))
return GetSheetInformations(stream, config);
}

public static List<SheetInfo> GetSheetInformations(this Stream stream, OpenXmlConfiguration config = null)
{
config = config ?? OpenXmlConfiguration.DefaultConfig;

var archive = new ExcelOpenXmlZip(stream);
return new ExcelOpenXmlSheetReader(stream, config).GetWorkbookRels(archive.entries).Select((s, i) => s.ToSheetInfo((uint)i)).ToList();
}

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 = FileHelper.OpenSharedRead(path))
Expand Down
27 changes: 22 additions & 5 deletions src/MiniExcel/OpenXml/ExcelOpenXmlSheetReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;
Expand All @@ -25,7 +24,7 @@ internal class ExcelOpenXmlSheetReader : IExcelReader
private MergeCells _mergeCells;
private ExcelOpenXmlStyles _style;
private readonly ExcelOpenXmlZip _archive;
private OpenXmlConfiguration _config;
private readonly OpenXmlConfiguration _config;

private static readonly XmlReaderSettings _xmlSettings = new XmlReaderSettings
{
Expand Down Expand Up @@ -56,10 +55,19 @@ public IEnumerable<IDictionary<string, object>> Query(bool useHeaderRow, string
if (sheetName != null)
{
SetWorkbookRels(_archive.entries);
var s = _sheetRecords.SingleOrDefault(_ => _.Name == sheetName);
if (s == null)
var sheetRecord = _sheetRecords.SingleOrDefault(_ => _.Name == sheetName);
if (sheetRecord == null && _config.DynamicSheets != null)
{
var sheetConfig = _config.DynamicSheets.FirstOrDefault(ds => ds.Key == sheetName);
if (sheetConfig != null)
{
sheetRecord = _sheetRecords.SingleOrDefault(_ => _.Name == sheetConfig.Name);
}
}
if (sheetRecord == null)
throw new InvalidOperationException("Please check sheetName/Index is correct");
sheetEntry = sheets.Single(w => w.FullName == $"xl/{s.Path}" || w.FullName == $"/xl/{s.Path}" || w.FullName == s.Path || s.Path == $"/{w.FullName}");

sheetEntry = sheets.Single(w => w.FullName == $"xl/{sheetRecord.Path}" || w.FullName == $"/xl/{sheetRecord.Path}" || w.FullName == sheetRecord.Path || sheetRecord.Path == $"/{w.FullName}");
}
else if (sheets.Count() > 1)
{
Expand Down Expand Up @@ -402,6 +410,14 @@ private void SetCellsValueAndHeaders(object cellValue, bool useHeaderRow, ref Di

public IEnumerable<T> Query<T>(string sheetName, string startCell) where T : class, new()
{
if (sheetName == null)
{
var sheetInfo = CustomPropertyHelper.GetExcellSheetInfo(typeof(T), this._config);
if (sheetInfo != null)
{
sheetName = sheetInfo.ExcelSheetName;
}
}
return ExcelOpenXmlSheetReader.QueryImpl<T>(Query(false, sheetName, startCell), startCell, this._config);
}

Expand Down Expand Up @@ -562,6 +578,7 @@ internal IEnumerable<SheetRecord> ReadWorkbook(ReadOnlyCollection<ZipArchiveEntr
{
yield return new SheetRecord(
reader.GetAttribute("name"),
reader.GetAttribute("state"),
uint.Parse(reader.GetAttribute("sheetId")),
XmlReaderHelper.GetAttribute(reader, "id", _relationshiopNs)
);
Expand Down
54 changes: 45 additions & 9 deletions src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ internal class SheetDto
public string Name { get; set; }
public int SheetIdx { get; set; }
public string Path { get { return $"xl/worksheets/sheet{SheetIdx}.xml"; } }

public string State { get; set; }
}
internal class DrawingDto
{
Expand Down Expand Up @@ -62,7 +64,8 @@ public ExcelOpenXmlSheetWriter(Stream stream, object value, string sheetName, IC
this._archive = new MiniExcelZipArchive(_stream, ZipArchiveMode.Create, true, _utf8WithBom);
this._printHeader = printHeader;
this._value = value;
_sheets.Add(new SheetDto { Name = sheetName, SheetIdx = 1 }); //TODO:remove
var defaultSheetInfo = GetSheetInfos(sheetName);
_sheets.Add(defaultSheetInfo.ToDto(1)); //TODO:remove
}

public ExcelOpenXmlSheetWriter()
Expand All @@ -81,12 +84,13 @@ public void SaveAs()
foreach (var sheet in sheets)
{
sheetId++;
var s = new SheetDto { Name = sheet.Key, SheetIdx = sheetId };
_sheets.Add(s); //TODO:remove
var sheetInfos = GetSheetInfos(sheet.Key);
var sheetDto = sheetInfos.ToDto(sheetId);
_sheets.Add(sheetDto); //TODO:remove

currentSheetIndex = sheetId;

CreateSheetXml(sheet.Value, s.Path);
CreateSheetXml(sheet.Value, sheetDto.Path);
}
}
else if (_value is DataSet)
Expand All @@ -97,13 +101,13 @@ public void SaveAs()
foreach (DataTable dt in sheets.Tables)
{
sheetId++;
var s = new SheetDto { Name = dt.TableName, SheetIdx = sheetId };
_sheets.Add(s); //TODO:remove
var sheetPath = s.Path;
var sheetInfos = GetSheetInfos(dt.TableName);
var sheetDto = sheetInfos.ToDto(sheetId);
_sheets.Add(sheetDto); //TODO:remove

currentSheetIndex = sheetId;

CreateSheetXml(dt, sheetPath);
CreateSheetXml(dt, sheetDto.Path);
}
}
else
Expand Down Expand Up @@ -722,6 +726,31 @@ private ExcelColumnInfo GetColumnInfosFromDynamicConfiguration(string columnName
return prop;
}

private ExcellSheetInfo GetSheetInfos(string sheetName)
{
var info = new ExcellSheetInfo
{
ExcelSheetName = sheetName,
Key = sheetName,
ExcelSheetState = SheetState.Visible
};

if (_configuration.DynamicSheets == null || _configuration.DynamicSheets.Length <= 0)
return info;

var dynamicSheet = _configuration.DynamicSheets.SingleOrDefault(_ => _.Key == sheetName);
if (dynamicSheet == null)
{
return info;
}

if (dynamicSheet.Name != null)
info.ExcelSheetName = dynamicSheet.Name;
info.ExcelSheetState = dynamicSheet.State;

return info;
}

private static void WriteColumnsWidths(MiniExcelStreamWriter writer, IEnumerable<ExcelColumnInfo> props)
{
var ecwProps = props.Where(x => x?.ExcelColumnWidth != null).ToList();
Expand Down Expand Up @@ -839,7 +868,14 @@ private void GenerateEndXml()
foreach (var s in _sheets)
{
sheetId++;
workbookXml.AppendLine($@"<x:sheet name=""{s.Name}"" sheetId=""{sheetId}"" r:id=""{s.ID}"" />");
if (string.IsNullOrEmpty(s.State))
{
workbookXml.AppendLine($@"<x:sheet name=""{s.Name}"" sheetId=""{sheetId}"" r:id=""{s.ID}"" />");
}
else
{
workbookXml.AppendLine($@"<x:sheet name=""{s.Name}"" sheetId=""{sheetId}"" state=""{s.State}"" r:id=""{s.ID}"" />");
}
workbookRelsXml.AppendLine($@"<Relationship Type=""http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet"" Target=""/{s.Path}"" Id=""{s.ID}"" />");

//TODO: support multiple drawing
Expand Down
9 changes: 2 additions & 7 deletions src/MiniExcel/OpenXml/OpenXmlConfiguration.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,4 @@

using MiniExcelLibs.Utils;

using System.Collections.Generic;
using System;
using System.ComponentModel;
using MiniExcelLibs.Attributes;
using MiniExcelLibs.Attributes;

namespace MiniExcelLibs.OpenXml
{
Expand All @@ -19,5 +13,6 @@ public class OpenXmlConfiguration : Configuration
public bool EnableWriteNullValueCell { get; set; } = true;
public bool EnableSharedStringCache { get; set; } = true;
public long SharedStringCacheSize { get; set; } = 5 * 1024 * 1024;
public DynamicExcelSheet[] DynamicSheets { get; set; }
}
}
32 changes: 32 additions & 0 deletions src/MiniExcel/OpenXml/SheetInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
namespace MiniExcelLibs.OpenXml
{
public class SheetInfo
{
public SheetInfo(uint id, uint index, string name, SheetState sheetState)
{
Id = id;
Index = index;
Name = name;
State = sheetState;
}

/// <summary>
/// Internal sheet id - depends on the order in which the sheet is added
/// </summary>
public uint Id { get; }
/// <summary>
/// Next sheet index - numbered from 0
/// </summary>
public uint Index { get; }
/// <summary>
/// Sheet name
/// </summary>
public string Name { get; }
/// <summary>
/// Sheet visibility state
/// </summary>
public SheetState State { get; }
}

public enum SheetState { Visible, Hidden, VeryHidden }
}
22 changes: 20 additions & 2 deletions src/MiniExcel/OpenXml/SheetRecord.cs
Original file line number Diff line number Diff line change
@@ -1,20 +1,38 @@
namespace MiniExcelLibs.OpenXml
using System;

namespace MiniExcelLibs.OpenXml
{
internal sealed class SheetRecord
{
public SheetRecord(string name, uint id, string rid)
public SheetRecord(string name, string state, uint id, string rid)
{
Name = name;
State = state;
Id = id;
Rid = rid;
}

public string Name { get; }

public string State { get; set; }

public uint Id { get; }

public string Rid { get; set; }

public string Path { get; set; }

public SheetInfo ToSheetInfo(uint index)
{
if (string.IsNullOrEmpty(State))
{
return new SheetInfo(Id, index, Name, SheetState.Visible);
}
if (Enum.TryParse(State, true, out SheetState stateEnum))
{
return new SheetInfo(Id, index, Name, stateEnum);
}
throw new ArgumentException($"Unable to parse sheet state. Sheet name: {Name}");
}
}
}
Loading

0 comments on commit e6a4813

Please sign in to comment.