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
27 changes: 27 additions & 0 deletions src/EPPlus/Configuration/EPPlusSaveOption.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace OfficeOpenXml.Configuration
{
/// <summary>
/// Options used when saving an <see cref="ExcelPackage"/>.
/// Supplied via an action to the Save and SaveAs methods.
/// </summary>
public class EPPlusSaveOption
{
/// <summary>
/// The password to encrypt the workbook with.
/// This overrides the Encryption.Password on the package.
/// If null, the password set on the package is used.
/// </summary>
public string Password { get; set; } = null;

/// <summary>
/// If true, the package is saved as an Excel template (.xltx, or .xltm if the workbook
/// contains a VBA project) instead of a standard workbook. Default is false.
/// </summary>
public bool SaveAsTemplate { get; set; } = false;
}
}
6 changes: 5 additions & 1 deletion src/EPPlus/Constants/ContentTypes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,11 @@ internal class ContentTypes
internal const string contentTypeWorkbookMacroEnabled = "application/vnd.ms-excel.sheet.macroEnabled.main+xml";
internal const string contentTypeSharedString = @"application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml";
internal const string contentTypeMetaData = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheetMetadata+xml";


//Template
internal const string contentTypeTemplateDefault = @"application/vnd.openxmlformats-officedocument.spreadsheetml.template.main+xml";
internal const string contentTypeTemplateMacroEnabled = "application/vnd.ms-excel.template.macroEnabled.main+xml";

//Theme
internal const string contentTypeThemeOverride = "application/vnd.openxmlformats-officedocument.themeOverride+xml";
internal const string contentTypeTheme = @"application/vnd.openxmlformats-officedocument.theme+xml";
Expand Down
106 changes: 73 additions & 33 deletions src/EPPlus/ExcelPackage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -881,39 +881,6 @@ internal void SavePart(Uri uri, XmlDocument xmlDoc)
var xmlWriter = XmlWriter.Create(stream, xmlSettings);
xmlDoc.Save(xmlWriter);
}
/// <summary>
/// Saves the XmlDocument into the package at the specified Uri.
/// </summary>
/// <param name="uri">The Uri of the component</param>
/// <param name="xmlDoc">The XmlDocument to save</param>
internal void SaveWorkbook(Uri uri, XmlDocument xmlDoc)
{
Packaging.ZipPackagePart part = _zipPackage.GetPart(uri);
if (Workbook.VbaProject == null)
{
if (part.ContentType != ContentTypes.contentTypeWorkbookDefault)
{
part = _zipPackage.CreatePart(uri, ContentTypes.contentTypeWorkbookDefault, Compression);
}
}
else
{
if (part.ContentType != ContentTypes.contentTypeWorkbookMacroEnabled)
{
var rels = part.GetRelationships();
_zipPackage.DeletePart(uri);
part = ZipPackage.CreatePart(uri, ContentTypes.contentTypeWorkbookMacroEnabled);
foreach (ZipPackageRelationship rel in rels)
{
ZipPackage.DeleteRelationship(rel.Id);
part.CreateRelationship(rel.TargetUri, rel.TargetMode, rel.RelationshipType);
}
}
}
var stream = part.GetStream(FileMode.Create, FileAccess.Write);
xmlDoc.Save(stream);
}

#endregion

#region Dispose
Expand Down Expand Up @@ -1096,6 +1063,21 @@ public void Save(string password)
Save();
}
/// <summary>
/// Saves all the components back into the package.
/// This method recursively calls the Save method on all sub-components.
/// The package is closed after it has been saved
/// </summary>
/// <param name="options">An action used to configure the save options, for example saving the package as an Excel template.</param>
public void Save(Action<EPPlusSaveOption> options)
{
var o = new EPPlusSaveOption();
options.Invoke(o);

Encryption.Password = o.Password;
SaveAsTemplate = o.SaveAsTemplate;
Save();
}
/// <summary>
/// Saves the workbook to a new file
/// The package is closed after it has been saved
/// </summary>
Expand Down Expand Up @@ -1165,7 +1147,52 @@ public void SaveAs(Stream OutputStream, string password)
Encryption.Password = password;
SaveAs(OutputStream);
}
/// <summary>
/// Saves the workbook to a new file
/// The package is closed after it has been saved
/// </summary>
/// <param name="filePath">The file location</param>
/// <param name="options">An action used to configure the save options, for example saving the package as an Excel template.</param>
public void SaveAs(string filePath, Action<EPPlusSaveOption> options)
{
var o = new EPPlusSaveOption();
options.Invoke(o);

Encryption.Password = o.Password;
SaveAsTemplate = o.SaveAsTemplate;

SaveAs(new FileInfo(filePath));
}
/// <summary>
/// Saves the workbook to a new file
/// The package is closed after it has been saved
/// </summary>
/// <param name="file">The file</param>
/// <param name="options">An action used to configure the save options, for example saving the package as an Excel template.</param>
public void SaveAs(FileInfo file, Action<EPPlusSaveOption> options)
{
var o = new EPPlusSaveOption();
options.Invoke(o);

File = file;
Encryption.Password = o.Password;
SaveAsTemplate = o.SaveAsTemplate;
Save();
}
/// <summary>
/// Copies the Package to the Outstream
/// The package is closed after it has been saved
/// </summary>
/// <param name="OutputStream">The stream to copy the package to</param>
/// <param name="options">An action used to configure the save options, for example saving the package as an Excel template.</param>
public void SaveAs(Stream OutputStream, Action<EPPlusSaveOption> options)
{
var o = new EPPlusSaveOption();
options.Invoke(o);
Encryption.Password = o.Password;
SaveAsTemplate = o.SaveAsTemplate;
SaveAs(OutputStream);
}
/// <summary>
/// The output file. Null if no file is used
/// </summary>
Expand Down Expand Up @@ -1210,6 +1237,19 @@ public CompressionLevel Compression
ZipPackage.Compression = value;
}
}

/// <summary>
/// If true, the package is saved as an Excel template (.xltx, or .xltm if the workbook
/// contains a VBA project). If false, it is saved as a standard Excel workbook
/// (.xlsx/.xlsm). Default is false.
/// </summary>
/// <remarks>
/// This value is not derived from a loaded package, so a template that is loaded and saved
/// again becomes a standard workbook unless this is set to true. This mirrors how Excel
/// handles templates.
/// </remarks>
public bool SaveAsTemplate { get; set; } = false;

CompatibilitySettings _compatibility = null;
/// <summary>
/// Compatibility settings for older versions of EPPlus.
Expand Down
77 changes: 76 additions & 1 deletion src/EPPlus/ExcelPackageAsync.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@ Date Author Change
using System;
using System.IO;
using OfficeOpenXml.Utils.FileUtils;

using OfficeOpenXml.FormulaParsing.Excel.Functions.Finance.FinancialDayCount;
using OfficeOpenXml.Configuration;
using System.Runtime.CompilerServices;
using System.Security.Cryptography;
#if !NET35 && !NET40
using System.Threading;
using System.Threading.Tasks;
Expand Down Expand Up @@ -335,6 +338,24 @@ public async Task SaveAsync(string password, CancellationToken cancellationToken
await SaveAsync(cancellationToken).ConfigureAwait(false);
}

/// <summary>
/// Saves all the components back into the package asynchronously.
/// This method recursively calls the Save method on all sub-components.
/// The package is closed after it has been saved.
/// Supply a password to encrypt the workbook with.
/// </summary>
/// <param name="options">An action used to configure the save options, for example saving the package as an Excel template.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A task that represents the asynchronous save operation.</returns>
public async Task SaveAsync(Action<EPPlusSaveOption> options, CancellationToken cancellationToken = default)
{
var o = new EPPlusSaveOption();
options.Invoke(o);
Encryption.Password = o.Password;
SaveAsTemplate = o.SaveAsTemplate;
await SaveAsync(cancellationToken).ConfigureAwait(false);
}

#endregion

#region SaveAsAsync
Expand All @@ -361,6 +382,22 @@ public async Task SaveAsAsync(string filePath, CancellationToken cancellationTok
await SaveAsAsync(new FileInfo(filePath), cancellationToken);
}

/// <summary>
/// Saves the workbook to a new file
/// The package is closed after it has been saved
/// </summary>
/// <param name="filePath">The file location</param>
/// <param name="options">An action used to configure the save options, for example saving the package as an Excel template.</param>
/// <param name="cancellationToken">The cancellation token</param>
public async Task SaveAsAsync(string filePath, Action<EPPlusSaveOption> options, CancellationToken cancellationToken = default)
{
var o = new EPPlusSaveOption();
options.Invoke(o);
Encryption.Password = o.Password;
SaveAsTemplate = o.SaveAsTemplate;
await SaveAsAsync(new FileInfo(filePath), cancellationToken);
}

/// <summary>
/// Saves the workbook to a new file
/// The package is closed after it has been saved
Expand All @@ -375,6 +412,23 @@ public async Task SaveAsAsync(FileInfo file, string password, CancellationToken
Encryption.Password = password;
await SaveAsync(cancellationToken).ConfigureAwait(false);
}

/// <summary>
/// Saves the workbook to a new file
/// The package is closed after it has been saved
/// </summary>
/// <param name="file">The file</param>
/// <param name="options">An action used to configure the save options, for example saving the package as an Excel template.</param>
/// <param name="cancellationToken">The cancellation token</param>
public async Task SaveAsAsync(FileInfo file, Action<EPPlusSaveOption> options, CancellationToken cancellationToken = default)
{
var o = new EPPlusSaveOption();
options.Invoke(o);
File = file;
Encryption.Password = o.Password;
SaveAsTemplate = o.SaveAsTemplate;
await SaveAsync(cancellationToken).ConfigureAwait(false);
}
/// <summary>
/// Saves the workbook to a new file
/// The package is closed after it has been saved
Expand Down Expand Up @@ -404,6 +458,27 @@ public async Task SaveAsAsync(Stream OutputStream, CancellationToken cancellatio
await StreamUtil.CopyStreamAsync(_stream, OutputStream, cancellationToken).ConfigureAwait(false);
}
}
/// <summary>
/// Copies the Package to the Outstream
/// The package is closed after it has been saved
/// </summary>
/// <param name="OutputStream">The stream to copy the package to</param>
/// <param name="options">An action used to configure the save options, for example saving the package as an Excel template.</param>
/// <param name="cancellationToken">The cancellation token</param>
public async Task SaveAsAsync(Stream OutputStream, Action<EPPlusSaveOption> options, CancellationToken cancellationToken = default)
{
var o = new EPPlusSaveOption();
options.Invoke(o);
SaveAsTemplate = o.SaveAsTemplate;
Encryption.Password = o.Password;
File = null;
await SaveAsync(cancellationToken).ConfigureAwait(false);

if (OutputStream != _stream)
{
await StreamUtil.CopyStreamAsync(_stream, OutputStream, cancellationToken).ConfigureAwait(false);
}
}

/// <summary>
/// Copies the Package to the Outstream
Expand Down
38 changes: 24 additions & 14 deletions src/EPPlus/ExcelWorkbook.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1559,21 +1559,15 @@ internal void Save() // Workbook Save
SetXmlNodeBool("d:calcPr/@fullPrecision", FullPrecision, false);

if(_workbookCreatedInEPPlus == false) EnsureCalculationFeatures(); //Ensure that the calculation features are included in the file to make sure that new functions are supported.

if (_vba == null && !_package.ZipPackage.PartExists(new Uri(ExcelVbaProject.PartUri, UriKind.Relative)))

bool isMacroEnabled = !(_vba == null &&
!_package.ZipPackage.PartExists(new Uri(ExcelVbaProject.PartUri, UriKind.Relative)));

var targetContentType = GetWorkbookContentType(_package.SaveAsTemplate, isMacroEnabled);

if (Part.ContentType != targetContentType)
{
if (Part.ContentType != ContentTypes.contentTypeWorkbookDefault &&
Part.ContentType != ContentTypes.contentTypeWorkbookMacroEnabled)
{
Part.ContentType = ContentTypes.contentTypeWorkbookDefault;
}
}
else
{
if (Part.ContentType != ContentTypes.contentTypeWorkbookMacroEnabled)
{
Part.ContentType = ContentTypes.contentTypeWorkbookMacroEnabled;
}
Part.ContentType = targetContentType;
}

UpdateDefinedNamesXml();
Expand Down Expand Up @@ -1705,6 +1699,22 @@ internal void Save() // Workbook Save
}
}

internal string GetWorkbookContentType(bool saveAsTemplate, bool isMacroEnabled)
{
if (saveAsTemplate)
{
return isMacroEnabled
? ContentTypes.contentTypeTemplateMacroEnabled
: ContentTypes.contentTypeTemplateDefault;
}
else
{
return isMacroEnabled
? ContentTypes.contentTypeWorkbookMacroEnabled
: ContentTypes.contentTypeWorkbookDefault;
}
}

private void HandleLicense()
{
switch (ExcelPackage.License.LicenseType)
Expand Down
4 changes: 3 additions & 1 deletion src/EPPlus/Packaging/ZipPackage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -534,7 +534,9 @@ internal void Save(Stream stream)
part.ContentType == ContentTypes.contentTypeRichDataValueStructure ||
part.ContentType == ContentTypes.contentTypeRichDataValue ||
part.ContentType == ContentTypes.contentTypeWorkbookDefault ||
part.ContentType == ContentTypes.contentTypeWorkbookMacroEnabled)
part.ContentType == ContentTypes.contentTypeWorkbookMacroEnabled ||
part.ContentType == ContentTypes.contentTypeTemplateDefault ||
part.ContentType == ContentTypes.contentTypeTemplateMacroEnabled)
{
saveAfterParts.Add(part);
}
Expand Down
Loading
Loading