Skip to content

Commit 9f5dfb7

Browse files
committed
Fix ChangeDocumentType not removing VBA parts when converting to non-macro type
When changing a macro-enabled document (.docm) to a standard document (.docx) via ChangeDocumentType(), VBA-related parts (VbaProjectPart, etc.) were carried over to the new document type. This left orphaned vbaProject content type entries in [Content_Types].xml, producing a file that could not be opened by some tools or that silently re-enabled macros in a supposedly non-macro document. After ChangeDocumentTypeInternal completes, check if the new content type is non-macro-enabled. If so, remove parts with VBA relationship types (vbaProject, keyMapCustomizations) from the new main part. Fixes #618
1 parent 3ee250c commit 9f5dfb7

2 files changed

Lines changed: 87 additions & 0 deletions

File tree

src/DocumentFormat.OpenXml/Packaging/TypedPackageFeatureCollection.cs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@
33

44
using DocumentFormat.OpenXml.Features;
55
using System;
6+
using System.Collections.Generic;
67
using System.Diagnostics.CodeAnalysis;
78
using System.IO;
89
using System.IO.Packaging;
10+
using System.Linq;
911

1012
namespace DocumentFormat.OpenXml.Packaging;
1113

@@ -96,6 +98,19 @@ void IDocumentTypeFeature<TDocumentType>.ChangeDocumentType(TDocumentType newTyp
9698
try
9799
{
98100
Package.ChangeDocumentTypeInternal(CreateMainPart());
101+
102+
// When changing from a macro-enabled type to a non-macro type,
103+
// remove VBA-related parts that are no longer valid for the new type.
104+
// This prevents orphaned vbaProject content type entries in the package.
105+
// See: https://github.com/dotnet/Open-XML-SDK/issues/618
106+
var newContentType = GetContentType(newType);
107+
108+
if (newContentType is not null
109+
&& !newContentType.Contains("macroEnabled", StringComparison.OrdinalIgnoreCase)
110+
&& MainPart is { } mainPart)
111+
{
112+
RemoveVbaPartsFromMainPart(mainPart);
113+
}
99114
}
100115
catch (OpenXmlPackageException e)
101116
{
@@ -116,5 +131,30 @@ void IDocumentTypeFeature<TDocumentType>.ChangeDocumentType(TDocumentType newTyp
116131

117132
protected abstract TMainPart CreateMainPart();
118133

134+
/// <summary>
135+
/// Removes VBA-related parts (vbaProject, vbaData, keyMapCustomizations) from the main part.
136+
/// These parts are specific to macro-enabled document types and must be removed when
137+
/// converting to a non-macro type.
138+
/// </summary>
139+
private static void RemoveVbaPartsFromMainPart(OpenXmlPart mainPart)
140+
{
141+
// Relationship types for VBA-related parts that should not exist in non-macro documents
142+
var vbaRelationshipTypes = new HashSet<string>(StringComparer.Ordinal)
143+
{
144+
"http://schemas.microsoft.com/office/2006/relationships/vbaProject",
145+
"http://schemas.microsoft.com/office/2006/relationships/keyMapCustomizations",
146+
};
147+
148+
var partsToRemove = mainPart.ChildrenRelationshipParts
149+
.Where(p => vbaRelationshipTypes.Contains(p.Value.RelationshipType))
150+
.Select(p => p.Value)
151+
.ToList();
152+
153+
foreach (var part in partsToRemove)
154+
{
155+
mainPart.DeletePart(part);
156+
}
157+
}
158+
119159
bool IKnownDataPartFeature.IsKnown(string relationshipId) => false;
120160
}

test/DocumentFormat.OpenXml.Tests/DocxTests01.cs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,53 @@ public void W039_ChangeDocumentType()
361361
}
362362
}
363363

364+
[Fact]
365+
public void ChangeDocumentType_MacroEnabledToDocument_RemovesVbaParts()
366+
{
367+
using var stream = new MemoryStream();
368+
369+
// Create a macro-enabled document with a VbaProjectPart
370+
using (var doc = WordprocessingDocument.Create(stream, WordprocessingDocumentType.MacroEnabledDocument))
371+
{
372+
var mainPart = doc.AddMainDocumentPart();
373+
mainPart.Document = new W.Document(new W.Body(new W.Paragraph(new W.Run(new W.Text("Test")))));
374+
375+
// Add a VbaProjectPart (the part that causes the issue)
376+
mainPart.AddNewPart<VbaProjectPart>();
377+
378+
Assert.NotNull(mainPart.VbaProjectPart);
379+
Assert.Equal(WordprocessingDocumentType.MacroEnabledDocument, doc.DocumentType);
380+
}
381+
382+
// Re-open and change type to a standard document
383+
stream.Position = 0;
384+
385+
using (var doc = WordprocessingDocument.Open(stream, true))
386+
{
387+
Assert.Equal(WordprocessingDocumentType.MacroEnabledDocument, doc.DocumentType);
388+
Assert.NotNull(doc.MainDocumentPart.VbaProjectPart);
389+
390+
doc.ChangeDocumentType(WordprocessingDocumentType.Document);
391+
392+
Assert.Equal(WordprocessingDocumentType.Document, doc.DocumentType);
393+
394+
// After changing to a non-macro type, VbaProjectPart should be removed
395+
Assert.Null(doc.MainDocumentPart.VbaProjectPart);
396+
}
397+
398+
// Re-open and verify the VBA content type is not present in the package
399+
stream.Position = 0;
400+
401+
using (var package = Package.Open(stream, FileMode.Open, FileAccess.Read))
402+
{
403+
var contentTypes = package.GetParts()
404+
.Select(p => p.ContentType)
405+
.ToList();
406+
407+
Assert.DoesNotContain("application/vnd.ms-office.vbaProject", contentTypes);
408+
}
409+
}
410+
364411
[Fact]
365412
public void W038_DocxCreation_Package()
366413
{

0 commit comments

Comments
 (0)