Skip to content

Do not restore relationships to deleted parts when saving a Strict package - #2156

Open
jafin wants to merge 1 commit into
dotnet:mainfrom
jafin:fix/strict-deleted-part-relationship
Open

jafin wants to merge 1 commit into
dotnet:mainfrom
jafin:fix/strict-deleted-part-relationship

Conversation

@jafin

@jafin jafin commented Oct 15, 2026

Copy link
Copy Markdown

Summary

Saving an ISO Strict package put back the relationship to a part that was deleted after the package was opened. The saved package then could not be read again (Part: /xl/calcChain.xml doesn't exist in the package.). This change rewrites only the relationships that are still there.

Fixes #2155

Problem

StrictNamespaceExtensions.UseTransitionalRelationshipNamespaces records each Strict relationship that it translates to its transitional type on load. On save, it removes and re-creates every recorded relationship. It did not check that the relationship was still in its collection. So:

  • A deleted part: DeletePart removes the relationship, but the record stays, and the save creates the relationship again. This applies to part relationships (a calc chain or a worksheet) and to package relationships (for example the extended properties).
  • A reused id: delete a part, then add a new part with the same relationship id. The save replaced the new relationship with the old one.
  • Save order: the save callback runs for the package first, before most relationships are loaded. A relationship loaded later in the save was recorded too late, and kept its Strict type. Opening a Strict package and closing it without reading anything left _rels/.rels with Strict types.

Change

  • A recorded relationship is rewritten only if its collection still holds that same PackageRelationshipBuilder. A Contains(id) check alone is not enough, because of the reused-id case.
  • A relationship hidden by an ignore filter (IsRemoved, for example after IgnoreCalculationChainPartRelationship()) is never in the collection, and cannot be removed through it. It is rewritten as before.
  • The save callback loads every part before it rewrites anything, whichever container it is called for first. The one-time load that the Found getter already did is now StrictNamespaceFeature.EnsureAllPartsLoaded(), and both use it. TrySavePartContent already reads StrictRelationshipFound for every part, so this adds no new work in practice.

Tests

New test/DocumentFormat.OpenXml.Tests/IsoStrictTest/StrictRelationshipSaveTests.cs, with 6 tests. All 6 fail on main, and all pass with this change:

  • a deleted worksheet in the existing asset O14ISOStrict/Excel/2D Rotation-O12-XL-OartEffects.xlsx;
  • a deleted calc chain;
  • a deleted package-level part (extended properties);
  • a reused relationship id;
  • an ignored relationship is still written with its transitional type;
  • an open and close with nothing read writes every relationship with its transitional type.

The tests check the saved package with System.IO.Packaging: no internal relationship points to a missing part, and no relationship keeps a Strict type.

Existing suites on net10.0 pass: DocumentFormat.OpenXml.Tests (2,319 passed, 3 skipped as before), Packaging.Tests (190), Framework.Tests (192), Framework.Features.Tests (7) and Linq.Tests (22). The Framework project builds for every target framework with no warnings.

…ckage

Saving a Strict package re-created every relationship that was translated to its transitional type on load, including relationships whose part had been deleted since, so the saved package could not be read again. A relationship is now rewritten only if the collection still holds the same object. Relationships hidden by an ignore filter are rewritten as before. Every part is loaded before the first container is saved, so relationships loaded later in the save are also translated.

Fixes dotnet#2155

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Strict package: saving restores the relationship to a part deleted after open, so the saved file cannot be read again

1 participant