Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
794484d
add IsValidChild method for WordprocessingDocument
mikeebowen Apr 10, 2025
9f65d67
add IsValidDocument method for SpreadsheetDocument
mikeebowen Apr 14, 2025
b67a9bf
Add IsMinimumDocument method for Presentation
mikeebowen Apr 23, 2025
6b9d2aa
update tests and xml comments
mikeebowen Apr 23, 2025
61192e4
Merge remote-tracking branch 'upstream/main' into issue-816
mikeebowen Apr 23, 2025
0d6de91
Merge remote-tracking branch 'upstream/main' into issue-816
mikeebowen Apr 28, 2025
e9cca0a
Merge branch 'main' into issue-816
mikeebowen Apr 29, 2025
84edcb0
Merge branch 'issue-816' of github.com:mikeebowen/Open-XML-SDK into i…
mikeebowen Apr 29, 2025
48afb54
only test null argument for < .Net 8+
mikeebowen Apr 29, 2025
f83d1d0
move Wordprocessing minimum document validation to virtual method
mikeebowen Apr 29, 2025
8dc68ef
Change minimum Word doc API to OpenSettings options
mikeebowen May 1, 2025
1a0d73a
add VerifyMinimumPackage option to PresentationDocument
mikeebowen May 5, 2025
8ba0e4d
fix linting issues
mikeebowen May 5, 2025
55e3eef
remove trailing whitespace
mikeebowen May 6, 2025
69a5900
remove unnecessary $ from string
mikeebowen May 6, 2025
9bc42e2
remove unnecessary usings
mikeebowen May 6, 2025
3a0c367
use feature for minimum document
twsouthwick May 12, 2025
fca841a
a bit more
twsouthwick May 12, 2025
b46015b
Merge pull request #3 from twsouthwick/use-feature
mikeebowen May 12, 2025
a4b61a7
move VerfiyMinimumPackage to feature
mikeebowen May 12, 2025
8e1d50d
remove ThrowIfNotMinimumPackage from OpenXmlPackage
mikeebowen May 12, 2025
8d1d6ea
fix merge conflict
mikeebowen May 14, 2025
04ef42b
verify wordprocessing document
mikeebowen May 15, 2025
a236566
add minimum document validation for PowerPoint and Excel
mikeebowen May 15, 2025
af392cc
add tests
mikeebowen May 15, 2025
b6d9b45
Merge branch 'issue-816' of github.com:mikeebowen/Open-XML-SDK into i…
mikeebowen May 15, 2025
7d4c2b4
revert to match main
mikeebowen May 15, 2025
8c4d993
add validation error if part is empty
mikeebowen May 15, 2025
12699db
update how to check for empty parts
mikeebowen May 16, 2025
8772882
fix tests
mikeebowen May 16, 2025
a1847d7
custom file property part test passes
mikeebowen Jun 12, 2025
92dcc74
fix P002_Pptx_DeleteAdd_CoreExtendedProperties test
mikeebowen Jun 12, 2025
1df4b0b
fix X006_Xlsx_DeleteAdd_CoreExtendedProperties and W051_AddNewPart_To…
mikeebowen Jun 13, 2025
1cdf8d2
Merge remote-tracking branch 'upstream/main' into issue-816
mikeebowen Jun 13, 2025
d7ee41b
add blank line
mikeebowen Jun 13, 2025
2097f05
remove file from earlier version of API
mikeebowen Jun 16, 2025
31aec5a
add IsEmptyPart method to ensure stream is disposed
mikeebowen Jun 16, 2025
2d5dd09
Update src/DocumentFormat.OpenXml.Framework/Packaging/OpenXmlPart.cs
mikeebowen Jun 30, 2025
e911447
Merge remote-tracking branch 'upstream/main' into issue-816
mikeebowen Jun 30, 2025
4861f13
move file extension check to IsEmptyPartMethod
mikeebowen Jun 30, 2025
25e5c67
removed unnecessary using
mikeebowen Jun 30, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions src/DocumentFormat.OpenXml.Framework/Packaging/OpenXmlPart.cs
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,19 @@ internal virtual bool IsInVersion(FileFormatVersions version)
return true;
}

internal override bool IsEmptyPart()
{
if (!Uri.ToString().EndsWith(".xml", System.StringComparison.InvariantCultureIgnoreCase))
{
return false;
}

using (Stream stream = GetStream())
{
return stream.Length == 0;
}
}

#endregion

#region internal methods
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1657,6 +1657,11 @@ internal OpenXmlPart CreateOpenXmlPart(string relationshipType)
// find all reachable parts from the package root, the dictionary also used for cycle reference defense
internal abstract void FindAllReachableParts(IDictionary<OpenXmlPart, bool> reachableParts);

internal virtual bool IsEmptyPart()
{
return false;
}

#endregion

// Checks if the target part is in the same OpenXmlPackage as this part.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,19 @@ private void ValidatePart(OpenXmlPart part, ValidationContext context)
{
Validate(context);
}
else if (part.IsEmptyPart())
{
context.AddError(new ValidationErrorInfo
{
ErrorType = ValidationErrorType.Schema,
Id = "Sch_MissingPartRootElement",
Part = part,
Description = SR.Format(ValidationResources.Sch_MissingPartRootElement, part.Uri),
});

// The part's root element is empty, so no more errors in this part. Release the DOM to GC memory
part.UnloadRootElement();
}

if (!partRootElementLoaded && context.Errors.Count == lastErrorCount)
{
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -342,4 +342,7 @@
<data name="Sem_CellValue" xml:space="preserve">
<value>Cell contents have invalid value '{0}' for type '{1}'.</value>
</data>
<data name="Sch_MissingPartRootElement" xml:space="preserve">
<value>The '{0}' part is missing its root element.</value>
</data>
</root>
30 changes: 24 additions & 6 deletions test/DocumentFormat.OpenXml.Tests/DocxTests01.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using DocumentFormat.OpenXml.CustomProperties;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Validation;
using System;
Expand Down Expand Up @@ -136,8 +137,8 @@ public void W051_AddNewPart_ToOpenXmlPackage()
using (var stream = GetStream(TestFiles.Hyperlink, true))
using (var doc = WordprocessingDocument.Open(stream, true))
{
var pkg = (OpenXmlPackage)doc;
var wpcp = pkg.AddNewPart<RibbonExtensibilityPart>("application/xml", "rid1232131");
var footer = doc.MainDocumentPart.AddNewPart<FooterPart>();
footer.Footer = new W.Footer();
var v = new OpenXmlValidator(FileFormatVersions.Office2013);
var errs = v.Validate(doc, TestContext.Current.CancellationToken);

Expand All @@ -156,12 +157,24 @@ public void W050_DeleteAdd_CoreExtendedProperties()

doc.DeletePart(corePart);
doc.DeletePart(appPart);
doc.AddCoreFilePropertiesPart();
doc.AddExtendedFilePropertiesPart();
doc.AddCustomFilePropertiesPart();
var cfpp = doc.AddCoreFilePropertiesPart();

string xml = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?><coreProperties><title>hello</title></coreProperties>";
byte[] corePropsByteArray = System.Text.Encoding.UTF8.GetBytes(xml);
using (var stream1 = new MemoryStream(corePropsByteArray))
{
cfpp.FeedData(stream1);
}

var efpp = doc.AddExtendedFilePropertiesPart();
efpp.Properties = new ExtendedProperties.Properties();

var cusfpp = doc.AddCustomFilePropertiesPart();
cusfpp.Properties = new CustomProperties.Properties();

doc.AddDigitalSignatureOriginPart();
doc.AddExtendedPart("relType", "contentType/xml", ".xml");

doc.AddExtendedPart("relType", "contentType/xml", ".xml");
var tnPart = doc.AddThumbnailPart(ThumbnailPartType.Jpeg);
doc.DeletePart(tnPart);
tnPart = doc.AddThumbnailPart("image/jpg");
Expand All @@ -180,6 +193,7 @@ public void W049_AddNewPart_ToPackage()
using (var doc = WordprocessingDocument.Open(stream, true))
{
var wpcp = doc.AddNewPart<RibbonExtensibilityPart>("application/xml", "rid1232131");
wpcp.CustomUI = new Office.CustomUI.CustomUI();
var v = new OpenXmlValidator(FileFormatVersions.Office2013);
var errs = v.Validate(doc, TestContext.Current.CancellationToken);

Expand Down Expand Up @@ -207,6 +221,7 @@ public void W047_AddNewPart_ToPackage()
using (var doc = WordprocessingDocument.Open(stream, true))
{
var wpcp = doc.AddNewPart<RibbonExtensibilityPart>("rid123123");
wpcp.CustomUI = new Office.CustomUI.CustomUI();
var v = new OpenXmlValidator(FileFormatVersions.Office2013);
var errs = v.Validate(doc, TestContext.Current.CancellationToken);

Expand Down Expand Up @@ -239,6 +254,7 @@ public void W045_AddNewPart_ToPart()
using (var doc = WordprocessingDocument.Open(stream, true))
{
var wpcp = doc.MainDocumentPart.AddNewPart<WordprocessingCommentsPart>("application/vnd.openxmlformats-officedocument.wordprocessingml.comments+xml", "rid1232131");
wpcp.Comments = new W.Comments();
var v = new OpenXmlValidator(FileFormatVersions.Office2013);
var errs = v.Validate(doc, TestContext.Current.CancellationToken);

Expand Down Expand Up @@ -266,6 +282,7 @@ public void W043_AddNewPart()
using (var doc = WordprocessingDocument.Open(stream, true))
{
var wpcp = doc.MainDocumentPart.AddNewPart<WordprocessingCommentsPart>("rid123123");
wpcp.Comments = new W.Comments();
var v = new OpenXmlValidator(FileFormatVersions.Office2013);
var errs = v.Validate(doc, TestContext.Current.CancellationToken);

Expand All @@ -280,6 +297,7 @@ public void W042_AddNewPart()
using (var doc = WordprocessingDocument.Open(stream, true))
{
var wpcp = doc.MainDocumentPart.AddNewPart<WordprocessingCommentsPart>();
wpcp.Comments = new W.Comments();
var v = new OpenXmlValidator(FileFormatVersions.Office2013);
var errs = v.Validate(doc, TestContext.Current.CancellationToken);

Expand Down
94 changes: 91 additions & 3 deletions test/DocumentFormat.OpenXml.Tests/PptxTests01.cs
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,7 @@ public void P002_Pptx_DeleteAdd_CoreExtendedProperties()
var corePart = doc.CoreFilePropertiesPart;
var appPart = doc.ExtendedFilePropertiesPart;
var custFilePropsPart = doc.CustomFilePropertiesPart;

var thumbNailPart = doc.ThumbnailPart;

doc.DeletePart(corePart);
Expand All @@ -399,9 +400,96 @@ public void P002_Pptx_DeleteAdd_CoreExtendedProperties()
doc.DeletePart(thumbNailPart);
}

doc.AddCoreFilePropertiesPart();
doc.AddExtendedFilePropertiesPart();
doc.AddCustomFilePropertiesPart();
var coreFPP = doc.AddCoreFilePropertiesPart();
var coreFPPStream = coreFPP.GetStream();
using (var writer = new System.Xml.XmlTextWriter(coreFPPStream, System.Text.Encoding.UTF8))
{
writer.WriteRaw("""
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<cp:coreProperties xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dcterms="http://purl.org/dc/terms/" xmlns:dcmitype="http://purl.org/dc/dcmitype/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<dc:title></dc:title>
<dc:creator>Joey Daccord</dc:creator>
<cp:lastModifiedBy>Joey Daccord</cp:lastModifiedBy>
<cp:revision>2</cp:revision>
<dcterms:created xsi:type="dcterms:W3CDTF">2025-06-12T19:21:34Z</dcterms:created>
<dcterms:modified xsi:type="dcterms:W3CDTF">2025-06-12T21:23:11Z</dcterms:modified>
</cp:coreProperties>
""");
writer.Flush();
}

var appFPP = doc.AddExtendedFilePropertiesPart();
var appFPPStream = appFPP.GetStream();
using (var writer = new System.Xml.XmlTextWriter(appFPPStream, System.Text.Encoding.UTF8))
{
writer.WriteRaw("""
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties" xmlns:vt="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes">
<TotalTime>12</TotalTime>
<Words>0</Words>
<Application>Microsoft Office PowerPoint</Application>
<PresentationFormat>Widescreen</PresentationFormat>
<Paragraphs>0</Paragraphs>
<Slides>1</Slides>
<Notes>0</Notes>
<HiddenSlides>0</HiddenSlides>
<MMClips>0</MMClips>
<ScaleCrop>false</ScaleCrop>
<HeadingPairs>
<vt:vector size="6" baseType="variant">
<vt:variant>
<vt:lpstr>Fonts Used</vt:lpstr>
</vt:variant>
<vt:variant>
<vt:i4>3</vt:i4>
</vt:variant>
<vt:variant>
<vt:lpstr>Theme</vt:lpstr>
</vt:variant>
<vt:variant>
<vt:i4>1</vt:i4>
</vt:variant>
<vt:variant>
<vt:lpstr>Slide Titles</vt:lpstr>
</vt:variant>
<vt:variant>
<vt:i4>1</vt:i4>
</vt:variant>
</vt:vector>
</HeadingPairs>
<TitlesOfParts>
<vt:vector size="5" baseType="lpstr">
<vt:lpstr>Aptos</vt:lpstr>
<vt:lpstr>Aptos Display</vt:lpstr>
<vt:lpstr>Arial</vt:lpstr>
<vt:lpstr>Office Theme</vt:lpstr>
<vt:lpstr>PowerPoint Presentation</vt:lpstr>
</vt:vector>
</TitlesOfParts>
<Company></Company>
<LinksUpToDate>false</LinksUpToDate>
<SharedDoc>false</SharedDoc>
<HyperlinksChanged>false</HyperlinksChanged>
<AppVersion>16.0000</AppVersion>
</Properties>
""");
}

var custFPP = doc.AddCustomFilePropertiesPart();
var custFPPStream = custFPP.GetStream();
using (var writer = new System.Xml.XmlTextWriter(custFPPStream, System.Text.Encoding.UTF8))
{
writer.WriteRaw("""
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/custom-properties" xmlns:vt="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes">
<property fmtid="{D5CDD505-2E9C-101B-9397-08002B2CF9AE}" pid="2" name="MyCustomProp">
<vt:lpwstr>foobar</vt:lpwstr>
</property>
</Properties>
""");
writer.Flush();
}

doc.AddDigitalSignatureOriginPart();
doc.AddExtendedPart("relType", "contentType/xml", ".xml");

Expand Down
75 changes: 72 additions & 3 deletions test/DocumentFormat.OpenXml.Tests/XlsxTests01.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,78 @@ public void X006_Xlsx_DeleteAdd_CoreExtendedProperties()
var appPart = doc.ExtendedFilePropertiesPart;
doc.DeletePart(corePart);
doc.DeletePart(appPart);
doc.AddCoreFilePropertiesPart();
doc.AddExtendedFilePropertiesPart();
doc.AddCustomFilePropertiesPart();
var cFPP = doc.AddCoreFilePropertiesPart();
var cFPPStream = cFPP.GetStream();

using (var writer = new System.Xml.XmlTextWriter(cFPPStream, System.Text.Encoding.UTF8))
{
writer.WriteRaw("""
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<cp:coreProperties xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dcterms="http://purl.org/dc/terms/" xmlns:dcmitype="http://purl.org/dc/dcmitype/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<dc:creator>Shohei Ohtani</dc:creator>
<cp:lastModifiedBy>Shohei Ohtani</cp:lastModifiedBy>
<dcterms:created xsi:type="dcterms:W3CDTF">2015-06-05T18:17:20Z</dcterms:created>
<dcterms:modified xsi:type="dcterms:W3CDTF">2025-06-13T17:11:50Z</dcterms:modified>
</cp:coreProperties>
""");

writer.Flush();
}

var eFPP = doc.AddExtendedFilePropertiesPart();
var eFPPStream = eFPP.GetStream();

using (var writer = new System.Xml.XmlTextWriter(eFPPStream, System.Text.Encoding.UTF8))
{
writer.WriteRaw("""
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties" xmlns:vt="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes">
<Application>Microsoft Excel</Application>
<DocSecurity>0</DocSecurity>
<ScaleCrop>false</ScaleCrop>
<HeadingPairs>
<vt:vector size="2" baseType="variant">
<vt:variant>
<vt:lpstr>Worksheets</vt:lpstr>
</vt:variant>
<vt:variant>
<vt:i4>1</vt:i4>
</vt:variant>
</vt:vector>
</HeadingPairs>
<TitlesOfParts>
<vt:vector size="1" baseType="lpstr">
<vt:lpstr>Sheet1</vt:lpstr>
</vt:vector>
</TitlesOfParts>
<Company></Company>
<LinksUpToDate>false</LinksUpToDate>
<SharedDoc>false</SharedDoc>
<HyperlinksChanged>false</HyperlinksChanged>
<AppVersion>16.0300</AppVersion>
</Properties>
""");

writer.Flush();
}

var custFPP = doc.AddCustomFilePropertiesPart();
var custFPPStream = custFPP.GetStream();

using (var writer = new System.Xml.XmlTextWriter(custFPPStream, System.Text.Encoding.UTF8))
{
writer.WriteRaw("""
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/custom-properties" xmlns:vt="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes">
<property fmtid="{D5CDD505-2E9C-101B-9397-08002B2CF9AE}" pid="2" name="MyCustomProp">
<vt:lpwstr>tacocat</vt:lpwstr>
</property>
</Properties>
""");

writer.Flush();
}

doc.AddDigitalSignatureOriginPart();
doc.AddExtendedPart("relType", "contentType/xml", ".xml");
var tnPart = doc.AddThumbnailPart(ThumbnailPartType.Jpeg);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3832,5 +3832,23 @@ public void VersionMismatchPartValidatingTest()
Assert.Throws<System.InvalidOperationException>(() => O14Validator.Validate(wordTestDocument.MainDocumentPart, TestContext.Current.CancellationToken));
}
}

[Fact]
public void EmptyPartRootElementValidatingTest()
{
using (Stream stream = new MemoryStream())
using (WordprocessingDocument document = WordprocessingDocument.Create(stream, WordprocessingDocumentType.Document))
{
document.AddMainDocumentPart();

IEnumerable<ValidationErrorInfo> errors = O14Validator.Validate(document, TestContext.Current.CancellationToken);
ValidationErrorInfo info = errors.FirstOrDefault();

Assert.Single(errors);
Assert.Equal("Sch_MissingPartRootElement", info.Id);
Assert.Equal("The '/word/document.xml' part is missing its root element.", info.Description);
Assert.Equal(ValidationErrorType.Schema, info.ErrorType);
}
}
}
}
Loading