Skip to content

Commit 8861e15

Browse files
authored
Enable saving on .NET Core (#1307)
1 parent 4260711 commit 8861e15

File tree

6 files changed

+137
-19
lines changed

6 files changed

+137
-19
lines changed

CHANGELOG.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,19 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
66

77
## [3.0.0]
88

9+
## Added
10+
- Packages can now be saved on .NET Core and .NET 5+ if constructed with a path or stream (#1307).
11+
912
## Changed
1013
- When validation finds incorrect part, it will now include the relationship type rather than a class name
1114

1215
## Removed
1316
- .NET Standard 1.3 is no longer a supported platform. .NET Standard 2.0 is the lowest .NET Standard supported.
1417

1518
### Breaking change
19+
- OpenXmlPackage.CanSave is now an instance method (#1307)
1620
- IdPartPair is now a readonly struct rather than a class
1721
- IDisposableFeature is now a part of the framework package and is available by default on a package. Extension methods to manage this feature have been removed as it no longer needs to be opted into. It now registers all disposable actions to be done at the package level instead of adding support at the part level.
18-
- OpenXmlPackage.CanSave is now an instance method
1922
- Core infrastructure is now contained in a new package DocumentFormat.OpenXml.Framework. Typed classes are still in DocumentFormat.OpenXml. This means that you may reference DocumentFormat.OpenXml and still compile the same types, but if you want a smaller package, you may rely on just the framework package.
2023
- Removed `OpenXmlPackage.Package` property. A `OpenXmlPackage` is now backed by a `IPackage` instead of `System.IO.Packaging.Package`. This can be retrieved by `OpenXmlPackage.Features.Get<IPackageFeature>()`
2124
- Renamed PartExtensionProvider to IPartExtensionFeature and reduced its surface area to only two methods (instead of a full Dictionary<,>). The property to access this off of OpenXmlPackage has been removed, but may be accessed via `Features.Get<IPartExtensionFeature>()` if needed.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
namespace DocumentFormat.OpenXml.Packaging.Builder;
5+
6+
internal static class OpenXmlPackageInitializerExtensions
7+
{
8+
public static void InitializePackage(this OpenXmlPackage package) => package.Features
9+
.EnableSavePackage();
10+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
using DocumentFormat.OpenXml.Features;
5+
using System.IO;
6+
7+
namespace DocumentFormat.OpenXml.Packaging.Builder;
8+
9+
internal static class SavePackageExtensions
10+
{
11+
internal static IFeatureCollection EnableSavePackage(this IFeatureCollection features)
12+
{
13+
var feature = features.GetRequired<IPackageFeature>();
14+
15+
// No need to enable saving if package is only read-only
16+
if (feature.Package.FileOpenAccess == FileAccess.Read)
17+
{
18+
return features;
19+
}
20+
21+
var capabilities = feature.Capabilities;
22+
23+
if (!capabilities.HasFlagFast(PackageCapabilities.Save) && capabilities.HasFlagFast(PackageCapabilities.Reload))
24+
{
25+
features.Set<IPackageFeature>(new SaveablePackage(feature));
26+
}
27+
28+
return features;
29+
}
30+
31+
private sealed class SaveablePackage : DelegatingPackageFeature
32+
{
33+
public SaveablePackage(IPackageFeature package)
34+
: base(package)
35+
{
36+
}
37+
38+
public override PackageCapabilities Capabilities => base.Capabilities | PackageCapabilities.Save;
39+
40+
public override void Save() => Feature.Reload();
41+
}
42+
}

src/DocumentFormat.OpenXml.Framework/Packaging/OpenXmlPackage.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using DocumentFormat.OpenXml;
55
using DocumentFormat.OpenXml.Features;
6+
using DocumentFormat.OpenXml.Packaging.Builder;
67
using System;
78
using System.Collections.Generic;
89
using System.IO;
@@ -66,6 +67,8 @@ private protected OpenXmlPackage(IPackageFeature packageFeature, OpenSettings? s
6667
Features.GetRequired<IDisposableFeature>().Register(disposable);
6768
}
6869

70+
this.InitializePackage();
71+
6972
Load(packageFeature.Package);
7073
}
7174

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
using DocumentFormat.OpenXml.Features;
5+
using DocumentFormat.OpenXml.Packaging;
6+
using DocumentFormat.OpenXml.Packaging.Builder;
7+
using NSubstitute;
8+
using System.IO;
9+
using Xunit;
10+
11+
namespace DocumentFormat.OpenXml.Framework.Tests.Features;
12+
13+
public class SaveablePackageTests
14+
{
15+
[InlineData((int)PackageCapabilities.Save, FileAccess.Read, false)]
16+
[InlineData((int)PackageCapabilities.Save, FileAccess.ReadWrite, false)]
17+
[InlineData((int)PackageCapabilities.Reload, FileAccess.Read, false)]
18+
[InlineData((int)PackageCapabilities.Reload, FileAccess.ReadWrite, true)]
19+
[InlineData((int)PackageCapabilities.Save | (int)PackageCapabilities.Reload, FileAccess.Read, false)]
20+
[InlineData((int)PackageCapabilities.Save | (int)PackageCapabilities.Reload, FileAccess.ReadWrite, false)]
21+
[Theory]
22+
public void RequiredCapabilityCheck(int intCapabilities, FileAccess access, bool shouldUpdate)
23+
{
24+
// Arrange
25+
var capabilities = (PackageCapabilities)intCapabilities;
26+
var features = new FeatureCollection();
27+
28+
var package = Substitute.For<IPackage>();
29+
package.FileOpenAccess.Returns(access);
30+
31+
var feature = Substitute.For<IPackageFeature>();
32+
feature.Capabilities.Returns(capabilities);
33+
feature.Package.Returns(package);
34+
35+
features.Set(feature);
36+
37+
// Act
38+
var updatedFeatures = features.EnableSavePackage();
39+
40+
// Assert
41+
var updatedFeature = features.GetRequired<IPackageFeature>();
42+
Assert.Same(features, updatedFeatures);
43+
44+
if (shouldUpdate)
45+
{
46+
Assert.Equal(updatedFeature.Capabilities, capabilities | PackageCapabilities.Save);
47+
Assert.NotSame(feature, updatedFeature);
48+
}
49+
else
50+
{
51+
Assert.Same(feature, updatedFeature);
52+
}
53+
}
54+
55+
[Fact]
56+
public void SaveCallsReload()
57+
{
58+
// Arrange
59+
var features = new FeatureCollection();
60+
61+
var feature = Substitute.For<IPackageFeature>();
62+
feature.Capabilities.Returns(PackageCapabilities.Reload);
63+
features.Set(feature);
64+
65+
// Act
66+
features.EnableSavePackage();
67+
var updatedFeature = features.GetRequired<IPackageFeature>();
68+
updatedFeature.Package.Save();
69+
70+
// Assert
71+
feature.Received(1).Reload();
72+
Assert.Equal(PackageCapabilities.Reload | PackageCapabilities.Save, updatedFeature.Capabilities);
73+
}
74+
}

test/DocumentFormat.OpenXml.Tests/SaveAndCloneTests.cs

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -369,26 +369,12 @@ byte[] GetNewSpreadsheet()
369369

370370
var bytes = GetNewSpreadsheet();
371371

372-
bool canSave =
373-
#if !NET6_0_OR_GREATER
374-
true;
375-
#else
376-
false;
377-
#endif
378-
379-
if (canSave)
380-
{
381-
Assert.NotEmpty(bytes);
372+
Assert.NotEmpty(bytes);
382373

383-
using (var stream = new MemoryStream(bytes))
384-
using (var source = SpreadsheetDocument.Open(stream, false))
385-
{
386-
Assert.Single(source.WorkbookPart.Workbook.ChildElements);
387-
}
388-
}
389-
else
374+
using (var stream = new MemoryStream(bytes))
375+
using (var source = SpreadsheetDocument.Open(stream, false))
390376
{
391-
Assert.Empty(bytes);
377+
Assert.Single(source.WorkbookPart.Workbook.ChildElements);
392378
}
393379
}
394380

0 commit comments

Comments
 (0)