Skip to content
Draft
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
2 changes: 2 additions & 0 deletions src/MiniExcel.OpenXml/Templates/OpenXmlTemplate.Impl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -701,6 +701,8 @@ private async Task<GenerateCellValuesContext> GenerateCellValuesAsync(
: tempReplacement;

replacements[key] = replacementValue;
AddFlattenedAndFormattedValues(replacements, key, cellValue, propInfo);

rowXml.Replace($"@header{{{{{key}}}}}", replacementValue);

if (isHeaderRow && row.Value.Contains(key))
Expand Down
41 changes: 31 additions & 10 deletions src/MiniExcel.OpenXml/Templates/OpenXmlTemplate.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,14 @@ public async Task SaveAsByTemplateAsync(byte[] templateBytes, object value, Canc
[CreateSyncVersion]
public async Task SaveAsByTemplateAsync(Stream templateStream, object value, CancellationToken cancellationToken = default)
{
if(!templateStream.CanSeek)
if (!templateStream.CanSeek)
throw new ArgumentException("The template stream must be seekable");

templateStream.Seek(0, SeekOrigin.Begin);
using var templateReader = await OpenXmlReader.CreateAsync(templateStream, null, cancellationToken: cancellationToken).ConfigureAwait(false);
var outputFileArchive = await OpenXmlZip.CreateAsync(_outputFileStream, mode: ZipArchiveMode.Create, true, Encoding.UTF8, isUpdateMode: false, cancellationToken: cancellationToken).ConfigureAwait(false);
await using var disposableOutputFileArchive = outputFileArchive.ConfigureAwait(false);

try
{
outputFileArchive.EntryCollection = templateReader.Archive.ZipFile.Entries; //TODO:need to remove
Expand All @@ -66,7 +66,7 @@ public async Task SaveAsByTemplateAsync(Stream templateStream, object value, Can
{
outputFileArchive.Entries.Add(entry.FullName.Replace('\\', '/'), entry);
}

// Create a new zip file for writing
templateStream.Position = 0;
#if NET10_0_OR_GREATER
Expand All @@ -75,14 +75,16 @@ public async Task SaveAsByTemplateAsync(Stream templateStream, object value, Can
#else
using var originalArchive = new ZipArchive(templateStream, ZipArchiveMode.Read);
#endif
// sheet name map
var sheetPathRealNameMap = GetRealSheetNameMap(originalArchive);

// Iterate through each entry in the original archive
foreach (var entry in originalArchive.Entries)
{
var entryName = entry.FullName.TrimStart('/');
if (entryName.StartsWith("xl/worksheets/sheet", StringComparison.OrdinalIgnoreCase) || entryName.Equals("xl/calcChain.xml"))
if (entryName.StartsWith("xl/worksheets/sheet", StringComparison.OrdinalIgnoreCase) || entryName.Equals("xl/calcChain.xml") || entryName.Equals("xl/workbook.xml") || entryName.Equals("xl/_rels/workbook.xml.rels"))
continue;

// Create a new entry in the new archive with the same name
var newEntry = outputFileArchive.ZipFile.CreateEntry(entry.FullName);

Expand All @@ -109,23 +111,33 @@ await originalEntryStream.CopyToAsync(newEntryStream
var templateSharedStrings = templateReader.SharedStrings;
templateStream.Position = 0;


//read all xlsx sheets
var templateSheets = templateReader.Archive.ZipFile.Entries
.Where(entry => entry.FullName
.TrimStart('/')
.StartsWith("xl/worksheets/sheet", StringComparison.OrdinalIgnoreCase));

int sheetIdx = 0;
// 全局收集所有工作表信息(单表/多表都在这里汇总)
var allSheetInfos = new List<(int Index, string Name)>();

foreach (var templateSheet in templateSheets)
{
// XRowInfos musy be cleared for every sheet or it'll cause duplicates: https://user-images.githubusercontent.com/12729184/115003101-0fcab700-9ed8-11eb-9151-ca4d7b86d59e.png
_xRowInfos.Clear();
_xMergeCellInfos.Clear();
_newXMergeCellInfos.Clear();
_calcChainCellRefs.Clear();

var templateFullName = templateSheet.FullName;
var inputValues = _inputValueExtractor.ToValueDictionary(value);
sheetPathRealNameMap.TryGetValue(templateFullName, out var realSheetName);


if (await HookSheetProcess(outputFileArchive, realSheetName, templateSharedStrings, sheetIdx, allSheetInfos, templateSheet, templateFullName, inputValues, cancellationToken).ConfigureAwait(false))
break;
Comment on lines +138 to +139
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Breaking the loop when HookSheetProcess returns true prevents any subsequent template sheets from being processed. If a template contains multiple sheets and one of them uses the expansion logic, all sheets following it will be missing from the output file. Furthermore, sheetIdx is passed by value, so the caller's index is not updated after expansion, which would cause file name conflicts if the loop were to continue.


var outputZipEntry = outputFileArchive.ZipFile.CreateEntry(templateFullName);

#if NET8_0_OR_GREATER
Expand All @@ -135,14 +147,22 @@ await originalEntryStream.CopyToAsync(newEntryStream
using var outputZipSheetEntryStream = outputZipEntry.Open();
#endif
await GenerateSheetByCreateModeAsync(templateSheet, outputZipSheetEntryStream, inputValues, templateSharedStrings, cancellationToken: cancellationToken).ConfigureAwait(false);

//doc.Save(zipStream); //don't do it because: https://user-images.githubusercontent.com/12729184/114361127-61a5d100-9ba8-11eb-9bb9-34f076ee28a2.png
// disposing writer disposes streams as well. read and parse calc functions before that

sheetIdx++;
allSheetInfos.Add((sheetIdx, realSheetName));
_calcChainContent.Append(CalcChainHelper.GetCalcChainContent(_calcChainCellRefs, sheetIdx));

}



// 【一次性写入所有配置】(修复覆盖BUG,表名生效)
await BatchAddSheetsToExcelConfigAsync(outputFileArchive.ZipFile, originalArchive, allSheetInfos, cancellationToken).ConfigureAwait(false);


// create mode we need to not create first then create here
var calcChain = outputFileArchive.EntryCollection.FirstOrDefault(e => e.FullName.Contains("xl/calcChain.xml"));
if (calcChain is not null)
Expand Down Expand Up @@ -194,4 +214,5 @@ await originalEntryStream.CopyToAsync(newEntryStream
outputFileArchive.ZipFile.Dispose();
#endif
}
}

}
Loading