Skip to content

Commit b460fae

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, CustomizationPart) were carried over to the new document type, leaving orphaned content type entries. Add a virtual OnDocumentTypeChanged hook in TypedPackageFeatureCollection and override it in WordprocessingDocumentFeatures to recursively remove VBA parts when the new type is not macro-enabled. Fixes #618
1 parent 3ee250c commit b460fae

3 files changed

Lines changed: 68 additions & 0 deletions

File tree

src/DocumentFormat.OpenXml/Packaging/TypedPackageFeatureCollection.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ void IDocumentTypeFeature<TDocumentType>.ChangeDocumentType(TDocumentType newTyp
9696
try
9797
{
9898
Package.ChangeDocumentTypeInternal(CreateMainPart());
99+
OnDocumentTypeChanged(newType);
99100
}
100101
catch (OpenXmlPackageException e)
101102
{
@@ -116,5 +117,13 @@ void IDocumentTypeFeature<TDocumentType>.ChangeDocumentType(TDocumentType newTyp
116117

117118
protected abstract TMainPart CreateMainPart();
118119

120+
/// <summary>
121+
/// Called after the document type has been changed. Subclasses can override to perform
122+
/// cleanup such as removing parts that are not valid for the new document type.
123+
/// </summary>
124+
protected virtual void OnDocumentTypeChanged(TDocumentType newType)
125+
{
126+
}
127+
119128
bool IKnownDataPartFeature.IsKnown(string relationshipId) => false;
120129
}

src/DocumentFormat.OpenXml/Packaging/WordprocessingDocument.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -582,6 +582,21 @@ public WordprocessingDocumentFeatures(OpenXmlPackage package)
582582
"application/vnd.ms-word.template.macroEnabledTemplate.main+xml" => WordprocessingDocumentType.MacroEnabledTemplate,
583583
_ => default,
584584
};
585+
586+
protected override void OnDocumentTypeChanged(WordprocessingDocumentType newType)
587+
{
588+
if (newType is WordprocessingDocumentType.MacroEnabledDocument
589+
or WordprocessingDocumentType.MacroEnabledTemplate)
590+
{
591+
return;
592+
}
593+
594+
if (MainPart is { } mainPart)
595+
{
596+
mainPart.DeletePartsRecursivelyOfTypeBase<VbaProjectPart>();
597+
mainPart.DeletePartsRecursivelyOfTypeBase<CustomizationPart>();
598+
}
599+
}
585600
}
586601
}
587602
}

test/DocumentFormat.OpenXml.Tests/DocxTests01.cs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,50 @@ 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 part is no longer present
399+
stream.Position = 0;
400+
401+
using (var doc = WordprocessingDocument.Open(stream, false))
402+
{
403+
Assert.Null(doc.MainDocumentPart.VbaProjectPart);
404+
Assert.Empty(doc.MainDocumentPart.GetPartsOfType<VbaProjectPart>());
405+
}
406+
}
407+
364408
[Fact]
365409
public void W038_DocxCreation_Package()
366410
{

0 commit comments

Comments
 (0)