Skip to content

Commit bf45459

Browse files
authored
Add Check for empty parts to OpenXmlValidator (#1920)
The issue was not that there was no way to check for a minimally valid document, the issue was that the validator considered completely empty parts to be valid. So this issue was resolved by adding a check for empty parts to the OpenXmlValidator.
1 parent 5eff966 commit bf45459

File tree

9 files changed

+248
-12
lines changed

9 files changed

+248
-12
lines changed

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,19 @@ internal virtual bool IsInVersion(FileFormatVersions version)
459459
return true;
460460
}
461461

462+
internal override bool IsEmptyPart()
463+
{
464+
if (!Uri.ToString().EndsWith(".xml", System.StringComparison.InvariantCultureIgnoreCase))
465+
{
466+
return false;
467+
}
468+
469+
using (Stream stream = GetStream())
470+
{
471+
return stream.Length == 0;
472+
}
473+
}
474+
462475
#endregion
463476

464477
#region internal methods

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1657,6 +1657,11 @@ internal OpenXmlPart CreateOpenXmlPart(string relationshipType)
16571657
// find all reachable parts from the package root, the dictionary also used for cycle reference defense
16581658
internal abstract void FindAllReachableParts(IDictionary<OpenXmlPart, bool> reachableParts);
16591659

1660+
internal virtual bool IsEmptyPart()
1661+
{
1662+
return false;
1663+
}
1664+
16601665
#endregion
16611666

16621667
// Checks if the target part is in the same OpenXmlPackage as this part.

src/DocumentFormat.OpenXml.Framework/Validation/DocumentValidator.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,19 @@ private void ValidatePart(OpenXmlPart part, ValidationContext context)
106106
{
107107
Validate(context);
108108
}
109+
else if (part.IsEmptyPart())
110+
{
111+
context.AddError(new ValidationErrorInfo
112+
{
113+
ErrorType = ValidationErrorType.Schema,
114+
Id = "Sch_MissingPartRootElement",
115+
Part = part,
116+
Description = SR.Format(ValidationResources.Sch_MissingPartRootElement, part.Uri),
117+
});
118+
119+
// The part's root element is empty, so no more errors in this part. Release the DOM to GC memory
120+
part.UnloadRootElement();
121+
}
109122

110123
if (!partRootElementLoaded && context.Errors.Count == lastErrorCount)
111124
{

src/DocumentFormat.OpenXml.Framework/Validation/ValidationResources.Designer.cs

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/DocumentFormat.OpenXml.Framework/Validation/ValidationResources.resx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,4 +342,7 @@
342342
<data name="Sem_CellValue" xml:space="preserve">
343343
<value>Cell contents have invalid value '{0}' for type '{1}'.</value>
344344
</data>
345+
<data name="Sch_MissingPartRootElement" xml:space="preserve">
346+
<value>The '{0}' part is missing its root element.</value>
347+
</data>
345348
</root>

test/DocumentFormat.OpenXml.Tests/DocxTests01.cs

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) Microsoft. All rights reserved.
22
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
33

4+
using DocumentFormat.OpenXml.CustomProperties;
45
using DocumentFormat.OpenXml.Packaging;
56
using DocumentFormat.OpenXml.Validation;
67
using System;
@@ -136,8 +137,8 @@ public void W051_AddNewPart_ToOpenXmlPackage()
136137
using (var stream = GetStream(TestFiles.Hyperlink, true))
137138
using (var doc = WordprocessingDocument.Open(stream, true))
138139
{
139-
var pkg = (OpenXmlPackage)doc;
140-
var wpcp = pkg.AddNewPart<RibbonExtensibilityPart>("application/xml", "rid1232131");
140+
var footer = doc.MainDocumentPart.AddNewPart<FooterPart>();
141+
footer.Footer = new W.Footer();
141142
var v = new OpenXmlValidator(FileFormatVersions.Office2013);
142143
var errs = v.Validate(doc, TestContext.Current.CancellationToken);
143144

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

157158
doc.DeletePart(corePart);
158159
doc.DeletePart(appPart);
159-
doc.AddCoreFilePropertiesPart();
160-
doc.AddExtendedFilePropertiesPart();
161-
doc.AddCustomFilePropertiesPart();
160+
var cfpp = doc.AddCoreFilePropertiesPart();
161+
162+
string xml = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?><coreProperties><title>hello</title></coreProperties>";
163+
byte[] corePropsByteArray = System.Text.Encoding.UTF8.GetBytes(xml);
164+
using (var stream1 = new MemoryStream(corePropsByteArray))
165+
{
166+
cfpp.FeedData(stream1);
167+
}
168+
169+
var efpp = doc.AddExtendedFilePropertiesPart();
170+
efpp.Properties = new ExtendedProperties.Properties();
171+
172+
var cusfpp = doc.AddCustomFilePropertiesPart();
173+
cusfpp.Properties = new CustomProperties.Properties();
174+
162175
doc.AddDigitalSignatureOriginPart();
163-
doc.AddExtendedPart("relType", "contentType/xml", ".xml");
164176

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

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

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

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

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

test/DocumentFormat.OpenXml.Tests/PptxTests01.cs

Lines changed: 91 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,7 @@ public void P002_Pptx_DeleteAdd_CoreExtendedProperties()
389389
var corePart = doc.CoreFilePropertiesPart;
390390
var appPart = doc.ExtendedFilePropertiesPart;
391391
var custFilePropsPart = doc.CustomFilePropertiesPart;
392+
392393
var thumbNailPart = doc.ThumbnailPart;
393394

394395
doc.DeletePart(corePart);
@@ -399,9 +400,96 @@ public void P002_Pptx_DeleteAdd_CoreExtendedProperties()
399400
doc.DeletePart(thumbNailPart);
400401
}
401402

402-
doc.AddCoreFilePropertiesPart();
403-
doc.AddExtendedFilePropertiesPart();
404-
doc.AddCustomFilePropertiesPart();
403+
var coreFPP = doc.AddCoreFilePropertiesPart();
404+
var coreFPPStream = coreFPP.GetStream();
405+
using (var writer = new System.Xml.XmlTextWriter(coreFPPStream, System.Text.Encoding.UTF8))
406+
{
407+
writer.WriteRaw("""
408+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
409+
<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">
410+
<dc:title></dc:title>
411+
<dc:creator>Joey Daccord</dc:creator>
412+
<cp:lastModifiedBy>Joey Daccord</cp:lastModifiedBy>
413+
<cp:revision>2</cp:revision>
414+
<dcterms:created xsi:type="dcterms:W3CDTF">2025-06-12T19:21:34Z</dcterms:created>
415+
<dcterms:modified xsi:type="dcterms:W3CDTF">2025-06-12T21:23:11Z</dcterms:modified>
416+
</cp:coreProperties>
417+
""");
418+
writer.Flush();
419+
}
420+
421+
var appFPP = doc.AddExtendedFilePropertiesPart();
422+
var appFPPStream = appFPP.GetStream();
423+
using (var writer = new System.Xml.XmlTextWriter(appFPPStream, System.Text.Encoding.UTF8))
424+
{
425+
writer.WriteRaw("""
426+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
427+
<Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties" xmlns:vt="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes">
428+
<TotalTime>12</TotalTime>
429+
<Words>0</Words>
430+
<Application>Microsoft Office PowerPoint</Application>
431+
<PresentationFormat>Widescreen</PresentationFormat>
432+
<Paragraphs>0</Paragraphs>
433+
<Slides>1</Slides>
434+
<Notes>0</Notes>
435+
<HiddenSlides>0</HiddenSlides>
436+
<MMClips>0</MMClips>
437+
<ScaleCrop>false</ScaleCrop>
438+
<HeadingPairs>
439+
<vt:vector size="6" baseType="variant">
440+
<vt:variant>
441+
<vt:lpstr>Fonts Used</vt:lpstr>
442+
</vt:variant>
443+
<vt:variant>
444+
<vt:i4>3</vt:i4>
445+
</vt:variant>
446+
<vt:variant>
447+
<vt:lpstr>Theme</vt:lpstr>
448+
</vt:variant>
449+
<vt:variant>
450+
<vt:i4>1</vt:i4>
451+
</vt:variant>
452+
<vt:variant>
453+
<vt:lpstr>Slide Titles</vt:lpstr>
454+
</vt:variant>
455+
<vt:variant>
456+
<vt:i4>1</vt:i4>
457+
</vt:variant>
458+
</vt:vector>
459+
</HeadingPairs>
460+
<TitlesOfParts>
461+
<vt:vector size="5" baseType="lpstr">
462+
<vt:lpstr>Aptos</vt:lpstr>
463+
<vt:lpstr>Aptos Display</vt:lpstr>
464+
<vt:lpstr>Arial</vt:lpstr>
465+
<vt:lpstr>Office Theme</vt:lpstr>
466+
<vt:lpstr>PowerPoint Presentation</vt:lpstr>
467+
</vt:vector>
468+
</TitlesOfParts>
469+
<Company></Company>
470+
<LinksUpToDate>false</LinksUpToDate>
471+
<SharedDoc>false</SharedDoc>
472+
<HyperlinksChanged>false</HyperlinksChanged>
473+
<AppVersion>16.0000</AppVersion>
474+
</Properties>
475+
""");
476+
}
477+
478+
var custFPP = doc.AddCustomFilePropertiesPart();
479+
var custFPPStream = custFPP.GetStream();
480+
using (var writer = new System.Xml.XmlTextWriter(custFPPStream, System.Text.Encoding.UTF8))
481+
{
482+
writer.WriteRaw("""
483+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
484+
<Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/custom-properties" xmlns:vt="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes">
485+
<property fmtid="{D5CDD505-2E9C-101B-9397-08002B2CF9AE}" pid="2" name="MyCustomProp">
486+
<vt:lpwstr>foobar</vt:lpwstr>
487+
</property>
488+
</Properties>
489+
""");
490+
writer.Flush();
491+
}
492+
405493
doc.AddDigitalSignatureOriginPart();
406494
doc.AddExtendedPart("relType", "contentType/xml", ".xml");
407495

test/DocumentFormat.OpenXml.Tests/XlsxTests01.cs

Lines changed: 72 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,78 @@ public void X006_Xlsx_DeleteAdd_CoreExtendedProperties()
6767
var appPart = doc.ExtendedFilePropertiesPart;
6868
doc.DeletePart(corePart);
6969
doc.DeletePart(appPart);
70-
doc.AddCoreFilePropertiesPart();
71-
doc.AddExtendedFilePropertiesPart();
72-
doc.AddCustomFilePropertiesPart();
70+
var cFPP = doc.AddCoreFilePropertiesPart();
71+
var cFPPStream = cFPP.GetStream();
72+
73+
using (var writer = new System.Xml.XmlTextWriter(cFPPStream, System.Text.Encoding.UTF8))
74+
{
75+
writer.WriteRaw("""
76+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
77+
<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">
78+
<dc:creator>Shohei Ohtani</dc:creator>
79+
<cp:lastModifiedBy>Shohei Ohtani</cp:lastModifiedBy>
80+
<dcterms:created xsi:type="dcterms:W3CDTF">2015-06-05T18:17:20Z</dcterms:created>
81+
<dcterms:modified xsi:type="dcterms:W3CDTF">2025-06-13T17:11:50Z</dcterms:modified>
82+
</cp:coreProperties>
83+
""");
84+
85+
writer.Flush();
86+
}
87+
88+
var eFPP = doc.AddExtendedFilePropertiesPart();
89+
var eFPPStream = eFPP.GetStream();
90+
91+
using (var writer = new System.Xml.XmlTextWriter(eFPPStream, System.Text.Encoding.UTF8))
92+
{
93+
writer.WriteRaw("""
94+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
95+
<Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties" xmlns:vt="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes">
96+
<Application>Microsoft Excel</Application>
97+
<DocSecurity>0</DocSecurity>
98+
<ScaleCrop>false</ScaleCrop>
99+
<HeadingPairs>
100+
<vt:vector size="2" baseType="variant">
101+
<vt:variant>
102+
<vt:lpstr>Worksheets</vt:lpstr>
103+
</vt:variant>
104+
<vt:variant>
105+
<vt:i4>1</vt:i4>
106+
</vt:variant>
107+
</vt:vector>
108+
</HeadingPairs>
109+
<TitlesOfParts>
110+
<vt:vector size="1" baseType="lpstr">
111+
<vt:lpstr>Sheet1</vt:lpstr>
112+
</vt:vector>
113+
</TitlesOfParts>
114+
<Company></Company>
115+
<LinksUpToDate>false</LinksUpToDate>
116+
<SharedDoc>false</SharedDoc>
117+
<HyperlinksChanged>false</HyperlinksChanged>
118+
<AppVersion>16.0300</AppVersion>
119+
</Properties>
120+
""");
121+
122+
writer.Flush();
123+
}
124+
125+
var custFPP = doc.AddCustomFilePropertiesPart();
126+
var custFPPStream = custFPP.GetStream();
127+
128+
using (var writer = new System.Xml.XmlTextWriter(custFPPStream, System.Text.Encoding.UTF8))
129+
{
130+
writer.WriteRaw("""
131+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
132+
<Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/custom-properties" xmlns:vt="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes">
133+
<property fmtid="{D5CDD505-2E9C-101B-9397-08002B2CF9AE}" pid="2" name="MyCustomProp">
134+
<vt:lpwstr>tacocat</vt:lpwstr>
135+
</property>
136+
</Properties>
137+
""");
138+
139+
writer.Flush();
140+
}
141+
73142
doc.AddDigitalSignatureOriginPart();
74143
doc.AddExtendedPart("relType", "contentType/xml", ".xml");
75144
var tnPart = doc.AddThumbnailPart(ThumbnailPartType.Jpeg);

test/DocumentFormat.OpenXml.Tests/ofapiTest/OpenXmlValidatorTest.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3832,5 +3832,23 @@ public void VersionMismatchPartValidatingTest()
38323832
Assert.Throws<System.InvalidOperationException>(() => O14Validator.Validate(wordTestDocument.MainDocumentPart, TestContext.Current.CancellationToken));
38333833
}
38343834
}
3835+
3836+
[Fact]
3837+
public void EmptyPartRootElementValidatingTest()
3838+
{
3839+
using (Stream stream = new MemoryStream())
3840+
using (WordprocessingDocument document = WordprocessingDocument.Create(stream, WordprocessingDocumentType.Document))
3841+
{
3842+
document.AddMainDocumentPart();
3843+
3844+
IEnumerable<ValidationErrorInfo> errors = O14Validator.Validate(document, TestContext.Current.CancellationToken);
3845+
ValidationErrorInfo info = errors.FirstOrDefault();
3846+
3847+
Assert.Single(errors);
3848+
Assert.Equal("Sch_MissingPartRootElement", info.Id);
3849+
Assert.Equal("The '/word/document.xml' part is missing its root element.", info.Description);
3850+
Assert.Equal(ValidationErrorType.Schema, info.ErrorType);
3851+
}
3852+
}
38353853
}
38363854
}

0 commit comments

Comments
 (0)