Skip to content

Commit 8f63aa4

Browse files
committed
added method SaveCopyAs
+ fixed issue with Epplus4 with saving for 2nd time (by using workaround with immediate reload from file)
1 parent fc1121c commit 8f63aa4

7 files changed

Lines changed: 155 additions & 21 deletions

File tree

ExcelOps-EpplusFreeFixCalcsEdition/EpplusFreeExcelDataOperations.SharedCode.vb

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,14 @@ Namespace ExcelOps
3030
End Get
3131
End Property
3232

33-
Protected Overrides Sub SaveInternal()
34-
If Me.PasswordForOpening <> Nothing Then
35-
Me.WorkbookPackage.Save(Me.PasswordForOpening)
36-
Else
37-
Me.WorkbookPackage.Save()
38-
End If
33+
Protected Overrides Sub SaveInternal(cachedCalculationsOption As SaveOptionsForDisabledCalculationEngines)
34+
Me.SaveAsInternal(Me.FilePath, cachedCalculationsOption)
35+
'FOLLOWING CODE MIGHT END UP IN EXCEPTION BECAUSE OF Epplus4 BUGS, THEREFORE SAVEAS IS USED INSTEAD OF SAVE
36+
'If Me.PasswordForOpening <> Nothing Then
37+
' Me.WorkbookPackage.Save(Me.PasswordForOpening)
38+
'Else
39+
' Me.WorkbookPackage.Save()
40+
'End If
3941
End Sub
4042

4143
Protected Overrides Sub SaveInternal_ApplyCachedCalculationOption(cachedCalculationsOption As SaveOptionsForDisabledCalculationEngines)
@@ -61,6 +63,7 @@ Namespace ExcelOps
6163
Else
6264
Me.WorkbookPackage.SaveAs(FullPath)
6365
End If
66+
Me.ReloadFromFile() 'WITHOUT THIS STEP, NEXT SAVE/SAVEAS MIGHT END UP IN EXCEPTION BECAUSE OF Epplus4 BUGS, THEREFORE SAVEAS IS USED INSTEAD OF SAVE
6467
Me._FilePath = FullPath.FullName
6568
End Sub
6669

ExcelOps-EpplusPolyform/EpplusPolyformExcelDataOperations.SharedCode.vb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ Namespace ExcelOps
3030
End Get
3131
End Property
3232

33-
Protected Overrides Sub SaveInternal()
33+
Protected Overrides Sub SaveInternal(cachedCalculationsOption As SaveOptionsForDisabledCalculationEngines)
3434
If Me.PasswordForOpening <> Nothing Then
3535
Me.WorkbookPackage.Save(Me.PasswordForOpening)
3636
Else

ExcelOps-FreeSpireXls/FreeSpireXlsDataOperations.SharedCode.vb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ Namespace ExcelOps
3939
End Get
4040
End Property
4141

42-
Protected Overrides Sub SaveInternal()
42+
Protected Overrides Sub SaveInternal(cachedCalculationsOption As SaveOptionsForDisabledCalculationEngines)
4343
Me._Workbook.SaveToFile(Me.FilePath) 'NOTE: _Workbook.Save is forbidden since the file path might have changed in background due to a workaround required for RemoveVbaProject
4444
End Sub
4545

ExcelOps-MicrosoftExcel/ExcelOpsLowLevel/MsExcelDataOperations.vb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,7 @@ Namespace Global.CompuMaster.Excel.ExcelOps
330330
End Get
331331
End Property
332332

333-
Protected Overrides Sub SaveInternal()
333+
Protected Overrides Sub SaveInternal(cachedCalculationsOption As SaveOptionsForDisabledCalculationEngines)
334334
If Me.PasswordForOpening <> Nothing Then
335335
Me.Workbook.Protect(Me.PasswordForOpening)
336336
End If

ExcelOps-SpireXls/SpireXlsDataOperations.SharedCode.vb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ Namespace ExcelOps
3939
End Get
4040
End Property
4141

42-
Protected Overrides Sub SaveInternal()
42+
Protected Overrides Sub SaveInternal(cachedCalculationsOption As SaveOptionsForDisabledCalculationEngines)
4343
Me._Workbook.SaveToFile(Me.FilePath) 'NOTE: _Workbook.Save is forbidden since the file path might have changed in background due to a workaround required for RemoveVbaProject
4444
End Sub
4545

ExcelOps/ExcelOpsLowLevel/ExcelDataOperationsBase.vb

Lines changed: 68 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -312,8 +312,8 @@ Namespace ExcelOps
312312
''' </summary>
313313
''' <returns></returns>
314314
''' <remarks>Please note: this property is a workbook property (not an engine property!)</remarks>
315-
<Obsolete("Use AutoCalculationEnabledWorkbookSetting instead", True)>
316-
<System.ComponentModel.EditorBrowsable(ComponentModel.EditorBrowsableState.Never)>
315+
<Obsolete("Use AutoCalculationEnabledWorkbookSetting instead", True)>
316+
<System.ComponentModel.EditorBrowsable(ComponentModel.EditorBrowsableState.Never)>
317317
Public Property AutoCalculationEnabled As Boolean
318318
Get
319319
Return AutoCalculationEnabledWorkbookSetting
@@ -441,12 +441,12 @@ Namespace ExcelOps
441441
Dim AutoCalcBuffer As Boolean = Me.AutoCalculationEnabledWorkbookSetting
442442
Try
443443
Me.AutoCalculationEnabledWorkbookSetting = True
444-
Me.SaveInternal()
444+
Me.SaveInternal(cachedCalculationsOption)
445445
Finally
446446
Me.AutoCalculationEnabledWorkbookSetting = AutoCalcBuffer
447447
End Try
448448
Else
449-
Me.SaveInternal()
449+
Me.SaveInternal(cachedCalculationsOption)
450450
End If
451451
End If
452452
End Sub
@@ -475,15 +475,15 @@ Namespace ExcelOps
475475
''' <summary>
476476
''' Save modifications made to the workbook
477477
''' </summary>
478-
Protected MustOverride Sub SaveInternal()
478+
Protected MustOverride Sub SaveInternal(cachedCalculationsOption As SaveOptionsForDisabledCalculationEngines)
479479

480480
''' <summary>
481481
''' Save workbook as another file
482482
''' </summary>
483483
''' <param name="filePath"></param>
484484
''' <remarks>Depending on <c ref="AutoCalculationResetToEnabledForAllSavedWorkbooks">AutoCalculationResetToEnabledForAllSavedWorkbooks</c>, <c ref="AutoCalculationEnabledWorkbookSetting">AutoCalculationEnabledWorkbookSetting</c> will be reset to True in saved workbook</remarks>
485-
<Obsolete("Use overloaded method", True)>
486-
<System.ComponentModel.EditorBrowsable(ComponentModel.EditorBrowsableState.Never)>
485+
<Obsolete("Use overloaded method", True)>
486+
<System.ComponentModel.EditorBrowsable(ComponentModel.EditorBrowsableState.Never)>
487487
Public Sub SaveAs(filePath As String)
488488
Me.SaveAs(filePath, SaveOptionsForDisabledCalculationEngines.DefaultBehaviour)
489489
End Sub
@@ -505,12 +505,55 @@ Namespace ExcelOps
505505
''' <param name="cachedCalculationsOption"></param>
506506
''' <remarks>Depending on <c ref="AutoCalculationResetToEnabledForAllSavedWorkbooks">AutoCalculationResetToEnabledForAllSavedWorkbooks</c>, <c ref="AutoCalculationEnabledWorkbookSetting">AutoCalculationEnabledWorkbookSetting</c> will be reset to True in saved workbook</remarks>
507507
Public Sub SaveAs(filePath As String, recalculateWorkbook As Boolean?, cachedCalculationsOption As SaveOptionsForDisabledCalculationEngines)
508+
Me.SaveAs_Internal(filePath, recalculateWorkbook, cachedCalculationsOption, SaveMethodMode.SaveAs)
509+
End Sub
510+
511+
''' <summary>
512+
''' Save workbook as another file, but keep current file path and read-only status
513+
''' </summary>
514+
''' <param name="filePath"></param>
515+
''' <remarks>Depending on <c ref="AutoCalculationResetToEnabledForAllSavedWorkbooks">AutoCalculationResetToEnabledForAllSavedWorkbooks</c>, <c ref="AutoCalculationEnabledWorkbookSetting">AutoCalculationEnabledWorkbookSetting</c> will be reset to True in saved workbook</remarks>
516+
Public Sub SaveCopyAs(filePath As String, cachedCalculationsOption As SaveOptionsForDisabledCalculationEngines)
517+
Me.SaveCopyAs(filePath, New Boolean?, cachedCalculationsOption)
518+
End Sub
519+
520+
''' <summary>
521+
''' Save workbook as another file, but keep current file path and read-only status
522+
''' </summary>
523+
''' <param name="filePath"></param>
524+
''' <param name="recalculateWorkbook">True to force recalculation, False to forbid recalculation, null to use default rule</param>
525+
''' <param name="cachedCalculationsOption"></param>
526+
''' <remarks>Depending on <c ref="AutoCalculationResetToEnabledForAllSavedWorkbooks">AutoCalculationResetToEnabledForAllSavedWorkbooks</c>, <c ref="AutoCalculationEnabledWorkbookSetting">AutoCalculationEnabledWorkbookSetting</c> will be reset to True in saved workbook</remarks>
527+
Public Sub SaveCopyAs(filePath As String, recalculateWorkbook As Boolean?, cachedCalculationsOption As SaveOptionsForDisabledCalculationEngines)
528+
Me.SaveAs_Internal(filePath, recalculateWorkbook, cachedCalculationsOption, SaveMethodMode.SaveCopyAs)
529+
End Sub
530+
531+
Private Enum SaveMethodMode As Byte
532+
Save = 0
533+
SaveAs = 1
534+
SaveCopyAs = 2
535+
End Enum
536+
537+
''' <summary>
538+
''' Save workbook as another file
539+
''' </summary>
540+
''' <param name="filePath"></param>
541+
''' <param name="recalculateWorkbook">True to force recalculation, False to forbid recalculation, null to use default rule</param>
542+
''' <param name="cachedCalculationsOption"></param>
543+
''' <remarks>Depending on <c ref="AutoCalculationResetToEnabledForAllSavedWorkbooks">AutoCalculationResetToEnabledForAllSavedWorkbooks</c>, <c ref="AutoCalculationEnabledWorkbookSetting">AutoCalculationEnabledWorkbookSetting</c> will be reset to True in saved workbook</remarks>
544+
Private Sub SaveAs_Internal(filePath As String, recalculateWorkbook As Boolean?, cachedCalculationsOption As SaveOptionsForDisabledCalculationEngines, saveMode As SaveMethodMode)
545+
If saveMode = SaveMethodMode.Save Then Throw New NotImplementedException("SaveMethodMode.Save not fully implemented / Save method still calls Save_Internal instead of SaveAs_Internal")
508546
If Me.ReadOnly = True AndAlso Me._FilePath = filePath AndAlso Me.WorkbookFilePath <> Nothing Then
509547
Throw New FileReadOnlyException("File """ & filePath & """ is read-only and can't be saved at same location")
510548
End If
511549
If filePath.ToLowerInvariant.EndsWith(".xlsx", False, System.Globalization.CultureInfo.InvariantCulture) AndAlso Me.HasVbaProject Then
512550
Throw New NotSupportedException("VBA projects are not supported for .xlsx files, run RemoveVbaProject() method, first")
513551
End If
552+
553+
Dim OriginalFilePath As String = Me._FilePath
554+
Dim OriginalReadOnlyStatus As Boolean = Me.ReadOnly
555+
Dim SaveFilePath As New System.IO.FileInfo(filePath)
556+
514557
If filePath.ToLowerInvariant.EndsWith(".xlsx", False, System.Globalization.CultureInfo.InvariantCulture) Then 'remove any last bit of a VBA project (HasVbaModule is not 100% sure)
515558
Me.RemoveVbaProject()
516559
End If
@@ -524,15 +567,29 @@ Namespace ExcelOps
524567
Dim AutoCalcBuffer As Boolean = Me.AutoCalculationEnabledWorkbookSetting
525568
Try
526569
Me.AutoCalculationEnabledWorkbookSetting = True
527-
Me.SaveAsInternal(filePath, cachedCalculationsOption)
570+
Me.SaveAsInternal(SaveFilePath.FullName, cachedCalculationsOption)
528571
Finally
529572
Me.AutoCalculationEnabledWorkbookSetting = AutoCalcBuffer
530573
End Try
531574
Else
532-
Me.SaveAsInternal(filePath, cachedCalculationsOption)
575+
Me.SaveAsInternal(SaveFilePath.FullName, cachedCalculationsOption)
533576
End If
534-
Me._FilePath = filePath
535-
Me.ReadOnly = False
577+
Select Case saveMode
578+
Case SaveMethodMode.Save
579+
'Keep original file path and read-only status - or force both to original values in case of temporary change in SaveAsInternal (e.g. for workarounds with RemoveVbaProject)
580+
Me._FilePath = OriginalFilePath
581+
Me.ReadOnly = OriginalReadOnlyStatus
582+
Case SaveMethodMode.SaveAs
583+
'Accept new file path and set read-only status to false
584+
Me._FilePath = SaveFilePath.FullName
585+
Me.ReadOnly = False
586+
Case SaveMethodMode.SaveCopyAs
587+
'Update file path and read-only status only if not in SaveAsCopy mode, otherwise keep original file path and read-only status (as in SaveAsCopy mode, the file is just saved to another location, but the current file path and read-only status of the engine instance should not be changed)
588+
Me._FilePath = OriginalFilePath
589+
Me.ReadOnly = OriginalReadOnlyStatus
590+
Case Else
591+
Throw New NotImplementedException("Invalid save mode: " & saveMode.ToString)
592+
End Select
536593
End Sub
537594

538595
Public Enum SaveOptionsForDisabledCalculationEngines As Byte

ExcelOpsTest/ExcelOpsTests.Engines/ExcelOpsTestBase.vb

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,80 @@ Namespace ExcelOpsTests.Engines
256256

257257
End Sub
258258

259+
<Test> Public Sub SaveCopyAsVsSaveAsVsSave(<Values("ExcelOpsGrund01.xlsx", "VbaProject.xlsm")> testFileName As String)
260+
Dim Wb As T
261+
'Testfile without password
262+
Dim TestFile As String = TestEnvironment.FullPathOfExistingTestFile("test_data", testFileName)
263+
Wb = Me.CreateInstance(TestFile, ExcelDataOperationsBase.OpenMode.OpenExistingFile, New ExcelDataOperationsOptions(ExcelDataOperationsOptions.WriteProtectionMode.ReadOnly))
264+
Dim HasVbaProjectBefore As Boolean = Wb.HasVbaProject
265+
Dim FilePathLocationBefore As String = Wb.FilePath
266+
267+
Assert.That(Wb.ReadOnly, [Is].True)
268+
Assert.That(Wb.HasVbaProject, [Is].EqualTo(HasVbaProjectBefore))
269+
270+
Assert.Throws(Of CompuMaster.Excel.ExcelOps.FileReadOnlyException)(Sub() Wb.Save(ExcelDataOperationsBase.SaveOptionsForDisabledCalculationEngines.NoReset))
271+
272+
Dim NewXlsxTargetPath As String
273+
NewXlsxTargetPath = TestEnvironment.FullPathOfDynTestFile(Wb, "SaveCopyAs.xlsx")
274+
If HasVbaProjectBefore Then
275+
Assert.Throws(Of NotSupportedException)(Sub() Wb.SaveCopyAs(NewXlsxTargetPath, ExcelDataOperationsBase.SaveOptionsForDisabledCalculationEngines.DefaultBehaviour))
276+
Else
277+
Wb.SaveCopyAs(NewXlsxTargetPath, ExcelDataOperationsBase.SaveOptionsForDisabledCalculationEngines.DefaultBehaviour)
278+
End If
279+
280+
Assert.That(Wb.ReadOnly, [Is].EqualTo(True))
281+
Assert.That(Wb.FilePath, [Is].EqualTo(FilePathLocationBefore))
282+
Assert.That(Wb.HasVbaProject, [Is].EqualTo(HasVbaProjectBefore))
283+
284+
Assert.Throws(Of CompuMaster.Excel.ExcelOps.FileReadOnlyException)(Sub() Wb.Save(ExcelDataOperationsBase.SaveOptionsForDisabledCalculationEngines.NoReset))
285+
286+
Assert.That(Wb.ReadOnly, [Is].EqualTo(True))
287+
Assert.That(Wb.FilePath, [Is].EqualTo(FilePathLocationBefore))
288+
Assert.That(Wb.HasVbaProject, [Is].EqualTo(HasVbaProjectBefore))
289+
290+
NewXlsxTargetPath = TestEnvironment.FullPathOfDynTestFile(Wb, "SaveAs.xlsx")
291+
If HasVbaProjectBefore Then
292+
Assert.Throws(Of NotSupportedException)(Sub() Wb.SaveAs(NewXlsxTargetPath, ExcelDataOperationsBase.SaveOptionsForDisabledCalculationEngines.DefaultBehaviour))
293+
Assert.That(Wb.ReadOnly, [Is].EqualTo(True))
294+
Assert.That(Wb.FilePath, [Is].EqualTo(FilePathLocationBefore))
295+
Assert.That(Wb.HasVbaProject, [Is].EqualTo(HasVbaProjectBefore))
296+
Else
297+
Wb.SaveAs(NewXlsxTargetPath, ExcelDataOperationsBase.SaveOptionsForDisabledCalculationEngines.DefaultBehaviour)
298+
Assert.That(Wb.ReadOnly, [Is].EqualTo(False))
299+
Assert.That(Wb.FilePath, [Is].Not.EqualTo(FilePathLocationBefore))
300+
Assert.That(Wb.HasVbaProject, [Is].EqualTo(HasVbaProjectBefore))
301+
End If
302+
303+
'Wb.ReloadFromFile() '==> if not reloading with Epplus4, following Save() call would fail with System.ArgumentOutOfRangeException : Index was out of range. Must be non-negative and less than the size of the collection. (Parameter 'index')
304+
305+
If HasVbaProjectBefore Then
306+
'Never saved as, so still read-only
307+
Assert.Throws(Of CompuMaster.Excel.ExcelOps.FileReadOnlyException)(Sub() Wb.Save(ExcelDataOperationsBase.SaveOptionsForDisabledCalculationEngines.NoReset))
308+
Assert.That(Wb.ReadOnly, [Is].EqualTo(True))
309+
Assert.That(Wb.FilePath, [Is].EqualTo(FilePathLocationBefore))
310+
Assert.That(Wb.HasVbaProject, [Is].EqualTo(HasVbaProjectBefore))
311+
Else
312+
Wb.Save(ExcelDataOperationsBase.SaveOptionsForDisabledCalculationEngines.NoReset)
313+
Assert.That(Wb.ReadOnly, [Is].EqualTo(False))
314+
Assert.That(Wb.FilePath, [Is].Not.EqualTo(FilePathLocationBefore))
315+
Assert.That(Wb.HasVbaProject, [Is].EqualTo(HasVbaProjectBefore))
316+
End If
317+
318+
'Save a 2nd time
319+
If HasVbaProjectBefore Then
320+
'Never saved as, so still read-only
321+
Assert.Throws(Of CompuMaster.Excel.ExcelOps.FileReadOnlyException)(Sub() Wb.Save(ExcelDataOperationsBase.SaveOptionsForDisabledCalculationEngines.DefaultBehaviour))
322+
Assert.That(Wb.ReadOnly, [Is].EqualTo(True))
323+
Assert.That(Wb.FilePath, [Is].EqualTo(FilePathLocationBefore))
324+
Assert.That(Wb.HasVbaProject, [Is].EqualTo(HasVbaProjectBefore))
325+
Else
326+
Wb.Save(ExcelDataOperationsBase.SaveOptionsForDisabledCalculationEngines.DefaultBehaviour)
327+
Assert.That(Wb.ReadOnly, [Is].EqualTo(False))
328+
Assert.That(Wb.FilePath, [Is].Not.EqualTo(FilePathLocationBefore))
329+
Assert.That(Wb.HasVbaProject, [Is].EqualTo(HasVbaProjectBefore))
330+
End If
331+
End Sub
332+
259333
<Test> Public Sub LoadFileFromByteArray()
260334
Dim Wb As T
261335
'Testfile without password

0 commit comments

Comments
 (0)