From ca784803c27a4787c36eb41d71868b1da388bb2b Mon Sep 17 00:00:00 2001 From: Amos Date: Sun, 23 Feb 2025 12:22:10 +0800 Subject: [PATCH] fix(Insert Sheet): Added insert sheet feature about `ContentTypesXml` processing (#728) * Added insert sheet feature about `ContentTypesXml` processing * adjust the order of attributes --- .../OpenXml/ExcelOpenXmlSheetWriter.Async.cs | 53 ++++++++++++----- .../OpenXml/ExcelOpenXmlSheetWriter.cs | 58 +++++++++++++------ 2 files changed, 80 insertions(+), 31 deletions(-) diff --git a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.Async.cs b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.Async.cs index 2d16d02..88ab241 100644 --- a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.Async.cs +++ b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.Async.cs @@ -11,6 +11,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; +using System.Xml.Linq; namespace MiniExcelLibs.OpenXml { @@ -65,19 +66,18 @@ public async Task InsertAsync(bool overwriteSheet = false, CancellationToken can { currentSheetIndex = existSheetDto.SheetIdx; _archive.Entries.Single(s => s.FullName == existSheetDto.Path).Delete(); - _archive.Entries.SingleOrDefault(s => s.FullName == ExcelFileNames.DrawingRels(currentSheetIndex))?.Delete(); - _archive.Entries.SingleOrDefault(s => s.FullName == ExcelFileNames.Drawing(currentSheetIndex))?.Delete(); await CreateSheetXmlAsync(_value, existSheetDto.Path, cancellationToken); } await AddFilesToZipAsync(cancellationToken); - await GenerateDrawinRelXmlAsync(currentSheetIndex, cancellationToken); + _archive.Entries.SingleOrDefault(s => s.FullName == ExcelFileNames.DrawingRels(currentSheetIndex - 1))?.Delete(); + await GenerateDrawinRelXmlAsync(currentSheetIndex - 1, cancellationToken); - await GenerateDrawingXmlAsync(currentSheetIndex, cancellationToken); + _archive.Entries.SingleOrDefault(s => s.FullName == ExcelFileNames.Drawing(currentSheetIndex - 1))?.Delete(); + await GenerateDrawingXmlAsync(currentSheetIndex - 1, cancellationToken); GenerateWorkBookXmls(out StringBuilder workbookXml, out StringBuilder workbookRelsXml, out Dictionary sheetsRelsXml); - foreach (var sheetRelsXml in sheetsRelsXml) { var sheetRelsXmlPath = ExcelFileNames.SheetRels(sheetRelsXml.Key); @@ -91,6 +91,8 @@ public async Task InsertAsync(bool overwriteSheet = false, CancellationToken can _archive.Entries.SingleOrDefault(s => s.FullName == ExcelFileNames.WorkbookRels)?.Delete(); await CreateZipEntryAsync(ExcelFileNames.WorkbookRels, null, ExcelXml.DefaultWorkbookXmlRels.Replace("{{sheets}}", workbookRelsXml.ToString()), cancellationToken); + await InsertContentTypesXmlAsync(cancellationToken); + _archive.Dispose(); } @@ -382,9 +384,6 @@ private async Task AddFilesToZipAsync(CancellationToken cancellationToken) } } - /// - /// styles.xml - /// private async Task GenerateStylesXmlAsync(CancellationToken cancellationToken) { using (var context = new SheetStyleBuildContext(_zipDictionary, _archive, _utf8WithBom, _configuration.DynamicColumns)) @@ -440,9 +439,6 @@ await CreateZipEntryAsync( cancellationToken); } - /// - /// workbook.xml 、 workbookRelsXml - /// private async Task GenerateWorkbookXmlAsync(CancellationToken cancellationToken) { GenerateWorkBookXmls( @@ -472,9 +468,6 @@ await CreateZipEntryAsync( cancellationToken); } - /// - /// [Content_Types].xml - /// private async Task GenerateContentTypesXmlAsync(CancellationToken cancellationToken) { var contentTypes = GetContentTypesXml(); @@ -482,6 +475,38 @@ private async Task GenerateContentTypesXmlAsync(CancellationToken cancellationTo await CreateZipEntryAsync(ExcelFileNames.ContentTypes, null, contentTypes, cancellationToken); } + private async Task InsertContentTypesXmlAsync(CancellationToken cancellationToken) + { + var contentTypesZipEntry = _archive.Entries.SingleOrDefault(s => s.FullName == ExcelFileNames.ContentTypes); + if (contentTypesZipEntry == null) + { + await GenerateContentTypesXmlAsync(cancellationToken); + return; + } + using (var stream = contentTypesZipEntry.Open()) + { + var doc = XDocument.Load(stream); + var ns = doc.Root.GetDefaultNamespace(); + var typesElement = doc.Descendants(ns + "Types").Single(); + var partNames = new HashSet(StringComparer.InvariantCultureIgnoreCase); + foreach (var partName in typesElement.Elements(ns + "Override").Select(s => s.Attribute("PartName").Value)) + { + partNames.Add(partName); + } + foreach (var p in _zipDictionary) + { + var partName = $"/{p.Key}"; + if (!partNames.Contains(partName)) + { + var newElement = new XElement(ns + "Override", new XAttribute("ContentType", p.Value.ContentType), new XAttribute("PartName", partName)); + typesElement.Add(newElement); + } + } + stream.Position = 0; + doc.Save(stream); + } + } + private async Task CreateZipEntryAsync(string path, string contentType, string content, CancellationToken cancellationToken) { ZipArchiveEntry entry = _archive.CreateEntry(path, CompressionLevel.Fastest); diff --git a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.cs b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.cs index d290fe3..39526cf 100644 --- a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.cs +++ b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.cs @@ -6,12 +6,12 @@ using MiniExcelLibs.WriteAdapter; using MiniExcelLibs.Zip; using System; -using System.Collections; using System.Collections.Generic; using System.IO; using System.IO.Compression; using System.Linq; using System.Text; +using System.Xml.Linq; namespace MiniExcelLibs.OpenXml { @@ -101,19 +101,18 @@ public void Insert(bool overwriteSheet = false) { currentSheetIndex = existSheetDto.SheetIdx; _archive.Entries.Single(s => s.FullName == existSheetDto.Path).Delete(); - _archive.Entries.SingleOrDefault(s => s.FullName == ExcelFileNames.DrawingRels(currentSheetIndex))?.Delete(); - _archive.Entries.SingleOrDefault(s => s.FullName == ExcelFileNames.Drawing(currentSheetIndex))?.Delete(); CreateSheetXml(_value, existSheetDto.Path); } AddFilesToZip(); - GenerateDrawinRelXml(currentSheetIndex); + _archive.Entries.SingleOrDefault(s => s.FullName == ExcelFileNames.DrawingRels(currentSheetIndex - 1))?.Delete(); + GenerateDrawinRelXml(currentSheetIndex - 1); - GenerateDrawingXml(currentSheetIndex); + _archive.Entries.SingleOrDefault(s => s.FullName == ExcelFileNames.Drawing(currentSheetIndex - 1))?.Delete(); + GenerateDrawingXml(currentSheetIndex - 1); GenerateWorkBookXmls(out StringBuilder workbookXml, out StringBuilder workbookRelsXml, out Dictionary sheetsRelsXml); - foreach (var sheetRelsXml in sheetsRelsXml) { var sheetRelsXmlPath = ExcelFileNames.SheetRels(sheetRelsXml.Key); @@ -121,12 +120,14 @@ public void Insert(bool overwriteSheet = false) CreateZipEntry(sheetRelsXmlPath, null, ExcelXml.DefaultSheetRelXml.Replace("{{format}}", sheetRelsXml.Value)); } - _archive.Entries.SingleOrDefault(s => s.FullName == ExcelFileNames.Workbook)?.Delete(); + _archive.Entries.SingleOrDefault(s => s.FullName == ExcelFileNames.Workbook).Delete(); CreateZipEntry(ExcelFileNames.Workbook, ExcelContentTypes.Workbook, ExcelXml.DefaultWorkbookXml.Replace("{{sheets}}", workbookXml.ToString())); - _archive.Entries.SingleOrDefault(s => s.FullName == ExcelFileNames.WorkbookRels)?.Delete(); + _archive.Entries.SingleOrDefault(s => s.FullName == ExcelFileNames.WorkbookRels).Delete(); CreateZipEntry(ExcelFileNames.WorkbookRels, null, ExcelXml.DefaultWorkbookXmlRels.Replace("{{sheets}}", workbookRelsXml.ToString())); + InsertContentTypesXml(); + _archive.Dispose(); } @@ -384,9 +385,6 @@ private void AddFilesToZip() } } - /// - /// styles.xml - /// private void GenerateStylesXml() { using (var context = new SheetStyleBuildContext(_zipDictionary, _archive, _utf8WithBom, _configuration.DynamicColumns)) @@ -440,9 +438,6 @@ private void GenerateDrawingXml(int sheetIndex) ExcelXml.DefaultDrawing.Replace("{{format}}", drawing)); } - /// - /// workbook.xml、workbookRelsXml - /// private void GenerateWorkbookXml() { GenerateWorkBookXmls( @@ -469,9 +464,6 @@ private void GenerateWorkbookXml() ExcelXml.DefaultWorkbookXmlRels.Replace("{{sheets}}", workbookRelsXml.ToString())); } - /// - /// [Content_Types].xml - /// private void GenerateContentTypesXml() { var contentTypes = GetContentTypesXml(); @@ -479,6 +471,38 @@ private void GenerateContentTypesXml() CreateZipEntry(ExcelFileNames.ContentTypes, null, contentTypes); } + private void InsertContentTypesXml() + { + var contentTypesZipEntry = _archive.Entries.SingleOrDefault(s => s.FullName == ExcelFileNames.ContentTypes); + if (contentTypesZipEntry == null) + { + GenerateContentTypesXml(); + return; + } + using (var stream = contentTypesZipEntry.Open()) + { + var doc = XDocument.Load(stream); + var ns = doc.Root.GetDefaultNamespace(); + var typesElement = doc.Descendants(ns + "Types").Single(); + var partNames = new HashSet(StringComparer.InvariantCultureIgnoreCase); + foreach (var partName in typesElement.Elements(ns + "Override").Select(s => s.Attribute("PartName").Value)) + { + partNames.Add(partName); + } + foreach (var p in _zipDictionary) + { + var partName = $"/{p.Key}"; + if (!partNames.Contains(partName)) + { + var newElement = new XElement(ns + "Override", new XAttribute("ContentType", p.Value.ContentType), new XAttribute("PartName", partName)); + typesElement.Add(newElement); + } + } + stream.Position = 0; + doc.Save(stream); + } + } + private void CreateZipEntry(string path, string contentType, string content) { ZipArchiveEntry entry = _archive.CreateEntry(path, CompressionLevel.Fastest);