From 794484d978f0be2329ecca3f8a1360350e87f686 Mon Sep 17 00:00:00 2001 From: Michael Bowen <10384982+mikeebowen@users.noreply.github.com> Date: Thu, 10 Apr 2025 14:55:17 -0700 Subject: [PATCH 01/33] add IsValidChild method for WordprocessingDocument --- .../Packaging/WordprocessingDocument.cs | 67 ++++++++++ .../OpenXmlPackageTests.cs | 118 ++++++++++++++++++ 2 files changed, 185 insertions(+) diff --git a/src/DocumentFormat.OpenXml/Packaging/WordprocessingDocument.cs b/src/DocumentFormat.OpenXml/Packaging/WordprocessingDocument.cs index 85a1e7488..94da4f21d 100644 --- a/src/DocumentFormat.OpenXml/Packaging/WordprocessingDocument.cs +++ b/src/DocumentFormat.OpenXml/Packaging/WordprocessingDocument.cs @@ -310,6 +310,73 @@ public static WordprocessingDocument Open(Package package, OpenSettings openSett public static WordprocessingDocument Open(Package package) => Open(package, new OpenSettings()); + /// + /// Validates whether the specified file is a valid WordprocessingDocument of the given type. + /// + /// The path to the WordprocessingDocument file. + /// The expected type of the WordprocessingDocument. Defaults to . + /// True if the file is a valid WordprocessingDocument of the specified type; otherwise, false. + public static bool IsValidDocument(string path, WordprocessingDocumentType documentType = WordprocessingDocumentType.Document) + { + if (string.IsNullOrEmpty(path)) + { + return false; + } + + try + { + if (!File.Exists(path)) + { + return false; + } + + string ext = new FileInfo(path).Extension.ToUpperInvariant(); + + switch (ext) + { + case ".DOCX": + if (documentType != WordprocessingDocumentType.Document) + { + return false; + } + + break; + case ".DOTX": + if (documentType != WordprocessingDocumentType.Template) + { + return false; + } + + break; + case ".DOCM": + if (documentType != WordprocessingDocumentType.MacroEnabledDocument) + { + return false; + } + + break; + case ".DOTM": + if (documentType != WordprocessingDocumentType.MacroEnabledTemplate) + { + return false; + } + + break; + default: + return false; + } + + using (WordprocessingDocument wordprocessingDocument = Open(path, false)) + { + return wordprocessingDocument?.MainDocumentPart?.Document?.Body is not null; + } + } + catch + { + return false; + } + } + /// /// Changes the document type. /// diff --git a/test/DocumentFormat.OpenXml.Packaging.Tests/OpenXmlPackageTests.cs b/test/DocumentFormat.OpenXml.Packaging.Tests/OpenXmlPackageTests.cs index 1f4a0b86f..0531c8f13 100644 --- a/test/DocumentFormat.OpenXml.Packaging.Tests/OpenXmlPackageTests.cs +++ b/test/DocumentFormat.OpenXml.Packaging.Tests/OpenXmlPackageTests.cs @@ -3,6 +3,7 @@ using DocumentFormat.OpenXml.Presentation; using DocumentFormat.OpenXml.Wordprocessing; +using Microsoft.Testing.Platform.MSBuild; using System; using System.Collections.Generic; using System.IO; @@ -330,5 +331,122 @@ public void SucceedWithMissingCalcChainPart() Assert.NotNull(spd); } + + [Fact] + public void IsValidDocument_ShouldReturnFalse_WhenPathIsNull() + { + // Act + bool result = WordprocessingDocument.IsValidDocument(null); + + // Assert + Assert.False(result); + } + + [Fact] + public void IsValidDocument_ShouldReturnFalse_WhenFileDoesNotExist() + { + // Arrange + string nonExistentPath = string.Concat(Path.GetTempPath(), "nonexistent.docx"); + + // Act + bool result = WordprocessingDocument.IsValidDocument(nonExistentPath); + + // Assert + Assert.False(result); + } + + [Fact] + public void IsValidDocument_ShouldReturnFalse_WhenFileExtensionIsUnsupported() + { + // Arrange + string unsupportedFilePath = string.Concat(Path.GetTempPath(), "unsupported.txt"); + File.WriteAllText(unsupportedFilePath, "Test content"); + + try + { + // Act + bool result = WordprocessingDocument.IsValidDocument(unsupportedFilePath); + + // Assert + Assert.False(result); + } + finally + { + File.Delete(unsupportedFilePath); + } + } + + [Fact] + public void IsValidDocument_ShouldReturnFalse_WhenDocumentTypeDoesNotMatchExtension() + { + // Arrange + string filePath = string.Concat(Path.GetTempPath(), "test.docx"); + File.WriteAllText(filePath, "Test content"); + + try + { + // Act + bool result = WordprocessingDocument.IsValidDocument(filePath, WordprocessingDocumentType.Template); + + // Assert + Assert.False(result); + } + finally + { + File.Delete(filePath); + } + } + + [Fact] + public void IsValidDocument_ShouldReturnTrue_ForValidDocument() + { + // Arrange + string filePath = string.Concat(Path.GetTempPath(), "test.docx"); + + using (WordprocessingDocument document = WordprocessingDocument.Create(filePath, WordprocessingDocumentType.Document)) + { + document.AddMainDocumentPart(); + document.MainDocumentPart.Document = new Document(new Body()); + } + + try + { + // Act + bool result = WordprocessingDocument.IsValidDocument(filePath); + + // Assert + Assert.True(result); + } + finally + { + File.Delete(filePath); + } + } + + [Fact] + public void IsValidDocument_ShouldReturnFalse_WhenDocumentIsCorrupted() + { + // Arrange + string corruptedFilePath = string.Concat(Path.GetTempPath(), "corrupt.docx"); + using (WordprocessingDocument wordprocessingDocument = WordprocessingDocument.Create(corruptedFilePath, WordprocessingDocumentType.Document)) + { + MainDocumentPart mainDocumentPart = wordprocessingDocument.AddMainDocumentPart(); + + mainDocumentPart.Document = new Document(new Paragraph()); + } + + try + { + // Act + bool result = WordprocessingDocument.IsValidDocument(corruptedFilePath); + + // Assert + Assert.False(result); + } + finally + { + File.Delete(corruptedFilePath); + } + } } } From 9f65d671fa80c051ce01d6554c13a74f68032cfa Mon Sep 17 00:00:00 2001 From: Michael Bowen <10384982+mikeebowen@users.noreply.github.com> Date: Mon, 14 Apr 2025 15:41:27 -0700 Subject: [PATCH 02/33] add IsValidDocument method for SpreadsheetDocument --- .../Packaging/SpreadsheetDocument.cs | 78 ++++++++++ .../OpenXmlPackageTests.cs | 139 +++++++++++++++++- 2 files changed, 210 insertions(+), 7 deletions(-) diff --git a/src/DocumentFormat.OpenXml/Packaging/SpreadsheetDocument.cs b/src/DocumentFormat.OpenXml/Packaging/SpreadsheetDocument.cs index f950048c1..61ea63d5c 100644 --- a/src/DocumentFormat.OpenXml/Packaging/SpreadsheetDocument.cs +++ b/src/DocumentFormat.OpenXml/Packaging/SpreadsheetDocument.cs @@ -3,6 +3,7 @@ using DocumentFormat.OpenXml.Builder; using DocumentFormat.OpenXml.Features; +using DocumentFormat.OpenXml.Spreadsheet; using System; using System.Diagnostics.CodeAnalysis; using System.IO; @@ -267,6 +268,83 @@ public static SpreadsheetDocument Open(System.IO.Stream stream, bool isEditable) public static SpreadsheetDocument Open(System.IO.Packaging.Package package) => Open(package, new OpenSettings()); + /// + /// Validates whether the specified file is a valid SpreadsheetDocument of the given type. + /// + /// The path to the SpreadsheetDocument file. + /// The expected type of the SpreadsheetDocument. Defaults to . + /// True if the file is a valid SpreadsheetDocument of the specified type; otherwise, false. + public static bool IsValidDocument(string path, SpreadsheetDocumentType documentType = SpreadsheetDocumentType.Workbook) + { + if (string.IsNullOrEmpty(path)) + { + return false; + } + + try + { + if (!File.Exists(path)) + { + return false; + } + + string ext = new FileInfo(path).Extension.ToUpperInvariant(); + + switch (ext) + { + case ".XLSX": + if (documentType != SpreadsheetDocumentType.Workbook) + { + return false; + } + + break; + case ".XLTX": + if (documentType != SpreadsheetDocumentType.Template) + { + return false; + } + + break; + case ".XLSM": + if (documentType != SpreadsheetDocumentType.MacroEnabledWorkbook) + { + return false; + } + + break; + case ".XLTM": + if (documentType != SpreadsheetDocumentType.MacroEnabledTemplate) + { + return false; + } + + break; + case ".XLAM": + if (documentType != SpreadsheetDocumentType.AddIn) + { + return false; + } + + break; + default: + return false; + } + + using (SpreadsheetDocument spreadsheetDocument = Open(path, false)) + { + Sheet? sheet = spreadsheetDocument?.WorkbookPart?.Workbook?.Sheets?.GetFirstChild(); + SheetData? sheetData = spreadsheetDocument?.WorkbookPart?.WorksheetParts?.FirstOrDefaultAndMaxOne()?.Worksheet?.GetFirstChild(); + + return sheet is not null && sheetData is not null; + } + } + catch + { + return false; + } + } + /// /// Changes the document type. /// diff --git a/test/DocumentFormat.OpenXml.Packaging.Tests/OpenXmlPackageTests.cs b/test/DocumentFormat.OpenXml.Packaging.Tests/OpenXmlPackageTests.cs index 0531c8f13..90e594320 100644 --- a/test/DocumentFormat.OpenXml.Packaging.Tests/OpenXmlPackageTests.cs +++ b/test/DocumentFormat.OpenXml.Packaging.Tests/OpenXmlPackageTests.cs @@ -3,7 +3,6 @@ using DocumentFormat.OpenXml.Presentation; using DocumentFormat.OpenXml.Wordprocessing; -using Microsoft.Testing.Platform.MSBuild; using System; using System.Collections.Generic; using System.IO; @@ -333,7 +332,7 @@ public void SucceedWithMissingCalcChainPart() } [Fact] - public void IsValidDocument_ShouldReturnFalse_WhenPathIsNull() + public void WordprocessingDocument_IsValidDocument_ShouldReturnFalse_WhenPathIsNull() { // Act bool result = WordprocessingDocument.IsValidDocument(null); @@ -343,7 +342,7 @@ public void IsValidDocument_ShouldReturnFalse_WhenPathIsNull() } [Fact] - public void IsValidDocument_ShouldReturnFalse_WhenFileDoesNotExist() + public void WordprocessingDocument_IsValidDocument_ShouldReturnFalse_WhenFileDoesNotExist() { // Arrange string nonExistentPath = string.Concat(Path.GetTempPath(), "nonexistent.docx"); @@ -356,7 +355,7 @@ public void IsValidDocument_ShouldReturnFalse_WhenFileDoesNotExist() } [Fact] - public void IsValidDocument_ShouldReturnFalse_WhenFileExtensionIsUnsupported() + public void WordprocessingDocument_IsValidDocument_ShouldReturnFalse_WhenFileExtensionIsUnsupported() { // Arrange string unsupportedFilePath = string.Concat(Path.GetTempPath(), "unsupported.txt"); @@ -377,7 +376,7 @@ public void IsValidDocument_ShouldReturnFalse_WhenFileExtensionIsUnsupported() } [Fact] - public void IsValidDocument_ShouldReturnFalse_WhenDocumentTypeDoesNotMatchExtension() + public void WordprocessingDocument_IsValidDocument_ShouldReturnFalse_WhenDocumentTypeDoesNotMatchExtension() { // Arrange string filePath = string.Concat(Path.GetTempPath(), "test.docx"); @@ -398,7 +397,7 @@ public void IsValidDocument_ShouldReturnFalse_WhenDocumentTypeDoesNotMatchExtens } [Fact] - public void IsValidDocument_ShouldReturnTrue_ForValidDocument() + public void WordprocessingDocument_IsValidDocument_ShouldReturnTrue_ForValidDocument() { // Arrange string filePath = string.Concat(Path.GetTempPath(), "test.docx"); @@ -424,7 +423,7 @@ public void IsValidDocument_ShouldReturnTrue_ForValidDocument() } [Fact] - public void IsValidDocument_ShouldReturnFalse_WhenDocumentIsCorrupted() + public void WordprocessingDocument_IsValidDocument_ShouldReturnFalse_WhenDocumentIsCorrupted() { // Arrange string corruptedFilePath = string.Concat(Path.GetTempPath(), "corrupt.docx"); @@ -448,5 +447,131 @@ public void IsValidDocument_ShouldReturnFalse_WhenDocumentIsCorrupted() File.Delete(corruptedFilePath); } } + + [Fact] + public void SpreadsheetDocument_IsValidDocument_ShouldReturnFalse_WhenPathIsNull() + { + // Act + bool result = SpreadsheetDocument.IsValidDocument(null); + + // Assert + Assert.False(result); + } + + [Fact] + public void SpreadsheetDocument_IsValidDocument_ShouldReturnFalse_WhenFileDoesNotExist() + { + // Arrange + string nonExistentPath = string.Concat(Path.GetTempPath(), "nonexistent.docx"); + + // Act + bool result = SpreadsheetDocument.IsValidDocument(nonExistentPath); + + // Assert + Assert.False(result); + } + + [Fact] + public void SpreadsheetDocument_IsValidDocument_ShouldReturnFalse_WhenFileExtensionIsUnsupported() + { + // Arrange + string unsupportedFilePath = Path.GetTempFileName(); + File.WriteAllText(unsupportedFilePath, "Test content"); + + try + { + // Act + bool result = SpreadsheetDocument.IsValidDocument(unsupportedFilePath); + + // Assert + Assert.False(result); + } + finally + { + File.Delete(unsupportedFilePath); + } + } + + [Fact] + public void SpreadsheetDocument_IsValidDocument_ShouldReturnFalse_WhenDocumentTypeDoesNotMatchExtension() + { + // Arrange + string filePath = Path.Combine(Path.GetTempPath(), "test.xlsx"); + + using (SpreadsheetDocument spreadsheetDocument = SpreadsheetDocument.Create(filePath, SpreadsheetDocumentType.Workbook)) + { + WorkbookPart wbp = spreadsheetDocument.AddWorkbookPart(); + WorksheetPart wsp = wbp.AddNewPart(); + wbp.Workbook = new Spreadsheet.Workbook(new Spreadsheet.Sheets(new Spreadsheet.Sheet() { Id = wbp.GetIdOfPart(wsp), SheetId = 1, Name = "Sheet1" })); + wsp.Worksheet = new Spreadsheet.Worksheet(new Spreadsheet.SheetData()); + } + + try + { + // Act + bool result = SpreadsheetDocument.IsValidDocument(filePath, SpreadsheetDocumentType.Template); + + // Assert + Assert.False(result); + } + finally + { + File.Delete(filePath); + } + } + + [Fact] + public void SpreadsheetDocument_IsValidDocument_ShouldReturnTrue_ForValidDocument() + { + // Arrange + string filePath = Path.Combine(Path.GetTempPath(), "valid.xlsx"); + + using (SpreadsheetDocument spreadsheetDocument = SpreadsheetDocument.Create(filePath, SpreadsheetDocumentType.Workbook)) + { + WorkbookPart wbp = spreadsheetDocument.AddWorkbookPart(); + WorksheetPart wsp = wbp.AddNewPart(); + wbp.Workbook = new Spreadsheet.Workbook(new Spreadsheet.Sheets(new Spreadsheet.Sheet() { Id = wbp.GetIdOfPart(wsp), SheetId = 1, Name = "Sheet1" })); + wsp.Worksheet = new Spreadsheet.Worksheet(new Spreadsheet.SheetData()); + } + + try + { + // Act + bool result = SpreadsheetDocument.IsValidDocument(filePath); + + // Assert + Assert.True(result); + } + finally + { + File.Delete(filePath); + } + } + + [Fact] + public void SpreadsheetDocument_IsValidDocument_ShouldReturnFalse_WhenDocumentIsInvalid() + { + // Arrange + string filePath = Path.Combine(Path.GetTempPath(), "invalid.xlsx"); + + using (SpreadsheetDocument invalidSpreadsheetDocument = SpreadsheetDocument.Create(filePath, SpreadsheetDocumentType.Workbook)) + { + WorkbookPart wbp = invalidSpreadsheetDocument.AddWorkbookPart(); + WorksheetPart wsp = wbp.AddNewPart(); + } + + try + { + // Act + bool result = SpreadsheetDocument.IsValidDocument(filePath); + + // Assert + Assert.False(result); + } + finally + { + File.Delete(filePath); + } + } } } From b67a9bfee7e41826a6aa92d3a9b7211c62f32252 Mon Sep 17 00:00:00 2001 From: Michael Bowen <10384982+mikeebowen@users.noreply.github.com> Date: Wed, 23 Apr 2025 10:42:01 -0700 Subject: [PATCH 03/33] Add IsMinimumDocument method for Presentation --- .../Packaging/PresentationDocument.cs | 93 +++++++++++ .../OpenXmlPackageTests.cs | 155 ++++++++++++++++++ 2 files changed, 248 insertions(+) diff --git a/src/DocumentFormat.OpenXml/Packaging/PresentationDocument.cs b/src/DocumentFormat.OpenXml/Packaging/PresentationDocument.cs index fe7995fa2..a75087bd2 100644 --- a/src/DocumentFormat.OpenXml/Packaging/PresentationDocument.cs +++ b/src/DocumentFormat.OpenXml/Packaging/PresentationDocument.cs @@ -2,11 +2,14 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using DocumentFormat.OpenXml.Builder; +using DocumentFormat.OpenXml.Drawing; using DocumentFormat.OpenXml.Features; +using DocumentFormat.OpenXml.Presentation; using System; using System.Diagnostics.CodeAnalysis; using System.IO; using System.IO.Packaging; +using System.Linq; using System.Reflection; namespace DocumentFormat.OpenXml.Packaging @@ -267,6 +270,96 @@ public static PresentationDocument Open(Package package, OpenSettings openSettin .Build() .Open(package); + /// + /// Validates whether the specified file is a minimum valid PresentationDocument. + /// + /// The path to the PresentationDocument file. + /// The expected type of the PresentationDocument. Defaults to PresentationDocumentType.Presentation. + /// True if the file is a minimum valid PresentationDocument; otherwise, false. + /// + /// Thrown when the is invalid or unsupported. + /// + public static bool IsMinimumDocument(string path, PresentationDocumentType documentType = PresentationDocumentType.Presentation) + { + if (documentType == PresentationDocumentType.AddIn || documentType == PresentationDocumentType.Slideshow || documentType == PresentationDocumentType.MacroEnabledSlideshow) + { + throw new ArgumentException($"Invalid value: {documentType}. Allowed values are PresentationDocumentType.Presentation, PresentationDocumentType.MacroEnabledPresentation, PresentationDocumentType.MacroEnabledTemplate, and PresentationDocumentType.Template."); + } + + if (string.IsNullOrEmpty(path)) + { + return false; + } + + if (path.IndexOfAny(System.IO.Path.GetInvalidPathChars()) >= 0) + { + return false; + } + + try + { + if (!File.Exists(path)) + { + return false; + } + + string ext = new FileInfo(path).Extension.ToUpperInvariant(); + + switch (ext) + { + case ".PPTX": + if (documentType != PresentationDocumentType.Presentation) + { + return false; + } + + break; + case ".POTX": + if (documentType != PresentationDocumentType.Template) + { + return false; + } + + break; + case ".PPTM": + if (documentType != PresentationDocumentType.MacroEnabledPresentation) + { + return false; + } + + break; + case ".POTM": + if (documentType != PresentationDocumentType.MacroEnabledTemplate) + { + return false; + } + + break; + case ".PPSX": + throw new FileFormatException($"Validation for PresentationDocumentType.AddIn (.ppsx) is not supported."); + case ".PPSM": + throw new FileFormatException($"Validation for PresentationDocumentType.AddIn (.ppsm) is not supported."); + case ".PPAM": + throw new FileFormatException($"Validation for PresentationDocumentType.AddIn (.ppam) is not supported."); + default: + return false; + } + + using (PresentationDocument presentationDocument = Open(path, false)) + { + NotesSize? notesSize = presentationDocument.PresentationPart?.Presentation?.NotesSize; + + return notesSize is not null && notesSize.Cx is not null && notesSize.Cx.HasValue && + notesSize.Cx >= 0 && notesSize.Cx <= 27273042316900 && notesSize.Cy is not null && notesSize.Cy.HasValue && + notesSize.Cy >= 0 && notesSize.Cy <= 27273042316900; + } + } + catch + { + return false; + } + } + /// /// Changes the document type. /// diff --git a/test/DocumentFormat.OpenXml.Packaging.Tests/OpenXmlPackageTests.cs b/test/DocumentFormat.OpenXml.Packaging.Tests/OpenXmlPackageTests.cs index 90e594320..3a87091e3 100644 --- a/test/DocumentFormat.OpenXml.Packaging.Tests/OpenXmlPackageTests.cs +++ b/test/DocumentFormat.OpenXml.Packaging.Tests/OpenXmlPackageTests.cs @@ -573,5 +573,160 @@ public void SpreadsheetDocument_IsValidDocument_ShouldReturnFalse_WhenDocumentIs File.Delete(filePath); } } + +#region PresentationDocument.IsMinimumDocument tests + [Fact] + public void IsMinimumDocument_ValidPptx_ReturnsTrue() + { + // Arrange + string filePath = Path.Combine(Path.GetTempPath(), "invalid.pptx"); + + using (PresentationDocument presentationDocument = PresentationDocument.Create(filePath, PresentationDocumentType.Presentation)) + { + PresentationPart presentationPart = presentationDocument.AddPresentationPart(); + presentationPart.Presentation = new Presentation.Presentation(); + presentationPart.Presentation.AddChild(new NotesSize() { Cx = 913607, Cy = 913607 }); + } + + // Act + bool result = PresentationDocument.IsMinimumDocument(filePath, PresentationDocumentType.Presentation); + + // Assert + Assert.True(result); + + // Cleanup + File.Delete(filePath); + } + + [Fact] + public void IsMinimumDocument_ValidPotx_ReturnsTrue() + { + // Arrange + string filePath = Path.Combine(Path.GetTempPath(), "invalid.potx"); + + using (PresentationDocument presentationDocument = PresentationDocument.Create(filePath, PresentationDocumentType.Presentation)) + { + PresentationPart presentationPart = presentationDocument.AddPresentationPart(); + presentationPart.Presentation = new Presentation.Presentation(); + presentationPart.Presentation.AddChild(new NotesSize() { Cx = 913607, Cy = 913607 }); + } + + // Act + bool result = PresentationDocument.IsMinimumDocument(filePath, PresentationDocumentType.Template); + + // Assert + Assert.True(result); + + // Cleanup + File.Delete(filePath); + } + + [Fact] + public void IsMinimumDocument_InvalidExtension_ReturnsFalse() + { + // Arrange + string filePath = Path.Combine(Path.GetTempPath(), "invalid.txt"); + + File.WriteAllText(filePath, string.Empty); + + // Act + bool result = PresentationDocument.IsMinimumDocument(filePath, PresentationDocumentType.Presentation); + + // Assert + Assert.False(result); + + // Cleanup + File.Delete(filePath); + } + + [Fact] + public void IsMinimumDocument_NonExistentFile_ReturnsFalse() + { + // Arrange + string filePath = "nonexistent.pptx"; + + // Act + bool result = PresentationDocument.IsMinimumDocument(filePath, PresentationDocumentType.Presentation); + + // Assert + Assert.False(result); + } + + [Fact] + public void IsMinimumDocument_InvalidPathCharacters_ReturnsFalse() + { + // Arrange + string filePath = "invalid|path.pptx"; + + // Act + bool result = PresentationDocument.IsMinimumDocument(filePath, PresentationDocumentType.Presentation); + + // Assert + Assert.False(result); + } + + [Fact] + public void IsMinimumDocument_UnsupportedDocumentType_ThrowsArgumentException() + { + // Arrange + string filePath = Path.Combine(Path.GetTempPath(), "invalid.pptx"); + + using (PresentationDocument presentationDocument = PresentationDocument.Create(filePath, PresentationDocumentType.Presentation)) + { + PresentationPart presentationPart = presentationDocument.AddPresentationPart(); + presentationPart.Presentation = new Presentation.Presentation(); + presentationPart.Presentation.AddChild(new NotesSize() { Cx = 913607, Cy = 913607 }); + } + + // Act & Assert + Assert.Throws(() => + PresentationDocument.IsMinimumDocument(filePath, PresentationDocumentType.AddIn)); + + // Cleanup + File.Delete(filePath); + } + + [Fact] + public void IsMinimumDocument_EmptyPath_ReturnsFalse() + { + // Act + bool result = PresentationDocument.IsMinimumDocument(string.Empty, PresentationDocumentType.Presentation); + + // Assert + Assert.False(result); + } + + [Fact] + public void IsMinimumDocument_NullPath_ReturnsFalse() + { + // Act + bool result = PresentationDocument.IsMinimumDocument(null, PresentationDocumentType.Presentation); + + // Assert + Assert.False(result); + } + + [Fact] + public void IsMinimumDocument_InvalidContent_ReturnsFalse() + { + // Arrange + string filePath = Path.Combine(Path.GetTempPath(), "invalid.pptx"); + + using (PresentationDocument presentationDocument = PresentationDocument.Create(filePath, PresentationDocumentType.Presentation)) + { + PresentationPart presentationPart = presentationDocument.AddPresentationPart(); + presentationPart.Presentation = new Presentation.Presentation(); + } + + // Act + bool result = PresentationDocument.IsMinimumDocument(filePath, PresentationDocumentType.Presentation); + + // Assert + Assert.False(result); + + // Cleanup + File.Delete(filePath); + } + #endregion } } From 6b9d2aa1ffb8f2f0971a6c73cc091a600ddba043 Mon Sep 17 00:00:00 2001 From: Michael Bowen <10384982+mikeebowen@users.noreply.github.com> Date: Wed, 23 Apr 2025 11:54:07 -0700 Subject: [PATCH 04/33] update tests and xml comments --- .../Packaging/PresentationDocument.cs | 28 +- .../Packaging/SpreadsheetDocument.cs | 48 ++- .../Packaging/WordprocessingDocument.cs | 31 +- .../OpenXmlPackageTests.cs | 329 ++++++++++-------- 4 files changed, 279 insertions(+), 157 deletions(-) diff --git a/src/DocumentFormat.OpenXml/Packaging/PresentationDocument.cs b/src/DocumentFormat.OpenXml/Packaging/PresentationDocument.cs index a75087bd2..1843bcceb 100644 --- a/src/DocumentFormat.OpenXml/Packaging/PresentationDocument.cs +++ b/src/DocumentFormat.OpenXml/Packaging/PresentationDocument.cs @@ -274,11 +274,35 @@ public static PresentationDocument Open(Package package, OpenSettings openSettin /// Validates whether the specified file is a minimum valid PresentationDocument. /// /// The path to the PresentationDocument file. - /// The expected type of the PresentationDocument. Defaults to PresentationDocumentType.Presentation. - /// True if the file is a minimum valid PresentationDocument; otherwise, false. + /// + /// The expected type of the PresentationDocument. Defaults to . + /// Supported types are: + /// + /// (.pptx) + /// (.potx) + /// (.pptm) + /// (.potm) + /// + /// + /// + /// true if the file is a minimum valid PresentationDocument; otherwise, false. + /// /// /// Thrown when the is invalid or unsupported. /// + /// + /// A minimum valid PresentationDocument must meet the following criteria: + /// + /// The file must exist and have a valid extension matching the . + /// The file must contain a valid element in the presentation part. + /// + /// Unsupported document types include: + /// + /// (.ppam) + /// (.ppsx) + /// (.ppsm) + /// + /// public static bool IsMinimumDocument(string path, PresentationDocumentType documentType = PresentationDocumentType.Presentation) { if (documentType == PresentationDocumentType.AddIn || documentType == PresentationDocumentType.Slideshow || documentType == PresentationDocumentType.MacroEnabledSlideshow) diff --git a/src/DocumentFormat.OpenXml/Packaging/SpreadsheetDocument.cs b/src/DocumentFormat.OpenXml/Packaging/SpreadsheetDocument.cs index 61ea63d5c..2b7ecab5f 100644 --- a/src/DocumentFormat.OpenXml/Packaging/SpreadsheetDocument.cs +++ b/src/DocumentFormat.OpenXml/Packaging/SpreadsheetDocument.cs @@ -269,18 +269,51 @@ public static SpreadsheetDocument Open(System.IO.Packaging.Package package) => Open(package, new OpenSettings()); /// - /// Validates whether the specified file is a valid SpreadsheetDocument of the given type. + /// Validates whether the specified file is a minimum valid SpreadsheetDocument. /// /// The path to the SpreadsheetDocument file. - /// The expected type of the SpreadsheetDocument. Defaults to . - /// True if the file is a valid SpreadsheetDocument of the specified type; otherwise, false. - public static bool IsValidDocument(string path, SpreadsheetDocumentType documentType = SpreadsheetDocumentType.Workbook) + /// + /// The expected type of the SpreadsheetDocument. Defaults to . + /// Supported types are: + /// + /// (.xlsx) + /// (.xltx) + /// (.xlsm) + /// (.xltm) + /// + /// + /// + /// true if the file is a minimum valid SpreadsheetDocument; otherwise, false. + /// + /// + /// Thrown when the is invalid or unsupported. + /// + /// + /// A minimum valid SpreadsheetDocument must meet the following criteria: + /// + /// The file must exist and have a valid extension matching the . + /// The file must contain at least one element in the workbook part. + /// The file must contain at least one element in the worksheet part. + /// + /// Unsupported document types include (.xlam). + /// + public static bool IsMinimumDocument(string path, SpreadsheetDocumentType documentType = SpreadsheetDocumentType.Workbook) { + if (documentType == SpreadsheetDocumentType.AddIn) + { + throw new ArgumentException($"Invalid value: {documentType}. Allowed values are SpreadsheetDocumentType.Workbook, SpreadsheetDocumentType.Template, SpreadsheetDocumentType.MacroEnabledWorkbook, and SpreadsheetDocumentType.MacroEnabledTemplate."); + } + if (string.IsNullOrEmpty(path)) { return false; } + if (path.IndexOfAny(System.IO.Path.GetInvalidPathChars()) >= 0) + { + return false; + } + try { if (!File.Exists(path)) @@ -321,12 +354,7 @@ public static bool IsValidDocument(string path, SpreadsheetDocumentType document break; case ".XLAM": - if (documentType != SpreadsheetDocumentType.AddIn) - { - return false; - } - - break; + throw new FileFormatException($"Validation for SpreadsheetDocument.AddIn (.xlam) is not supported."); default: return false; } diff --git a/src/DocumentFormat.OpenXml/Packaging/WordprocessingDocument.cs b/src/DocumentFormat.OpenXml/Packaging/WordprocessingDocument.cs index 94da4f21d..5ad3df091 100644 --- a/src/DocumentFormat.OpenXml/Packaging/WordprocessingDocument.cs +++ b/src/DocumentFormat.OpenXml/Packaging/WordprocessingDocument.cs @@ -311,18 +311,41 @@ public static WordprocessingDocument Open(Package package) => Open(package, new OpenSettings()); /// - /// Validates whether the specified file is a valid WordprocessingDocument of the given type. + /// Validates whether the specified file is a minimum valid WordprocessingDocument. /// /// The path to the WordprocessingDocument file. - /// The expected type of the WordprocessingDocument. Defaults to . - /// True if the file is a valid WordprocessingDocument of the specified type; otherwise, false. - public static bool IsValidDocument(string path, WordprocessingDocumentType documentType = WordprocessingDocumentType.Document) + /// + /// The expected type of the WordprocessingDocument. Defaults to . + /// Supported types are: + /// + /// (.docx) + /// (.dotx) + /// (.docm) + /// (.dotm) + /// + /// + /// + /// true if the file is a minimum valid WordprocessingDocument; otherwise, false. + /// + /// + /// A minimum valid WordprocessingDocument must meet the following criteria: + /// + /// The file must exist and have a valid extension matching the . + /// The file must contain a element in the main document part. + /// + /// + public static bool IsMinimumDocument(string path, WordprocessingDocumentType documentType = WordprocessingDocumentType.Document) { if (string.IsNullOrEmpty(path)) { return false; } + if (path.IndexOfAny(System.IO.Path.GetInvalidPathChars()) >= 0) + { + return false; + } + try { if (!File.Exists(path)) diff --git a/test/DocumentFormat.OpenXml.Packaging.Tests/OpenXmlPackageTests.cs b/test/DocumentFormat.OpenXml.Packaging.Tests/OpenXmlPackageTests.cs index 3a87091e3..34066e204 100644 --- a/test/DocumentFormat.OpenXml.Packaging.Tests/OpenXmlPackageTests.cs +++ b/test/DocumentFormat.OpenXml.Packaging.Tests/OpenXmlPackageTests.cs @@ -331,172 +331,235 @@ public void SucceedWithMissingCalcChainPart() Assert.NotNull(spd); } + #region WordprocessingDocument.IsMinimumDocument tests [Fact] - public void WordprocessingDocument_IsValidDocument_ShouldReturnFalse_WhenPathIsNull() + public void WordprocessingDocument_IsMinimumDocument_ValidDocx_ReturnsTrue() { + // Arrange + string filePath = Path.Combine(Path.GetTempPath(), "valid.docx"); + + using (var wordDocument = WordprocessingDocument.Create(filePath, WordprocessingDocumentType.Document)) + { + var mainPart = wordDocument.AddMainDocumentPart(); + mainPart.Document = new Document(new Body()); + } + // Act - bool result = WordprocessingDocument.IsValidDocument(null); + bool result = WordprocessingDocument.IsMinimumDocument(filePath, WordprocessingDocumentType.Document); // Assert - Assert.False(result); + Assert.True(result); + + // Cleanup + File.Delete(filePath); + } + + [Fact] + public void WordprocessingDocument_IsMinimumDocument_ValidDotx_ReturnsTrue() + { + // Arrange + string filePath = Path.Combine(Path.GetTempPath(), "valid.dotx"); + + using (var wordDocument = WordprocessingDocument.Create(filePath, WordprocessingDocumentType.Document)) + { + var mainPart = wordDocument.AddMainDocumentPart(); + mainPart.Document = new Document(new Body()); + } + + // Act + bool result = WordprocessingDocument.IsMinimumDocument(filePath, WordprocessingDocumentType.Template); + + // Assert + Assert.True(result); + + // Cleanup + File.Delete(filePath); } [Fact] - public void WordprocessingDocument_IsValidDocument_ShouldReturnFalse_WhenFileDoesNotExist() + public void WordprocessingDocument_IsMinimumDocument_InvalidExtension_ReturnsFalse() { // Arrange - string nonExistentPath = string.Concat(Path.GetTempPath(), "nonexistent.docx"); + string filePath = Path.Combine(Path.GetTempPath(), "invalid-ext.txt"); + + File.WriteAllText(filePath, string.Empty); // Act - bool result = WordprocessingDocument.IsValidDocument(nonExistentPath); + bool result = WordprocessingDocument.IsMinimumDocument(filePath, WordprocessingDocumentType.Document); // Assert Assert.False(result); + + // Cleanup + File.Delete(filePath); } [Fact] - public void WordprocessingDocument_IsValidDocument_ShouldReturnFalse_WhenFileExtensionIsUnsupported() + public void WordprocessingDocument_IsMinimumDocument_NonExistentFile_ReturnsFalse() { // Arrange - string unsupportedFilePath = string.Concat(Path.GetTempPath(), "unsupported.txt"); - File.WriteAllText(unsupportedFilePath, "Test content"); + string filePath = Path.Combine(Path.GetTempPath(), "nonexistent.docx"); - try - { - // Act - bool result = WordprocessingDocument.IsValidDocument(unsupportedFilePath); + // Act + bool result = WordprocessingDocument.IsMinimumDocument(filePath, WordprocessingDocumentType.Document); - // Assert - Assert.False(result); - } - finally - { - File.Delete(unsupportedFilePath); - } + // Assert + Assert.False(result); } [Fact] - public void WordprocessingDocument_IsValidDocument_ShouldReturnFalse_WhenDocumentTypeDoesNotMatchExtension() + public void WordprocessingDocument_IsMinimumDocument_InvalidPathCharacters_ReturnsFalse() { // Arrange - string filePath = string.Concat(Path.GetTempPath(), "test.docx"); - File.WriteAllText(filePath, "Test content"); + string filePath = "invalid|path.docx"; - try - { - // Act - bool result = WordprocessingDocument.IsValidDocument(filePath, WordprocessingDocumentType.Template); + // Act + bool result = WordprocessingDocument.IsMinimumDocument(filePath, WordprocessingDocumentType.Document); - // Assert - Assert.False(result); - } - finally - { - File.Delete(filePath); - } + // Assert + Assert.False(result); + } + + [Fact] + public void WordprocessingDocument_IsMinimumDocument_EmptyPath_ReturnsFalse() + { + // Act + bool result = WordprocessingDocument.IsMinimumDocument(string.Empty, WordprocessingDocumentType.Document); + + // Assert + Assert.False(result); } [Fact] - public void WordprocessingDocument_IsValidDocument_ShouldReturnTrue_ForValidDocument() + public void WordprocessingDocument_IsMinimumDocument_NullPath_ReturnsFalse() + { + // Act + bool result = WordprocessingDocument.IsMinimumDocument(null, WordprocessingDocumentType.Document); + + // Assert + Assert.False(result); + } + + [Fact] + public void WordprocessingDocument_IsMinimumDocument_InvalidContent_ReturnsFalse() { // Arrange - string filePath = string.Concat(Path.GetTempPath(), "test.docx"); + string filePath = Path.Combine(Path.GetTempPath(), "invalid.docx"); - using (WordprocessingDocument document = WordprocessingDocument.Create(filePath, WordprocessingDocumentType.Document)) + using (var wordDocument = WordprocessingDocument.Create(filePath, WordprocessingDocumentType.Document)) { - document.AddMainDocumentPart(); - document.MainDocumentPart.Document = new Document(new Body()); + var mainPart = wordDocument.AddMainDocumentPart(); + mainPart.Document = new Document(); } - try - { - // Act - bool result = WordprocessingDocument.IsValidDocument(filePath); + // Act + bool result = WordprocessingDocument.IsMinimumDocument(filePath, WordprocessingDocumentType.Document); - // Assert - Assert.True(result); - } - finally - { - File.Delete(filePath); - } + // Assert + Assert.False(result); + + // Cleanup + File.Delete(filePath); } + #endregion + #region SpreadsheetDocument.IsMinimumDocument tests [Fact] - public void WordprocessingDocument_IsValidDocument_ShouldReturnFalse_WhenDocumentIsCorrupted() + public void IsMinimumDocument_ValidXlsx_ReturnsTrue() { // Arrange - string corruptedFilePath = string.Concat(Path.GetTempPath(), "corrupt.docx"); - using (WordprocessingDocument wordprocessingDocument = WordprocessingDocument.Create(corruptedFilePath, WordprocessingDocumentType.Document)) - { - MainDocumentPart mainDocumentPart = wordprocessingDocument.AddMainDocumentPart(); + string filePath = Path.Combine(Path.GetTempPath(), "valid.xlsx"); - mainDocumentPart.Document = new Document(new Paragraph()); + using (SpreadsheetDocument spreadsheetDocument = SpreadsheetDocument.Create(filePath, SpreadsheetDocumentType.Workbook)) + { + WorkbookPart wbp = spreadsheetDocument.AddWorkbookPart(); + WorksheetPart wsp = wbp.AddNewPart(); + wbp.Workbook = new Spreadsheet.Workbook(new Spreadsheet.Sheets(new Spreadsheet.Sheet() { Id = wbp.GetIdOfPart(wsp), SheetId = 1, Name = "Sheet1" })); + wsp.Worksheet = new Spreadsheet.Worksheet(new Spreadsheet.SheetData()); } - try - { - // Act - bool result = WordprocessingDocument.IsValidDocument(corruptedFilePath); + // Act + bool result = SpreadsheetDocument.IsMinimumDocument(filePath, SpreadsheetDocumentType.Workbook); - // Assert - Assert.False(result); - } - finally + // Assert + Assert.True(result); + + // Cleanup + File.Delete(filePath); + } + + [Fact] + public void IsMinimumDocument_ValidXltx_ReturnsTrue() + { + // Arrange + string filePath = Path.Combine(Path.GetTempPath(), "valid.xltx"); + + using (SpreadsheetDocument spreadsheetDocument = SpreadsheetDocument.Create(filePath, SpreadsheetDocumentType.Template)) { - File.Delete(corruptedFilePath); + WorkbookPart wbp = spreadsheetDocument.AddWorkbookPart(); + WorksheetPart wsp = wbp.AddNewPart(); + wbp.Workbook = new Spreadsheet.Workbook(new Spreadsheet.Sheets(new Spreadsheet.Sheet() { Id = wbp.GetIdOfPart(wsp), SheetId = 1, Name = "Sheet1" })); + wsp.Worksheet = new Spreadsheet.Worksheet(new Spreadsheet.SheetData()); } + + // Act + bool result = SpreadsheetDocument.IsMinimumDocument(filePath, SpreadsheetDocumentType.Template); + + // Assert + Assert.True(result); + + // Cleanup + File.Delete(filePath); } [Fact] - public void SpreadsheetDocument_IsValidDocument_ShouldReturnFalse_WhenPathIsNull() + public void SpreadsheetDocument_IsMinimumDocument_InvalidExtension_ReturnsFalse() { + // Arrange + string filePath = Path.Combine(Path.GetTempPath(), string.Concat(Path.GetTempFileName(), ".txt")); + File.WriteAllText(filePath, string.Empty); + // Act - bool result = SpreadsheetDocument.IsValidDocument(null); + bool result = SpreadsheetDocument.IsMinimumDocument(filePath, SpreadsheetDocumentType.Workbook); // Assert Assert.False(result); + + // Cleanup + File.Delete(filePath); } [Fact] - public void SpreadsheetDocument_IsValidDocument_ShouldReturnFalse_WhenFileDoesNotExist() + public void SpreadsheetDocument_IsMinimumDocument_NonExistentFile_ReturnsFalse() { // Arrange - string nonExistentPath = string.Concat(Path.GetTempPath(), "nonexistent.docx"); + string filePath = "nonexistent.xlsx"; // Act - bool result = SpreadsheetDocument.IsValidDocument(nonExistentPath); + bool result = SpreadsheetDocument.IsMinimumDocument(filePath, SpreadsheetDocumentType.Workbook); // Assert Assert.False(result); } [Fact] - public void SpreadsheetDocument_IsValidDocument_ShouldReturnFalse_WhenFileExtensionIsUnsupported() + public void SpreadsheetDocument_IsMinimumDocument_InvalidPathCharacters_ReturnsFalse() { // Arrange - string unsupportedFilePath = Path.GetTempFileName(); - File.WriteAllText(unsupportedFilePath, "Test content"); + string filePath = "invalid|path.xlsx"; - try - { - // Act - bool result = SpreadsheetDocument.IsValidDocument(unsupportedFilePath); + // Act + bool result = SpreadsheetDocument.IsMinimumDocument(filePath, SpreadsheetDocumentType.Workbook); - // Assert - Assert.False(result); - } - finally - { - File.Delete(unsupportedFilePath); - } + // Assert + Assert.False(result); } [Fact] - public void SpreadsheetDocument_IsValidDocument_ShouldReturnFalse_WhenDocumentTypeDoesNotMatchExtension() + public void SpreadsheetDocument_IsMinimumDocument_UnsupportedDocumentType_ThrowsArgumentException() { // Arrange - string filePath = Path.Combine(Path.GetTempPath(), "test.xlsx"); + string filePath = Path.Combine(Path.GetTempPath(), "valid.xlsx"); using (SpreadsheetDocument spreadsheetDocument = SpreadsheetDocument.Create(filePath, SpreadsheetDocumentType.Workbook)) { @@ -506,80 +569,64 @@ public void SpreadsheetDocument_IsValidDocument_ShouldReturnFalse_WhenDocumentTy wsp.Worksheet = new Spreadsheet.Worksheet(new Spreadsheet.SheetData()); } - try - { - // Act - bool result = SpreadsheetDocument.IsValidDocument(filePath, SpreadsheetDocumentType.Template); + // Act & Assert + Assert.Throws(() => + SpreadsheetDocument.IsMinimumDocument(filePath, SpreadsheetDocumentType.AddIn)); - // Assert - Assert.False(result); - } - finally - { - File.Delete(filePath); - } + // Cleanup + File.Delete(filePath); } [Fact] - public void SpreadsheetDocument_IsValidDocument_ShouldReturnTrue_ForValidDocument() + public void SpreadsheetDocument_IsMinimumDocument_EmptyPath_ReturnsFalse() { - // Arrange - string filePath = Path.Combine(Path.GetTempPath(), "valid.xlsx"); + // Act + bool result = SpreadsheetDocument.IsMinimumDocument(string.Empty, SpreadsheetDocumentType.Workbook); - using (SpreadsheetDocument spreadsheetDocument = SpreadsheetDocument.Create(filePath, SpreadsheetDocumentType.Workbook)) - { - WorkbookPart wbp = spreadsheetDocument.AddWorkbookPart(); - WorksheetPart wsp = wbp.AddNewPart(); - wbp.Workbook = new Spreadsheet.Workbook(new Spreadsheet.Sheets(new Spreadsheet.Sheet() { Id = wbp.GetIdOfPart(wsp), SheetId = 1, Name = "Sheet1" })); - wsp.Worksheet = new Spreadsheet.Worksheet(new Spreadsheet.SheetData()); - } + // Assert + Assert.False(result); + } - try - { - // Act - bool result = SpreadsheetDocument.IsValidDocument(filePath); + [Fact] + public void SpreadsheetDocument_IsMinimumDocument_NullPath_ReturnsFalse() + { + // Act + bool result = SpreadsheetDocument.IsMinimumDocument(null, SpreadsheetDocumentType.Workbook); - // Assert - Assert.True(result); - } - finally - { - File.Delete(filePath); - } + // Assert + Assert.False(result); } [Fact] - public void SpreadsheetDocument_IsValidDocument_ShouldReturnFalse_WhenDocumentIsInvalid() + public void SpreadsheetDocument_IsMinimumDocument_InvalidContent_ReturnsFalse() { // Arrange string filePath = Path.Combine(Path.GetTempPath(), "invalid.xlsx"); - using (SpreadsheetDocument invalidSpreadsheetDocument = SpreadsheetDocument.Create(filePath, SpreadsheetDocumentType.Workbook)) + using (SpreadsheetDocument spreadsheetDocument = SpreadsheetDocument.Create(filePath, SpreadsheetDocumentType.Workbook)) { - WorkbookPart wbp = invalidSpreadsheetDocument.AddWorkbookPart(); + WorkbookPart wbp = spreadsheetDocument.AddWorkbookPart(); WorksheetPart wsp = wbp.AddNewPart(); + wbp.Workbook = new Spreadsheet.Workbook(new Spreadsheet.Sheets(new Spreadsheet.Sheet() { Id = wbp.GetIdOfPart(wsp), SheetId = 1, Name = "Sheet1" })); } - try - { - // Act - bool result = SpreadsheetDocument.IsValidDocument(filePath); + // Act + bool result = SpreadsheetDocument.IsMinimumDocument(filePath, SpreadsheetDocumentType.Workbook); - // Assert - Assert.False(result); - } - finally - { - File.Delete(filePath); - } + // Assert + Assert.False(result); + + // Cleanup + File.Delete(filePath); } + #endregion -#region PresentationDocument.IsMinimumDocument tests + #region PresentationDocument.IsMinimumDocument tests [Fact] public void IsMinimumDocument_ValidPptx_ReturnsTrue() { // Arrange - string filePath = Path.Combine(Path.GetTempPath(), "invalid.pptx"); + string filePath = Path.Combine(Path.GetTempPath(), "valid.pptx"); using (PresentationDocument presentationDocument = PresentationDocument.Create(filePath, PresentationDocumentType.Presentation)) { @@ -622,7 +669,7 @@ public void IsMinimumDocument_ValidPotx_ReturnsTrue() } [Fact] - public void IsMinimumDocument_InvalidExtension_ReturnsFalse() + public void PresentationDocument_IsMinimumDocument_InvalidExtension_ReturnsFalse() { // Arrange string filePath = Path.Combine(Path.GetTempPath(), "invalid.txt"); @@ -640,7 +687,7 @@ public void IsMinimumDocument_InvalidExtension_ReturnsFalse() } [Fact] - public void IsMinimumDocument_NonExistentFile_ReturnsFalse() + public void PresentationDocument_IsMinimumDocument_NonExistentFile_ReturnsFalse() { // Arrange string filePath = "nonexistent.pptx"; @@ -653,7 +700,7 @@ public void IsMinimumDocument_NonExistentFile_ReturnsFalse() } [Fact] - public void IsMinimumDocument_InvalidPathCharacters_ReturnsFalse() + public void PresentationDocument_IsMinimumDocument_InvalidPathCharacters_ReturnsFalse() { // Arrange string filePath = "invalid|path.pptx"; @@ -666,7 +713,7 @@ public void IsMinimumDocument_InvalidPathCharacters_ReturnsFalse() } [Fact] - public void IsMinimumDocument_UnsupportedDocumentType_ThrowsArgumentException() + public void PresentationDocument_IsMinimumDocument_UnsupportedDocumentType_ThrowsArgumentException() { // Arrange string filePath = Path.Combine(Path.GetTempPath(), "invalid.pptx"); @@ -687,7 +734,7 @@ public void IsMinimumDocument_UnsupportedDocumentType_ThrowsArgumentException() } [Fact] - public void IsMinimumDocument_EmptyPath_ReturnsFalse() + public void PresentationDocument_IsMinimumDocument_EmptyPath_ReturnsFalse() { // Act bool result = PresentationDocument.IsMinimumDocument(string.Empty, PresentationDocumentType.Presentation); @@ -697,7 +744,7 @@ public void IsMinimumDocument_EmptyPath_ReturnsFalse() } [Fact] - public void IsMinimumDocument_NullPath_ReturnsFalse() + public void PresentationDocument_IsMinimumDocument_NullPath_ReturnsFalse() { // Act bool result = PresentationDocument.IsMinimumDocument(null, PresentationDocumentType.Presentation); @@ -707,7 +754,7 @@ public void IsMinimumDocument_NullPath_ReturnsFalse() } [Fact] - public void IsMinimumDocument_InvalidContent_ReturnsFalse() + public void PresentationDocument_IsMinimumDocument_InvalidContent_ReturnsFalse() { // Arrange string filePath = Path.Combine(Path.GetTempPath(), "invalid.pptx"); From 48afb54d9d1f959bf14f461a33ddd5fedabf9eea Mon Sep 17 00:00:00 2001 From: Michael Bowen <10384982+mikeebowen@users.noreply.github.com> Date: Tue, 29 Apr 2025 10:04:31 -0700 Subject: [PATCH 05/33] only test null argument for < .Net 8+ --- .../OpenXmlPackageTests.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/DocumentFormat.OpenXml.Packaging.Tests/OpenXmlPackageTests.cs b/test/DocumentFormat.OpenXml.Packaging.Tests/OpenXmlPackageTests.cs index 6effd5b0f..9cb02159f 100644 --- a/test/DocumentFormat.OpenXml.Packaging.Tests/OpenXmlPackageTests.cs +++ b/test/DocumentFormat.OpenXml.Packaging.Tests/OpenXmlPackageTests.cs @@ -442,6 +442,7 @@ public void WordprocessingDocument_IsMinimumDocument_EmptyPath_ReturnsFalse() Assert.False(result); } +#if !NET8_0_OR_GREATER [Fact] public void WordprocessingDocument_IsMinimumDocument_NullPath_ReturnsFalse() { @@ -451,6 +452,7 @@ public void WordprocessingDocument_IsMinimumDocument_NullPath_ReturnsFalse() // Assert Assert.False(result); } +#endif [Fact] public void WordprocessingDocument_IsMinimumDocument_InvalidContent_ReturnsFalse() @@ -599,6 +601,7 @@ public void SpreadsheetDocument_IsMinimumDocument_EmptyPath_ReturnsFalse() Assert.False(result); } +#if !NET8_0_OR_GREATER [Fact] public void SpreadsheetDocument_IsMinimumDocument_NullPath_ReturnsFalse() { @@ -608,6 +611,7 @@ public void SpreadsheetDocument_IsMinimumDocument_NullPath_ReturnsFalse() // Assert Assert.False(result); } +#endif [Fact] public void SpreadsheetDocument_IsMinimumDocument_InvalidContent_ReturnsFalse() @@ -755,6 +759,7 @@ public void PresentationDocument_IsMinimumDocument_EmptyPath_ReturnsFalse() Assert.False(result); } +#if !NET8_0_OR_GREATER [Fact] public void PresentationDocument_IsMinimumDocument_NullPath_ReturnsFalse() { @@ -764,6 +769,7 @@ public void PresentationDocument_IsMinimumDocument_NullPath_ReturnsFalse() // Assert Assert.False(result); } +#endif [Fact] public void PresentationDocument_IsMinimumDocument_InvalidContent_ReturnsFalse() From f83d1d0f4f2bd296a426d98376d91ed7c7c91136 Mon Sep 17 00:00:00 2001 From: Michael Bowen <10384982+mikeebowen@users.noreply.github.com> Date: Tue, 29 Apr 2025 16:18:40 -0700 Subject: [PATCH 06/33] move Wordprocessing minimum document validation to virtual method --- .../Packaging/OpenSettings.cs | 12 + .../Packaging/OpenXmlPackage.cs | 27 ++ .../PublicAPI/PublicAPI.Shipped.txt | 2 + .../Packaging/WordprocessingDocument.cs | 135 ++++-- .../OpenXmlPackageTests.cs | 451 ------------------ 5 files changed, 124 insertions(+), 503 deletions(-) diff --git a/src/DocumentFormat.OpenXml.Framework/Packaging/OpenSettings.cs b/src/DocumentFormat.OpenXml.Framework/Packaging/OpenSettings.cs index 8e637b432..791e5ed68 100644 --- a/src/DocumentFormat.OpenXml.Framework/Packaging/OpenSettings.cs +++ b/src/DocumentFormat.OpenXml.Framework/Packaging/OpenSettings.cs @@ -74,5 +74,17 @@ public MarkupCompatibilityProcessSettings MarkupCompatibilityProcessSettings /// This property allows you to mitigate denial of service attacks where the attacker submits a package with an extremely large Open XML part. By limiting the size of the part, you can detect the attack and recover reliably. /// public long MaxCharactersInPart { get; set; } + + /// + /// Gets or sets a value indicating whether to validate that the document meets the minimum requirements for a valid package. + /// + /// + /// true if the document should be validated for minimum requirements; otherwise, false. + /// + /// + /// When set to true, the document will be checked to ensure it contains the necessary parts and structure + /// to be considered a valid Open XML package. + /// + public bool CheckMinimumPackage { get; set; } } } diff --git a/src/DocumentFormat.OpenXml.Framework/Packaging/OpenXmlPackage.cs b/src/DocumentFormat.OpenXml.Framework/Packaging/OpenXmlPackage.cs index c8ce6524a..2a63f0d08 100644 --- a/src/DocumentFormat.OpenXml.Framework/Packaging/OpenXmlPackage.cs +++ b/src/DocumentFormat.OpenXml.Framework/Packaging/OpenXmlPackage.cs @@ -105,6 +105,33 @@ internal void LoadAllParts() } } + /// + /// Determines whether the specified meets the minimum requirements for a valid package. + /// + /// The to validate. + /// + /// true if the package meets the minimum requirements; otherwise, false. + /// + /// + /// This method is intended to be overridden by derived classes to implement specific validation logic + /// for different types of Open XML packages (e.g., Wordprocessing, Spreadsheet, or Presentation documents). + /// + protected virtual bool IsMinimumPackage(OpenXmlPackage package) => false; + + /// + /// Determines whether the specified file path is valid for the given document type. + /// + /// The file path to validate. + /// The document type to validate against. + /// + /// true if the file path is valid for the specified document type; otherwise, false. + /// + /// + /// This method is intended to be overridden by derived classes to implement specific validation logic + /// for different types of Open XML documents (e.g., Wordprocessing, Spreadsheet, or Presentation documents). + /// + protected virtual bool IsValidDocumentPath(string path, Enum type) => false; + #region public methods /// diff --git a/src/DocumentFormat.OpenXml.Framework/PublicAPI/PublicAPI.Shipped.txt b/src/DocumentFormat.OpenXml.Framework/PublicAPI/PublicAPI.Shipped.txt index c7d7da820..7827e319d 100644 --- a/src/DocumentFormat.OpenXml.Framework/PublicAPI/PublicAPI.Shipped.txt +++ b/src/DocumentFormat.OpenXml.Framework/PublicAPI/PublicAPI.Shipped.txt @@ -1009,3 +1009,5 @@ DocumentFormat.OpenXml.OpenXmlPartWriterSettings.Encoding.set -> void DocumentFormat.OpenXml.OpenXmlPartWriterSettings.OpenXmlPartWriterSettings() -> void DocumentFormat.OpenXml.OpenXmlPartWriter.OpenXmlPartWriter(DocumentFormat.OpenXml.Packaging.OpenXmlPart! openXmlPart, DocumentFormat.OpenXml.OpenXmlPartWriterSettings! settings) -> void DocumentFormat.OpenXml.OpenXmlPartWriter.OpenXmlPartWriter(System.IO.Stream! partStream, DocumentFormat.OpenXml.OpenXmlPartWriterSettings! settings) -> void +DocumentFormat.OpenXml.Packaging.OpenSettings.CheckMinimumPackage.get -> bool +DocumentFormat.OpenXml.Packaging.OpenSettings.CheckMinimumPackage.set -> void diff --git a/src/DocumentFormat.OpenXml/Packaging/WordprocessingDocument.cs b/src/DocumentFormat.OpenXml/Packaging/WordprocessingDocument.cs index 6e7e5a395..5c3ad2cab 100644 --- a/src/DocumentFormat.OpenXml/Packaging/WordprocessingDocument.cs +++ b/src/DocumentFormat.OpenXml/Packaging/WordprocessingDocument.cs @@ -264,6 +264,24 @@ public static WordprocessingDocument Open(Stream stream, bool isEditable) public static WordprocessingDocument Open(string path, bool isEditable, OpenSettings openSettings) => CreateDefaultBuilder() .UseSettings(openSettings) + .Use(package => + { + if (openSettings.CheckMinimumPackage) + { + bool isValidPath = package.IsValidDocumentPath(path, package.DocumentType); + bool isMinimumDocument = package.IsMinimumPackage(package); + + if (!isValidPath) + { + throw new ArgumentException($"The provided path is invalid. {nameof(path)}"); + } + + if (!isMinimumDocument) + { + throw new FileFormatException($"The provided package does not conform to the minimum requirements for Office to open. {nameof(path)}"); + } + } + }) .Build() .Open(path, isEditable); @@ -312,30 +330,39 @@ public static WordprocessingDocument Open(Package package) => Open(package, new OpenSettings()); /// - /// Validates whether the specified file is a minimum valid WordprocessingDocument. + /// Determines whether the specified meets the minimum requirements for a valid WordprocessingDocument package. /// - /// The path to the WordprocessingDocument file. - /// - /// The expected type of the WordprocessingDocument. Defaults to . - /// Supported types are: - /// - /// (.docx) - /// (.dotx) - /// (.docm) - /// (.dotm) - /// - /// + /// The to validate. /// - /// true if the file is a minimum valid WordprocessingDocument; otherwise, false. + /// true if the package meets the minimum requirements for a WordprocessingDocument; otherwise, false. /// /// - /// A minimum valid WordprocessingDocument must meet the following criteria: - /// - /// The file must exist and have a valid extension matching the . - /// The file must contain a element in the main document part. - /// + /// This method checks whether the provided package is a valid and contains a + /// with a that has a element. /// - public static bool IsMinimumDocument(string path, WordprocessingDocumentType documentType = WordprocessingDocumentType.Document) + protected override bool IsMinimumPackage(OpenXmlPackage package) + { + if (package is WordprocessingDocument wordprocessingDocument) + { + return wordprocessingDocument.MainDocumentPart?.Document?.Body is not null; + } + + return false; + } + + /// + /// Determines whether the specified file path is valid for the given WordprocessingDocument type. + /// + /// The file path to validate. + /// The document type to validate against, such as . + /// + /// true if the file path is valid for the specified document type; otherwise, false. + /// + /// + /// This method checks the file path for validity, including ensuring the file exists, the path is well-formed, + /// and the file extension matches the expected type for the given . + /// + protected override bool IsValidDocumentPath(string path, Enum type) { if (string.IsNullOrEmpty(path)) { @@ -354,46 +381,50 @@ public static bool IsMinimumDocument(string path, WordprocessingDocumentType doc return false; } - string ext = new FileInfo(path).Extension.ToUpperInvariant(); - - switch (ext) + if (type is WordprocessingDocumentType) { - case ".DOCX": - if (documentType != WordprocessingDocumentType.Document) - { - return false; - } - - break; - case ".DOTX": - if (documentType != WordprocessingDocumentType.Template) - { - return false; - } + WordprocessingDocumentType documentType = (WordprocessingDocumentType)type; - break; - case ".DOCM": - if (documentType != WordprocessingDocumentType.MacroEnabledDocument) - { - return false; - } + string ext = new FileInfo(path).Extension.ToUpperInvariant(); - break; - case ".DOTM": - if (documentType != WordprocessingDocumentType.MacroEnabledTemplate) - { + switch (ext) + { + case ".DOCX": + if (documentType != WordprocessingDocumentType.Document) + { + return false; + } + + break; + case ".DOTX": + if (documentType != WordprocessingDocumentType.Template) + { + return false; + } + + break; + case ".DOCM": + if (documentType != WordprocessingDocumentType.MacroEnabledDocument) + { + return false; + } + + break; + case ".DOTM": + if (documentType != WordprocessingDocumentType.MacroEnabledTemplate) + { + return false; + } + + break; + default: return false; - } + } - break; - default: - return false; + return true; } - using (WordprocessingDocument wordprocessingDocument = Open(path, false)) - { - return wordprocessingDocument?.MainDocumentPart?.Document?.Body is not null; - } + return false; } catch { diff --git a/test/DocumentFormat.OpenXml.Packaging.Tests/OpenXmlPackageTests.cs b/test/DocumentFormat.OpenXml.Packaging.Tests/OpenXmlPackageTests.cs index 9cb02159f..3bb51863d 100644 --- a/test/DocumentFormat.OpenXml.Packaging.Tests/OpenXmlPackageTests.cs +++ b/test/DocumentFormat.OpenXml.Packaging.Tests/OpenXmlPackageTests.cs @@ -342,456 +342,5 @@ public void SucceedWithMissingCalcChainPart() Assert.NotNull(spd); } - - #region WordprocessingDocument.IsMinimumDocument tests - [Fact] - public void WordprocessingDocument_IsMinimumDocument_ValidDocx_ReturnsTrue() - { - // Arrange - string filePath = Path.Combine(Path.GetTempPath(), "valid.docx"); - - using (var wordDocument = WordprocessingDocument.Create(filePath, WordprocessingDocumentType.Document)) - { - var mainPart = wordDocument.AddMainDocumentPart(); - mainPart.Document = new Document(new Body()); - } - - // Act - bool result = WordprocessingDocument.IsMinimumDocument(filePath, WordprocessingDocumentType.Document); - - // Assert - Assert.True(result); - - // Cleanup - File.Delete(filePath); - } - - [Fact] - public void WordprocessingDocument_IsMinimumDocument_ValidDotx_ReturnsTrue() - { - // Arrange - string filePath = Path.Combine(Path.GetTempPath(), "valid.dotx"); - - using (var wordDocument = WordprocessingDocument.Create(filePath, WordprocessingDocumentType.Document)) - { - var mainPart = wordDocument.AddMainDocumentPart(); - mainPart.Document = new Document(new Body()); - } - - // Act - bool result = WordprocessingDocument.IsMinimumDocument(filePath, WordprocessingDocumentType.Template); - - // Assert - Assert.True(result); - - // Cleanup - File.Delete(filePath); - } - - [Fact] - public void WordprocessingDocument_IsMinimumDocument_InvalidExtension_ReturnsFalse() - { - // Arrange - string filePath = Path.Combine(Path.GetTempPath(), "invalid-ext.txt"); - - File.WriteAllText(filePath, string.Empty); - - // Act - bool result = WordprocessingDocument.IsMinimumDocument(filePath, WordprocessingDocumentType.Document); - - // Assert - Assert.False(result); - - // Cleanup - File.Delete(filePath); - } - - [Fact] - public void WordprocessingDocument_IsMinimumDocument_NonExistentFile_ReturnsFalse() - { - // Arrange - string filePath = Path.Combine(Path.GetTempPath(), "nonexistent.docx"); - - // Act - bool result = WordprocessingDocument.IsMinimumDocument(filePath, WordprocessingDocumentType.Document); - - // Assert - Assert.False(result); - } - - [Fact] - public void WordprocessingDocument_IsMinimumDocument_InvalidPathCharacters_ReturnsFalse() - { - // Arrange - string filePath = "invalid|path.docx"; - - // Act - bool result = WordprocessingDocument.IsMinimumDocument(filePath, WordprocessingDocumentType.Document); - - // Assert - Assert.False(result); - } - - [Fact] - public void WordprocessingDocument_IsMinimumDocument_EmptyPath_ReturnsFalse() - { - // Act - bool result = WordprocessingDocument.IsMinimumDocument(string.Empty, WordprocessingDocumentType.Document); - - // Assert - Assert.False(result); - } - -#if !NET8_0_OR_GREATER - [Fact] - public void WordprocessingDocument_IsMinimumDocument_NullPath_ReturnsFalse() - { - // Act - bool result = WordprocessingDocument.IsMinimumDocument(null, WordprocessingDocumentType.Document); - - // Assert - Assert.False(result); - } -#endif - - [Fact] - public void WordprocessingDocument_IsMinimumDocument_InvalidContent_ReturnsFalse() - { - // Arrange - string filePath = Path.Combine(Path.GetTempPath(), "invalid.docx"); - - using (var wordDocument = WordprocessingDocument.Create(filePath, WordprocessingDocumentType.Document)) - { - var mainPart = wordDocument.AddMainDocumentPart(); - mainPart.Document = new Document(); - } - - // Act - bool result = WordprocessingDocument.IsMinimumDocument(filePath, WordprocessingDocumentType.Document); - - // Assert - Assert.False(result); - - // Cleanup - File.Delete(filePath); - } - #endregion - - #region SpreadsheetDocument.IsMinimumDocument tests - [Fact] - public void IsMinimumDocument_ValidXlsx_ReturnsTrue() - { - // Arrange - string filePath = Path.Combine(Path.GetTempPath(), "valid.xlsx"); - - using (SpreadsheetDocument spreadsheetDocument = SpreadsheetDocument.Create(filePath, SpreadsheetDocumentType.Workbook)) - { - WorkbookPart wbp = spreadsheetDocument.AddWorkbookPart(); - WorksheetPart wsp = wbp.AddNewPart(); - wbp.Workbook = new Spreadsheet.Workbook(new Spreadsheet.Sheets(new Spreadsheet.Sheet() { Id = wbp.GetIdOfPart(wsp), SheetId = 1, Name = "Sheet1" })); - wsp.Worksheet = new Spreadsheet.Worksheet(new Spreadsheet.SheetData()); - } - - // Act - bool result = SpreadsheetDocument.IsMinimumDocument(filePath, SpreadsheetDocumentType.Workbook); - - // Assert - Assert.True(result); - - // Cleanup - File.Delete(filePath); - } - - [Fact] - public void IsMinimumDocument_ValidXltx_ReturnsTrue() - { - // Arrange - string filePath = Path.Combine(Path.GetTempPath(), "valid.xltx"); - - using (SpreadsheetDocument spreadsheetDocument = SpreadsheetDocument.Create(filePath, SpreadsheetDocumentType.Template)) - { - WorkbookPart wbp = spreadsheetDocument.AddWorkbookPart(); - WorksheetPart wsp = wbp.AddNewPart(); - wbp.Workbook = new Spreadsheet.Workbook(new Spreadsheet.Sheets(new Spreadsheet.Sheet() { Id = wbp.GetIdOfPart(wsp), SheetId = 1, Name = "Sheet1" })); - wsp.Worksheet = new Spreadsheet.Worksheet(new Spreadsheet.SheetData()); - } - - // Act - bool result = SpreadsheetDocument.IsMinimumDocument(filePath, SpreadsheetDocumentType.Template); - - // Assert - Assert.True(result); - - // Cleanup - File.Delete(filePath); - } - - [Fact] - public void SpreadsheetDocument_IsMinimumDocument_InvalidExtension_ReturnsFalse() - { - // Arrange - string filePath = Path.Combine(Path.GetTempPath(), string.Concat(Path.GetTempFileName(), ".txt")); - File.WriteAllText(filePath, string.Empty); - - // Act - bool result = SpreadsheetDocument.IsMinimumDocument(filePath, SpreadsheetDocumentType.Workbook); - - // Assert - Assert.False(result); - - // Cleanup - File.Delete(filePath); - } - - [Fact] - public void SpreadsheetDocument_IsMinimumDocument_NonExistentFile_ReturnsFalse() - { - // Arrange - string filePath = "nonexistent.xlsx"; - - // Act - bool result = SpreadsheetDocument.IsMinimumDocument(filePath, SpreadsheetDocumentType.Workbook); - - // Assert - Assert.False(result); - } - - [Fact] - public void SpreadsheetDocument_IsMinimumDocument_InvalidPathCharacters_ReturnsFalse() - { - // Arrange - string filePath = "invalid|path.xlsx"; - - // Act - bool result = SpreadsheetDocument.IsMinimumDocument(filePath, SpreadsheetDocumentType.Workbook); - - // Assert - Assert.False(result); - } - - [Fact] - public void SpreadsheetDocument_IsMinimumDocument_UnsupportedDocumentType_ThrowsArgumentException() - { - // Arrange - string filePath = Path.Combine(Path.GetTempPath(), "valid.xlsx"); - - using (SpreadsheetDocument spreadsheetDocument = SpreadsheetDocument.Create(filePath, SpreadsheetDocumentType.Workbook)) - { - WorkbookPart wbp = spreadsheetDocument.AddWorkbookPart(); - WorksheetPart wsp = wbp.AddNewPart(); - wbp.Workbook = new Spreadsheet.Workbook(new Spreadsheet.Sheets(new Spreadsheet.Sheet() { Id = wbp.GetIdOfPart(wsp), SheetId = 1, Name = "Sheet1" })); - wsp.Worksheet = new Spreadsheet.Worksheet(new Spreadsheet.SheetData()); - } - - // Act & Assert - Assert.Throws(() => - SpreadsheetDocument.IsMinimumDocument(filePath, SpreadsheetDocumentType.AddIn)); - - // Cleanup - File.Delete(filePath); - } - - [Fact] - public void SpreadsheetDocument_IsMinimumDocument_EmptyPath_ReturnsFalse() - { - // Act - bool result = SpreadsheetDocument.IsMinimumDocument(string.Empty, SpreadsheetDocumentType.Workbook); - - // Assert - Assert.False(result); - } - -#if !NET8_0_OR_GREATER - [Fact] - public void SpreadsheetDocument_IsMinimumDocument_NullPath_ReturnsFalse() - { - // Act - bool result = SpreadsheetDocument.IsMinimumDocument(null, SpreadsheetDocumentType.Workbook); - - // Assert - Assert.False(result); - } -#endif - - [Fact] - public void SpreadsheetDocument_IsMinimumDocument_InvalidContent_ReturnsFalse() - { - // Arrange - string filePath = Path.Combine(Path.GetTempPath(), "invalid.xlsx"); - - using (SpreadsheetDocument spreadsheetDocument = SpreadsheetDocument.Create(filePath, SpreadsheetDocumentType.Workbook)) - { - WorkbookPart wbp = spreadsheetDocument.AddWorkbookPart(); - WorksheetPart wsp = wbp.AddNewPart(); - wbp.Workbook = new Spreadsheet.Workbook(new Spreadsheet.Sheets(new Spreadsheet.Sheet() { Id = wbp.GetIdOfPart(wsp), SheetId = 1, Name = "Sheet1" })); - } - - // Act - bool result = SpreadsheetDocument.IsMinimumDocument(filePath, SpreadsheetDocumentType.Workbook); - - // Assert - Assert.False(result); - - // Cleanup - File.Delete(filePath); - } - #endregion - - #region PresentationDocument.IsMinimumDocument tests - [Fact] - public void IsMinimumDocument_ValidPptx_ReturnsTrue() - { - // Arrange - string filePath = Path.Combine(Path.GetTempPath(), "valid.pptx"); - - using (PresentationDocument presentationDocument = PresentationDocument.Create(filePath, PresentationDocumentType.Presentation)) - { - PresentationPart presentationPart = presentationDocument.AddPresentationPart(); - presentationPart.Presentation = new Presentation.Presentation(); - presentationPart.Presentation.AddChild(new NotesSize() { Cx = 913607, Cy = 913607 }); - } - - // Act - bool result = PresentationDocument.IsMinimumDocument(filePath, PresentationDocumentType.Presentation); - - // Assert - Assert.True(result); - - // Cleanup - File.Delete(filePath); - } - - [Fact] - public void IsMinimumDocument_ValidPotx_ReturnsTrue() - { - // Arrange - string filePath = Path.Combine(Path.GetTempPath(), "invalid.potx"); - - using (PresentationDocument presentationDocument = PresentationDocument.Create(filePath, PresentationDocumentType.Presentation)) - { - PresentationPart presentationPart = presentationDocument.AddPresentationPart(); - presentationPart.Presentation = new Presentation.Presentation(); - presentationPart.Presentation.AddChild(new NotesSize() { Cx = 913607, Cy = 913607 }); - } - - // Act - bool result = PresentationDocument.IsMinimumDocument(filePath, PresentationDocumentType.Template); - - // Assert - Assert.True(result); - - // Cleanup - File.Delete(filePath); - } - - [Fact] - public void PresentationDocument_IsMinimumDocument_InvalidExtension_ReturnsFalse() - { - // Arrange - string filePath = Path.Combine(Path.GetTempPath(), "invalid.txt"); - - File.WriteAllText(filePath, string.Empty); - - // Act - bool result = PresentationDocument.IsMinimumDocument(filePath, PresentationDocumentType.Presentation); - - // Assert - Assert.False(result); - - // Cleanup - File.Delete(filePath); - } - - [Fact] - public void PresentationDocument_IsMinimumDocument_NonExistentFile_ReturnsFalse() - { - // Arrange - string filePath = "nonexistent.pptx"; - - // Act - bool result = PresentationDocument.IsMinimumDocument(filePath, PresentationDocumentType.Presentation); - - // Assert - Assert.False(result); - } - - [Fact] - public void PresentationDocument_IsMinimumDocument_InvalidPathCharacters_ReturnsFalse() - { - // Arrange - string filePath = "invalid|path.pptx"; - - // Act - bool result = PresentationDocument.IsMinimumDocument(filePath, PresentationDocumentType.Presentation); - - // Assert - Assert.False(result); - } - - [Fact] - public void PresentationDocument_IsMinimumDocument_UnsupportedDocumentType_ThrowsArgumentException() - { - // Arrange - string filePath = Path.Combine(Path.GetTempPath(), "invalid.pptx"); - - using (PresentationDocument presentationDocument = PresentationDocument.Create(filePath, PresentationDocumentType.Presentation)) - { - PresentationPart presentationPart = presentationDocument.AddPresentationPart(); - presentationPart.Presentation = new Presentation.Presentation(); - presentationPart.Presentation.AddChild(new NotesSize() { Cx = 913607, Cy = 913607 }); - } - - // Act & Assert - Assert.Throws(() => - PresentationDocument.IsMinimumDocument(filePath, PresentationDocumentType.AddIn)); - - // Cleanup - File.Delete(filePath); - } - - [Fact] - public void PresentationDocument_IsMinimumDocument_EmptyPath_ReturnsFalse() - { - // Act - bool result = PresentationDocument.IsMinimumDocument(string.Empty, PresentationDocumentType.Presentation); - - // Assert - Assert.False(result); - } - -#if !NET8_0_OR_GREATER - [Fact] - public void PresentationDocument_IsMinimumDocument_NullPath_ReturnsFalse() - { - // Act - bool result = PresentationDocument.IsMinimumDocument(null, PresentationDocumentType.Presentation); - - // Assert - Assert.False(result); - } -#endif - - [Fact] - public void PresentationDocument_IsMinimumDocument_InvalidContent_ReturnsFalse() - { - // Arrange - string filePath = Path.Combine(Path.GetTempPath(), "invalid.pptx"); - - using (PresentationDocument presentationDocument = PresentationDocument.Create(filePath, PresentationDocumentType.Presentation)) - { - PresentationPart presentationPart = presentationDocument.AddPresentationPart(); - presentationPart.Presentation = new Presentation.Presentation(); - } - - // Act - bool result = PresentationDocument.IsMinimumDocument(filePath, PresentationDocumentType.Presentation); - - // Assert - Assert.False(result); - - // Cleanup - File.Delete(filePath); - } - #endregion } } From 8dc68ef9929aed1625aa7f1b40043c3834c7a998 Mon Sep 17 00:00:00 2001 From: Michael Bowen <10384982+mikeebowen@users.noreply.github.com> Date: Thu, 1 May 2025 14:33:58 -0700 Subject: [PATCH 07/33] Change minimum Word doc API to OpenSettings options --- .../Packaging/OpenXmlPackage.cs | 25 +--- .../Packaging/WordprocessingDocument.cs | 130 ++++-------------- .../ofapiTest/OpenXmlPackageTest.cs | 130 ++++++++++++++++++ 3 files changed, 159 insertions(+), 126 deletions(-) diff --git a/src/DocumentFormat.OpenXml.Framework/Packaging/OpenXmlPackage.cs b/src/DocumentFormat.OpenXml.Framework/Packaging/OpenXmlPackage.cs index 2a63f0d08..3b70ed3e3 100644 --- a/src/DocumentFormat.OpenXml.Framework/Packaging/OpenXmlPackage.cs +++ b/src/DocumentFormat.OpenXml.Framework/Packaging/OpenXmlPackage.cs @@ -106,31 +106,16 @@ internal void LoadAllParts() } /// - /// Determines whether the specified meets the minimum requirements for a valid package. + /// Throws a if the current does not meet the minimum requirements for a valid package. /// - /// The to validate. - /// - /// true if the package meets the minimum requirements; otherwise, false. - /// + /// + /// Thrown when the package does not conform to the minimum requirements for Office to open. + /// /// /// This method is intended to be overridden by derived classes to implement specific validation logic /// for different types of Open XML packages (e.g., Wordprocessing, Spreadsheet, or Presentation documents). /// - protected virtual bool IsMinimumPackage(OpenXmlPackage package) => false; - - /// - /// Determines whether the specified file path is valid for the given document type. - /// - /// The file path to validate. - /// The document type to validate against. - /// - /// true if the file path is valid for the specified document type; otherwise, false. - /// - /// - /// This method is intended to be overridden by derived classes to implement specific validation logic - /// for different types of Open XML documents (e.g., Wordprocessing, Spreadsheet, or Presentation documents). - /// - protected virtual bool IsValidDocumentPath(string path, Enum type) => false; + protected virtual void ThrowIfNotMinimumPackage() => throw new FileFormatException($"The provided package does not conform to the minimum requirements for Office to open."); #region public methods diff --git a/src/DocumentFormat.OpenXml/Packaging/WordprocessingDocument.cs b/src/DocumentFormat.OpenXml/Packaging/WordprocessingDocument.cs index 5c3ad2cab..392b6106e 100644 --- a/src/DocumentFormat.OpenXml/Packaging/WordprocessingDocument.cs +++ b/src/DocumentFormat.OpenXml/Packaging/WordprocessingDocument.cs @@ -268,18 +268,7 @@ public static WordprocessingDocument Open(string path, bool isEditable, OpenSett { if (openSettings.CheckMinimumPackage) { - bool isValidPath = package.IsValidDocumentPath(path, package.DocumentType); - bool isMinimumDocument = package.IsMinimumPackage(package); - - if (!isValidPath) - { - throw new ArgumentException($"The provided path is invalid. {nameof(path)}"); - } - - if (!isMinimumDocument) - { - throw new FileFormatException($"The provided package does not conform to the minimum requirements for Office to open. {nameof(path)}"); - } + package.ThrowIfNotMinimumPackage(); } }) .Build() @@ -299,6 +288,13 @@ public static WordprocessingDocument Open(string path, bool isEditable, OpenSett public static WordprocessingDocument Open(Stream stream, bool isEditable, OpenSettings openSettings) => CreateDefaultBuilder() .UseSettings(openSettings) + .Use(package => + { + if (openSettings.CheckMinimumPackage) + { + package.ThrowIfNotMinimumPackage(); + } + }) .Build() .Open(stream, isEditable); @@ -315,6 +311,13 @@ public static WordprocessingDocument Open(Stream stream, bool isEditable, OpenSe public static WordprocessingDocument Open(Package package, OpenSettings openSettings) => CreateDefaultBuilder() .UseSettings(openSettings) + .Use(package => + { + if (openSettings.CheckMinimumPackage) + { + package.ThrowIfNotMinimumPackage(); + } + }) .Build() .Open(package); @@ -330,105 +333,20 @@ public static WordprocessingDocument Open(Package package) => Open(package, new OpenSettings()); /// - /// Determines whether the specified meets the minimum requirements for a valid WordprocessingDocument package. + /// Throws a if the current does not meet the minimum requirements for a valid package. /// - /// The to validate. - /// - /// true if the package meets the minimum requirements for a WordprocessingDocument; otherwise, false. - /// + /// + /// Thrown when the is missing or its is null. + /// /// - /// This method checks whether the provided package is a valid and contains a - /// with a that has a element. + /// This method ensures that the contains the necessary parts and structure + /// to be opened with Word. /// - protected override bool IsMinimumPackage(OpenXmlPackage package) + protected override void ThrowIfNotMinimumPackage() { - if (package is WordprocessingDocument wordprocessingDocument) - { - return wordprocessingDocument.MainDocumentPart?.Document?.Body is not null; - } - - return false; - } - - /// - /// Determines whether the specified file path is valid for the given WordprocessingDocument type. - /// - /// The file path to validate. - /// The document type to validate against, such as . - /// - /// true if the file path is valid for the specified document type; otherwise, false. - /// - /// - /// This method checks the file path for validity, including ensuring the file exists, the path is well-formed, - /// and the file extension matches the expected type for the given . - /// - protected override bool IsValidDocumentPath(string path, Enum type) - { - if (string.IsNullOrEmpty(path)) - { - return false; - } - - if (path.IndexOfAny(System.IO.Path.GetInvalidPathChars()) >= 0) - { - return false; - } - - try - { - if (!File.Exists(path)) - { - return false; - } - - if (type is WordprocessingDocumentType) - { - WordprocessingDocumentType documentType = (WordprocessingDocumentType)type; - - string ext = new FileInfo(path).Extension.ToUpperInvariant(); - - switch (ext) - { - case ".DOCX": - if (documentType != WordprocessingDocumentType.Document) - { - return false; - } - - break; - case ".DOTX": - if (documentType != WordprocessingDocumentType.Template) - { - return false; - } - - break; - case ".DOCM": - if (documentType != WordprocessingDocumentType.MacroEnabledDocument) - { - return false; - } - - break; - case ".DOTM": - if (documentType != WordprocessingDocumentType.MacroEnabledTemplate) - { - return false; - } - - break; - default: - return false; - } - - return true; - } - - return false; - } - catch + if (this.MainDocumentPart?.Document?.Body is null) { - return false; + throw new FileFormatException("The provided package does not conform to the minimum requirements for Word to open."); } } diff --git a/test/DocumentFormat.OpenXml.Tests/ofapiTest/OpenXmlPackageTest.cs b/test/DocumentFormat.OpenXml.Tests/ofapiTest/OpenXmlPackageTest.cs index cbd1eeca6..872fe514f 100644 --- a/test/DocumentFormat.OpenXml.Tests/ofapiTest/OpenXmlPackageTest.cs +++ b/test/DocumentFormat.OpenXml.Tests/ofapiTest/OpenXmlPackageTest.cs @@ -3,8 +3,10 @@ using DocumentFormat.OpenXml.Packaging; using DocumentFormat.OpenXml.Wordprocessing; +using Microsoft.Testing.Platform.MSBuild; using System; using System.IO; +using System.IO.Packaging; using System.Linq; using Xunit; @@ -876,5 +878,133 @@ public void O15FileOpenTest() Assert.NotNull(webExtensionPart); } } + + [Fact] + public void CheckMinimumPackageTest_ValidDocumentPath_DoesNotThrow_Wordprocessing() + { + // Arrange + string path = string.Concat(Path.GetTempPath(), "valid.docx"); + + using (WordprocessingDocument document = WordprocessingDocument.Create(path, WordprocessingDocumentType.Document)) + { + document.AddMainDocumentPart().Document = new Document(new Body()); + + } + + // Act + Exception exception = Record.Exception(() => + { + using WordprocessingDocument wpd = WordprocessingDocument.Open(path, false, new OpenSettings() { CheckMinimumPackage = true }); + }); + + // Assert + Assert.Null(exception); + } + + [Fact] + public void CheckMinimumPackageTest_ValidDocumentStream_DoesNotThrow_Wordprocessing() + { + // Arrange + using (Stream stream = new MemoryStream()) + { + using (WordprocessingDocument document = WordprocessingDocument.Create(stream, WordprocessingDocumentType.Document)) + { + document.AddMainDocumentPart().Document = new Document(new Body()); + } + + // Act + Exception exception = Record.Exception(() => + { + using WordprocessingDocument wpd = WordprocessingDocument.Open(stream, false, new OpenSettings() { CheckMinimumPackage = true }); + }); + + // Assert + Assert.Null(exception); + } + } + + [Fact] + public void CheckMinimumPackageTest_ValidDocumentPackage_DoesNotThrow_Wordprocessing() + { + // Arrange + string path = Path.Combine(Path.GetTempPath(), "valid.docx"); + + using (WordprocessingDocument document = WordprocessingDocument.Create(path, WordprocessingDocumentType.Document)) + { + document.AddMainDocumentPart().Document = new Document(new Body()); + } + + // Act + using (Package package = Package.Open(path)) + { + Exception exception = Record.Exception(() => + { + using WordprocessingDocument wpd = WordprocessingDocument.Open(package, new OpenSettings() { CheckMinimumPackage = true }); + }); + + // Assert + Assert.Null(exception); + } + + } + + [Fact] + public void CheckMinimumPackageTest_InValidDocumentPath_Throws_Wordprocessing() + { + // Arrange + string path = string.Concat(Path.GetTempPath(), "invalid.docx"); + + using (WordprocessingDocument document = WordprocessingDocument.Create(path, WordprocessingDocumentType.Document)) + { + document.AddMainDocumentPart().Document = new Document(); + } + + // Act and Assert + Assert.Throws(() => + { + using WordprocessingDocument wpd = WordprocessingDocument.Open(path, false, new OpenSettings() { CheckMinimumPackage = true }); + }); + } + + [Fact] + public void CheckMinimumPackageTest_InValidDocumentStream_Throws_Wordprocessing() + { + // Arrange + using (Stream stream = new MemoryStream()) + { + using (WordprocessingDocument document = WordprocessingDocument.Create(stream, WordprocessingDocumentType.Document)) + { + document.AddMainDocumentPart().Document = new Document(); + } + + // Act and Assert + Assert.Throws(() => + { + using WordprocessingDocument wpd = WordprocessingDocument.Open(stream, false, new OpenSettings() { CheckMinimumPackage = true }); + }); + } + } + + [Fact] + public void CheckMinimumPackageTest_InValidDocumentPackage_Throws_Wordprocessing() + { + // Arrange + using (Stream stream = new MemoryStream()) + { + using (WordprocessingDocument document = WordprocessingDocument.Create(stream, WordprocessingDocumentType.Document)) + { + document.AddMainDocumentPart().Document = new Document(); + } + + // Act and Assert + using (Package package = Package.Open(stream)) + { + Assert.Throws(() => + { + using WordprocessingDocument wpd = WordprocessingDocument.Open(package, new OpenSettings() { CheckMinimumPackage = true }); + }); + } + } + } } } From 1a0d73a98b8e219212cf34fe0867225426abf0dd Mon Sep 17 00:00:00 2001 From: Michael Bowen <10384982+mikeebowen@users.noreply.github.com> Date: Mon, 5 May 2025 16:12:49 -0700 Subject: [PATCH 08/33] add VerifyMinimumPackage option to PresentationDocument --- .../Packaging/OpenSettings.cs | 2 +- .../PublicAPI/PublicAPI.Shipped.txt | 4 +- .../Packaging/PresentationDocument.cs | 139 ++---- .../Packaging/SpreadsheetDocument.cs | 127 ++--- .../Packaging/WordprocessingDocument.cs | 6 +- .../ofapiTest/OpenXmlPackageTest.cs | 437 +++++++++++++++++- 6 files changed, 509 insertions(+), 206 deletions(-) diff --git a/src/DocumentFormat.OpenXml.Framework/Packaging/OpenSettings.cs b/src/DocumentFormat.OpenXml.Framework/Packaging/OpenSettings.cs index 791e5ed68..a9e147c7b 100644 --- a/src/DocumentFormat.OpenXml.Framework/Packaging/OpenSettings.cs +++ b/src/DocumentFormat.OpenXml.Framework/Packaging/OpenSettings.cs @@ -85,6 +85,6 @@ public MarkupCompatibilityProcessSettings MarkupCompatibilityProcessSettings /// When set to true, the document will be checked to ensure it contains the necessary parts and structure /// to be considered a valid Open XML package. /// - public bool CheckMinimumPackage { get; set; } + public bool VerifyMinimumPackage { get; set; } } } diff --git a/src/DocumentFormat.OpenXml.Framework/PublicAPI/PublicAPI.Shipped.txt b/src/DocumentFormat.OpenXml.Framework/PublicAPI/PublicAPI.Shipped.txt index 7827e319d..f1c172980 100644 --- a/src/DocumentFormat.OpenXml.Framework/PublicAPI/PublicAPI.Shipped.txt +++ b/src/DocumentFormat.OpenXml.Framework/PublicAPI/PublicAPI.Shipped.txt @@ -1009,5 +1009,5 @@ DocumentFormat.OpenXml.OpenXmlPartWriterSettings.Encoding.set -> void DocumentFormat.OpenXml.OpenXmlPartWriterSettings.OpenXmlPartWriterSettings() -> void DocumentFormat.OpenXml.OpenXmlPartWriter.OpenXmlPartWriter(DocumentFormat.OpenXml.Packaging.OpenXmlPart! openXmlPart, DocumentFormat.OpenXml.OpenXmlPartWriterSettings! settings) -> void DocumentFormat.OpenXml.OpenXmlPartWriter.OpenXmlPartWriter(System.IO.Stream! partStream, DocumentFormat.OpenXml.OpenXmlPartWriterSettings! settings) -> void -DocumentFormat.OpenXml.Packaging.OpenSettings.CheckMinimumPackage.get -> bool -DocumentFormat.OpenXml.Packaging.OpenSettings.CheckMinimumPackage.set -> void +DocumentFormat.OpenXml.Packaging.OpenSettings.VerifyMinimumPackage.get -> bool +DocumentFormat.OpenXml.Packaging.OpenSettings.VerifyMinimumPackage.set -> void diff --git a/src/DocumentFormat.OpenXml/Packaging/PresentationDocument.cs b/src/DocumentFormat.OpenXml/Packaging/PresentationDocument.cs index 1843bcceb..9c251be5b 100644 --- a/src/DocumentFormat.OpenXml/Packaging/PresentationDocument.cs +++ b/src/DocumentFormat.OpenXml/Packaging/PresentationDocument.cs @@ -234,6 +234,13 @@ public static PresentationDocument Open(Package package) public static PresentationDocument Open(string path, bool isEditable, OpenSettings openSettings) => CreateDefaultBuilder() .UseSettings(openSettings) + .Use(package => + { + if (openSettings.VerifyMinimumPackage) + { + package.ThrowIfNotMinimumPackage(); + } + }) .Build() .Open(path, isEditable); @@ -251,6 +258,13 @@ public static PresentationDocument Open(string path, bool isEditable, OpenSettin public static PresentationDocument Open(Stream stream, bool isEditable, OpenSettings openSettings) => CreateDefaultBuilder() .UseSettings(openSettings) + .Use(package => + { + if (openSettings.VerifyMinimumPackage) + { + package.ThrowIfNotMinimumPackage(); + } + }) .Build() .Open(stream, isEditable); @@ -267,120 +281,55 @@ public static PresentationDocument Open(Stream stream, bool isEditable, OpenSett public static PresentationDocument Open(Package package, OpenSettings openSettings) => CreateDefaultBuilder() .UseSettings(openSettings) + .Use(package => + { + if (openSettings.VerifyMinimumPackage) + { + package.ThrowIfNotMinimumPackage(); + } + }) .Build() .Open(package); /// - /// Validates whether the specified file is a minimum valid PresentationDocument. - /// - /// The path to the PresentationDocument file. - /// - /// The expected type of the PresentationDocument. Defaults to . - /// Supported types are: - /// - /// (.pptx) - /// (.potx) - /// (.pptm) - /// (.potm) - /// - /// - /// - /// true if the file is a minimum valid PresentationDocument; otherwise, false. - /// - /// - /// Thrown when the is invalid or unsupported. + /// Validates that the current meets the minimum requirements for a valid package. + /// + /// + /// Thrown if the is , + /// , or , + /// as validation for these types is not supported. + /// + /// + /// Thrown if the does not contain valid NotesSize dimensions + /// or if the dimensions are outside the acceptable range for PowerPoint to open. /// /// - /// A minimum valid PresentationDocument must meet the following criteria: - /// - /// The file must exist and have a valid extension matching the . - /// The file must contain a valid element in the presentation part. - /// - /// Unsupported document types include: - /// - /// (.ppam) - /// (.ppsx) - /// (.ppsm) - /// + /// This method ensures that the document conforms to the minimum requirements for PowerPoint to open it. /// - public static bool IsMinimumDocument(string path, PresentationDocumentType documentType = PresentationDocumentType.Presentation) + protected override void ThrowIfNotMinimumPackage() { - if (documentType == PresentationDocumentType.AddIn || documentType == PresentationDocumentType.Slideshow || documentType == PresentationDocumentType.MacroEnabledSlideshow) + if (this.DocumentType == PresentationDocumentType.Slideshow) { - throw new ArgumentException($"Invalid value: {documentType}. Allowed values are PresentationDocumentType.Presentation, PresentationDocumentType.MacroEnabledPresentation, PresentationDocumentType.MacroEnabledTemplate, and PresentationDocumentType.Template."); + throw new NotSupportedException("Minimum package verification for PresentationDocumentType.Slideshow (.ppsx) is not supported."); } - if (string.IsNullOrEmpty(path)) + if (this.DocumentType == PresentationDocumentType.MacroEnabledSlideshow) { - return false; + throw new NotSupportedException("Minimum package verification for PresentationDocumentType.MacroEnabledSlideshow (.ppsm) is not supported."); } - if (path.IndexOfAny(System.IO.Path.GetInvalidPathChars()) >= 0) + if (this.DocumentType == PresentationDocumentType.AddIn) { - return false; + throw new NotSupportedException("Minimum package verification for PresentationDocumentType.AddIn (.ppam) is not supported."); } - try - { - if (!File.Exists(path)) - { - return false; - } - - string ext = new FileInfo(path).Extension.ToUpperInvariant(); + NotesSize? notesSize = this.PresentationPart?.Presentation?.NotesSize; - switch (ext) - { - case ".PPTX": - if (documentType != PresentationDocumentType.Presentation) - { - return false; - } - - break; - case ".POTX": - if (documentType != PresentationDocumentType.Template) - { - return false; - } - - break; - case ".PPTM": - if (documentType != PresentationDocumentType.MacroEnabledPresentation) - { - return false; - } - - break; - case ".POTM": - if (documentType != PresentationDocumentType.MacroEnabledTemplate) - { - return false; - } - - break; - case ".PPSX": - throw new FileFormatException($"Validation for PresentationDocumentType.AddIn (.ppsx) is not supported."); - case ".PPSM": - throw new FileFormatException($"Validation for PresentationDocumentType.AddIn (.ppsm) is not supported."); - case ".PPAM": - throw new FileFormatException($"Validation for PresentationDocumentType.AddIn (.ppam) is not supported."); - default: - return false; - } - - using (PresentationDocument presentationDocument = Open(path, false)) - { - NotesSize? notesSize = presentationDocument.PresentationPart?.Presentation?.NotesSize; - - return notesSize is not null && notesSize.Cx is not null && notesSize.Cx.HasValue && - notesSize.Cx >= 0 && notesSize.Cx <= 27273042316900 && notesSize.Cy is not null && notesSize.Cy.HasValue && - notesSize.Cy >= 0 && notesSize.Cy <= 27273042316900; - } - } - catch + if (!(notesSize is not null && notesSize.Cx is not null && notesSize.Cx.HasValue && + notesSize.Cx >= 0 && notesSize.Cx <= 27273042316900 && notesSize.Cy is not null && + notesSize.Cy.HasValue && notesSize.Cy >= 0 && notesSize.Cy <= 27273042316900)) { - return false; + throw new FileFormatException("The provided package does not conform to the minimum requirements for PowerPoint to open."); } } diff --git a/src/DocumentFormat.OpenXml/Packaging/SpreadsheetDocument.cs b/src/DocumentFormat.OpenXml/Packaging/SpreadsheetDocument.cs index 2b7ecab5f..d4e47f170 100644 --- a/src/DocumentFormat.OpenXml/Packaging/SpreadsheetDocument.cs +++ b/src/DocumentFormat.OpenXml/Packaging/SpreadsheetDocument.cs @@ -198,6 +198,13 @@ public static SpreadsheetDocument CreateFromTemplate(string path) public static SpreadsheetDocument Open(string path, bool isEditable, OpenSettings openSettings) => CreateDefaultBuilder() .UseSettings(openSettings) + .Use(package => + { + if (openSettings.VerifyMinimumPackage) + { + package.ThrowIfNotMinimumPackage(); + } + }) .Build() .Open(path, isEditable); @@ -215,6 +222,13 @@ public static SpreadsheetDocument Open(string path, bool isEditable, OpenSetting public static SpreadsheetDocument Open(Stream stream, bool isEditable, OpenSettings openSettings) => CreateDefaultBuilder() .UseSettings(openSettings) + .Use(package => + { + if (openSettings.VerifyMinimumPackage) + { + package.ThrowIfNotMinimumPackage(); + } + }) .Build() .Open(stream, isEditable); @@ -231,6 +245,13 @@ public static SpreadsheetDocument Open(Stream stream, bool isEditable, OpenSetti public static SpreadsheetDocument Open(Package package, OpenSettings openSettings) => CreateDefaultBuilder() .UseSettings(openSettings) + .Use(package => + { + if (openSettings.VerifyMinimumPackage) + { + package.ThrowIfNotMinimumPackage(); + } + }) .Build() .Open(package); @@ -269,107 +290,35 @@ public static SpreadsheetDocument Open(System.IO.Packaging.Package package) => Open(package, new OpenSettings()); /// - /// Validates whether the specified file is a minimum valid SpreadsheetDocument. + /// Throws a if the current + /// does not meet the minimum requirements for a valid package. /// - /// The path to the SpreadsheetDocument file. - /// - /// The expected type of the SpreadsheetDocument. Defaults to . - /// Supported types are: + /// + /// Thrown when the does not conform to the minimum requirements + /// for Excel to open. This includes: /// - /// (.xlsx) - /// (.xltx) - /// (.xlsm) - /// (.xltm) + /// The document type is . + /// The is missing or does not contain a valid . + /// The in the first is missing. /// - /// - /// - /// true if the file is a minimum valid SpreadsheetDocument; otherwise, false. - /// - /// - /// Thrown when the is invalid or unsupported. /// /// - /// A minimum valid SpreadsheetDocument must meet the following criteria: - /// - /// The file must exist and have a valid extension matching the . - /// The file must contain at least one element in the workbook part. - /// The file must contain at least one element in the worksheet part. - /// - /// Unsupported document types include (.xlam). + /// This method ensures that the contains the necessary parts and structure + /// to be opened with Excel. /// - public static bool IsMinimumDocument(string path, SpreadsheetDocumentType documentType = SpreadsheetDocumentType.Workbook) + protected override void ThrowIfNotMinimumPackage() { - if (documentType == SpreadsheetDocumentType.AddIn) - { - throw new ArgumentException($"Invalid value: {documentType}. Allowed values are SpreadsheetDocumentType.Workbook, SpreadsheetDocumentType.Template, SpreadsheetDocumentType.MacroEnabledWorkbook, and SpreadsheetDocumentType.MacroEnabledTemplate."); - } - - if (string.IsNullOrEmpty(path)) - { - return false; - } - - if (path.IndexOfAny(System.IO.Path.GetInvalidPathChars()) >= 0) + if (this.DocumentType == SpreadsheetDocumentType.AddIn) { - return false; + throw new NotSupportedException("Validation for SpreadsheetDocument.AddIn (.xlam) is not supported."); } - try - { - if (!File.Exists(path)) - { - return false; - } - - string ext = new FileInfo(path).Extension.ToUpperInvariant(); - - switch (ext) - { - case ".XLSX": - if (documentType != SpreadsheetDocumentType.Workbook) - { - return false; - } - - break; - case ".XLTX": - if (documentType != SpreadsheetDocumentType.Template) - { - return false; - } - - break; - case ".XLSM": - if (documentType != SpreadsheetDocumentType.MacroEnabledWorkbook) - { - return false; - } - - break; - case ".XLTM": - if (documentType != SpreadsheetDocumentType.MacroEnabledTemplate) - { - return false; - } - - break; - case ".XLAM": - throw new FileFormatException($"Validation for SpreadsheetDocument.AddIn (.xlam) is not supported."); - default: - return false; - } - - using (SpreadsheetDocument spreadsheetDocument = Open(path, false)) - { - Sheet? sheet = spreadsheetDocument?.WorkbookPart?.Workbook?.Sheets?.GetFirstChild(); - SheetData? sheetData = spreadsheetDocument?.WorkbookPart?.WorksheetParts?.FirstOrDefaultAndMaxOne()?.Worksheet?.GetFirstChild(); + Sheet? sheet = this.WorkbookPart?.Workbook?.Sheets?.GetFirstChild(); + SheetData? sheetData = this.WorkbookPart?.WorksheetParts?.FirstOrDefaultAndMaxOne()?.Worksheet?.GetFirstChild(); - return sheet is not null && sheetData is not null; - } - } - catch + if (sheet is null || sheetData is null) { - return false; + throw new FileFormatException("The provided package does not conform to the minimum requirements for Excel to open."); } } diff --git a/src/DocumentFormat.OpenXml/Packaging/WordprocessingDocument.cs b/src/DocumentFormat.OpenXml/Packaging/WordprocessingDocument.cs index 392b6106e..2422c2d7a 100644 --- a/src/DocumentFormat.OpenXml/Packaging/WordprocessingDocument.cs +++ b/src/DocumentFormat.OpenXml/Packaging/WordprocessingDocument.cs @@ -266,7 +266,7 @@ public static WordprocessingDocument Open(string path, bool isEditable, OpenSett .UseSettings(openSettings) .Use(package => { - if (openSettings.CheckMinimumPackage) + if (openSettings.VerifyMinimumPackage) { package.ThrowIfNotMinimumPackage(); } @@ -290,7 +290,7 @@ public static WordprocessingDocument Open(Stream stream, bool isEditable, OpenSe .UseSettings(openSettings) .Use(package => { - if (openSettings.CheckMinimumPackage) + if (openSettings.VerifyMinimumPackage) { package.ThrowIfNotMinimumPackage(); } @@ -313,7 +313,7 @@ public static WordprocessingDocument Open(Package package, OpenSettings openSett .UseSettings(openSettings) .Use(package => { - if (openSettings.CheckMinimumPackage) + if (openSettings.VerifyMinimumPackage) { package.ThrowIfNotMinimumPackage(); } diff --git a/test/DocumentFormat.OpenXml.Tests/ofapiTest/OpenXmlPackageTest.cs b/test/DocumentFormat.OpenXml.Tests/ofapiTest/OpenXmlPackageTest.cs index 872fe514f..9a59ff7a8 100644 --- a/test/DocumentFormat.OpenXml.Tests/ofapiTest/OpenXmlPackageTest.cs +++ b/test/DocumentFormat.OpenXml.Tests/ofapiTest/OpenXmlPackageTest.cs @@ -880,7 +880,7 @@ public void O15FileOpenTest() } [Fact] - public void CheckMinimumPackageTest_ValidDocumentPath_DoesNotThrow_Wordprocessing() + public void VerifyMinimumPackageTest_ValidDocumentPath_DoesNotThrow_Wordprocessing() { // Arrange string path = string.Concat(Path.GetTempPath(), "valid.docx"); @@ -894,7 +894,7 @@ public void CheckMinimumPackageTest_ValidDocumentPath_DoesNotThrow_Wordprocessin // Act Exception exception = Record.Exception(() => { - using WordprocessingDocument wpd = WordprocessingDocument.Open(path, false, new OpenSettings() { CheckMinimumPackage = true }); + using WordprocessingDocument wpd = WordprocessingDocument.Open(path, false, new OpenSettings() { VerifyMinimumPackage = true }); }); // Assert @@ -902,7 +902,7 @@ public void CheckMinimumPackageTest_ValidDocumentPath_DoesNotThrow_Wordprocessin } [Fact] - public void CheckMinimumPackageTest_ValidDocumentStream_DoesNotThrow_Wordprocessing() + public void VerifyMinimumPackageTest_ValidDocumentStream_DoesNotThrow_Wordprocessing() { // Arrange using (Stream stream = new MemoryStream()) @@ -915,7 +915,7 @@ public void CheckMinimumPackageTest_ValidDocumentStream_DoesNotThrow_Wordprocess // Act Exception exception = Record.Exception(() => { - using WordprocessingDocument wpd = WordprocessingDocument.Open(stream, false, new OpenSettings() { CheckMinimumPackage = true }); + using WordprocessingDocument wpd = WordprocessingDocument.Open(stream, false, new OpenSettings() { VerifyMinimumPackage = true }); }); // Assert @@ -924,7 +924,7 @@ public void CheckMinimumPackageTest_ValidDocumentStream_DoesNotThrow_Wordprocess } [Fact] - public void CheckMinimumPackageTest_ValidDocumentPackage_DoesNotThrow_Wordprocessing() + public void VerifyMinimumPackageTest_ValidDocumentPackage_DoesNotThrow_Wordprocessing() { // Arrange string path = Path.Combine(Path.GetTempPath(), "valid.docx"); @@ -939,17 +939,18 @@ public void CheckMinimumPackageTest_ValidDocumentPackage_DoesNotThrow_Wordproces { Exception exception = Record.Exception(() => { - using WordprocessingDocument wpd = WordprocessingDocument.Open(package, new OpenSettings() { CheckMinimumPackage = true }); + using WordprocessingDocument wpd = WordprocessingDocument.Open(package, new OpenSettings() { VerifyMinimumPackage = true }); }); // Assert Assert.Null(exception); } - } + private readonly string minPackageWordExMsg = "The provided package does not conform to the minimum requirements for Word to open."; + [Fact] - public void CheckMinimumPackageTest_InValidDocumentPath_Throws_Wordprocessing() + public void VerifyMinimumPackageTest_InValidDocumentPath_Throws_Wordprocessing() { // Arrange string path = string.Concat(Path.GetTempPath(), "invalid.docx"); @@ -960,14 +961,16 @@ public void CheckMinimumPackageTest_InValidDocumentPath_Throws_Wordprocessing() } // Act and Assert - Assert.Throws(() => + FileFormatException ex = Assert.Throws(() => { - using WordprocessingDocument wpd = WordprocessingDocument.Open(path, false, new OpenSettings() { CheckMinimumPackage = true }); + using WordprocessingDocument wpd = WordprocessingDocument.Open(path, false, new OpenSettings() { VerifyMinimumPackage = true }); }); + + Assert.Equal(minPackageWordExMsg, ex.Message); } [Fact] - public void CheckMinimumPackageTest_InValidDocumentStream_Throws_Wordprocessing() + public void VerifyMinimumPackageTest_InValidDocumentStream_Throws_Wordprocessing() { // Arrange using (Stream stream = new MemoryStream()) @@ -978,15 +981,17 @@ public void CheckMinimumPackageTest_InValidDocumentStream_Throws_Wordprocessing( } // Act and Assert - Assert.Throws(() => + FileFormatException ex = Assert.Throws(() => { - using WordprocessingDocument wpd = WordprocessingDocument.Open(stream, false, new OpenSettings() { CheckMinimumPackage = true }); + using WordprocessingDocument wpd = WordprocessingDocument.Open(stream, false, new OpenSettings() { VerifyMinimumPackage = true }); }); + + Assert.Equal(minPackageWordExMsg, ex.Message); } } [Fact] - public void CheckMinimumPackageTest_InValidDocumentPackage_Throws_Wordprocessing() + public void VerifyMinimumPackageTest_InValidDocumentPackage_Throws_Wordprocessing() { // Arrange using (Stream stream = new MemoryStream()) @@ -999,11 +1004,411 @@ public void CheckMinimumPackageTest_InValidDocumentPackage_Throws_Wordprocessing // Act and Assert using (Package package = Package.Open(stream)) { - Assert.Throws(() => + FileFormatException ex = Assert.Throws(() => { - using WordprocessingDocument wpd = WordprocessingDocument.Open(package, new OpenSettings() { CheckMinimumPackage = true }); + using WordprocessingDocument wpd = WordprocessingDocument.Open(package, new OpenSettings() { VerifyMinimumPackage = true }); }); + + Assert.Equal(minPackageWordExMsg, ex.Message); + } + } + } + + [Fact] + public void VerifyMinimumPackageTest_ValidDocumentPath_DoesNotThrow_Spreadsheet() + { + // Arrange + string path = string.Concat(Path.GetTempPath(), "valid.xlsx"); + + using (SpreadsheetDocument document = SpreadsheetDocument.Create(path, SpreadsheetDocumentType.Workbook)) + { + WorkbookPart wbp = document.AddWorkbookPart(); + WorksheetPart wsp = wbp.AddNewPart(); + wbp.Workbook = new Spreadsheet.Workbook(new Spreadsheet.Sheets(new Spreadsheet.Sheet() { Id = wbp.GetIdOfPart(wsp), SheetId = 1, Name = "Sheet1" })); + wsp.Worksheet = new Spreadsheet.Worksheet(new Spreadsheet.SheetData()); + } + + // Act + Exception exception = Record.Exception(() => + { + using SpreadsheetDocument ssd = SpreadsheetDocument.Open(path, false, new OpenSettings() { VerifyMinimumPackage = true }); + }); + + // Assert + Assert.Null(exception); + } + + [Fact] + public void VerifyMinimumPackageTest_ValidDocumentStream_DoesNotThrow_Spreadsheet() + { + // Arrange + using (Stream stream = new MemoryStream()) + { + using (SpreadsheetDocument document = SpreadsheetDocument.Create(stream, SpreadsheetDocumentType.MacroEnabledTemplate)) + { + WorkbookPart wbp = document.AddWorkbookPart(); + WorksheetPart wsp = wbp.AddNewPart(); + wbp.Workbook = new Spreadsheet.Workbook(new Spreadsheet.Sheets(new Spreadsheet.Sheet() { Id = wbp.GetIdOfPart(wsp), SheetId = 1, Name = "Sheet1" })); + wsp.Worksheet = new Spreadsheet.Worksheet(new Spreadsheet.SheetData()); } + + // Act + Exception exception = Record.Exception(() => + { + using SpreadsheetDocument ssd = SpreadsheetDocument.Open(stream, false, new OpenSettings() { VerifyMinimumPackage = true }); + }); + + // Assert + Assert.Null(exception); + } + } + + [Fact] + public void VerifyMinimumPackageTest_ValidDocumentPackage_DoesNotThrow_Spreadsheet() + { + // Arrange + string path = Path.Combine(Path.GetTempPath(), "valid.xlsx"); + + using (SpreadsheetDocument document = SpreadsheetDocument.Create(path, SpreadsheetDocumentType.Template)) + { + WorkbookPart wbp = document.AddWorkbookPart(); + WorksheetPart wsp = wbp.AddNewPart(); + wbp.Workbook = new Spreadsheet.Workbook(new Spreadsheet.Sheets(new Spreadsheet.Sheet() { Id = wbp.GetIdOfPart(wsp), SheetId = 1, Name = "Sheet1" })); + wsp.Worksheet = new Spreadsheet.Worksheet(new Spreadsheet.SheetData()); + } + + // Act + using (Package package = Package.Open(path)) + { + Exception exception = Record.Exception(() => + { + using SpreadsheetDocument ssd = SpreadsheetDocument.Open(package, new OpenSettings() { VerifyMinimumPackage = true }); + }); + + // Assert + Assert.Null(exception); + } + } + + private readonly string spreadsheetExMsg = "The provided package does not conform to the minimum requirements for Excel to open."; + + [Fact] + public void VerifyMinimumPackageTest_InValidDocumentPath_Throws_Spreadsheet() + { + // Arrange + string path = string.Concat(Path.GetTempPath(), "invalid.xlsx"); + + using (SpreadsheetDocument document = SpreadsheetDocument.Create(path, SpreadsheetDocumentType.MacroEnabledWorkbook)) + { + WorkbookPart wbp = document.AddWorkbookPart(); + WorksheetPart wsp = wbp.AddNewPart(); + wbp.Workbook = new Spreadsheet.Workbook(new Spreadsheet.Sheets(new Spreadsheet.Sheet() { Id = wbp.GetIdOfPart(wsp), SheetId = 1, Name = "Sheet1" })); + } + + // Act and Assert + FileFormatException ex = Assert.Throws(() => + { + using SpreadsheetDocument ssd = SpreadsheetDocument.Open(path, false, new OpenSettings() { VerifyMinimumPackage = true }); + }); + + Assert.Equal(spreadsheetExMsg, ex.Message); + } + + [Fact] + public void VerifyMinimumPackageTest_InValidDocumentStream_Throws_Spreadsheet() + { + // Arrange + using (Stream stream = new MemoryStream()) + { + using (SpreadsheetDocument document = SpreadsheetDocument.Create(stream, SpreadsheetDocumentType.Workbook)) + { + WorkbookPart wbp = document.AddWorkbookPart(); + WorksheetPart wsp = wbp.AddNewPart(); + wsp.Worksheet = new Spreadsheet.Worksheet(new Spreadsheet.SheetData()); + } + + // Act and Assert + FileFormatException ex = Assert.Throws(() => + { + using SpreadsheetDocument ssd = SpreadsheetDocument.Open(stream, false, new OpenSettings() { VerifyMinimumPackage = true }); + }); + + Assert.Equal(spreadsheetExMsg, ex.Message); + } + } + + [Fact] + public void VerifyMinimumPackageTest_InValidDocumentPackage_Throws_Spreadsheet() + { + // Arrange + using (Stream stream = new MemoryStream()) + { + using (SpreadsheetDocument document = SpreadsheetDocument.Create(stream, SpreadsheetDocumentType.Workbook)) + { + WorkbookPart wbp = document.AddWorkbookPart(); + WorksheetPart wsp = wbp.AddNewPart(); + } + + // Act and Assert + using (Package package = Package.Open(stream)) + { + FileFormatException ex = Assert.Throws(() => + { + using SpreadsheetDocument wpd = SpreadsheetDocument.Open(package, new OpenSettings() { VerifyMinimumPackage = true }); + }); + + Assert.Equal(spreadsheetExMsg, ex.Message); + } + } + } + + [Fact] + public void VerifyMinimumPackageTest_AddInDocumentPackage_Throws_Spreadsheet() + { + // Arrange + using (Stream stream = new MemoryStream()) + { + using (SpreadsheetDocument document = SpreadsheetDocument.Create(stream, SpreadsheetDocumentType.AddIn)) + { + WorkbookPart wbp = document.AddWorkbookPart(); + WorksheetPart wsp = wbp.AddNewPart(); + wbp.Workbook = new Spreadsheet.Workbook(new Spreadsheet.Sheets(new Spreadsheet.Sheet() { Id = wbp.GetIdOfPart(wsp), SheetId = 1, Name = "Sheet1" })); + wsp.Worksheet = new Spreadsheet.Worksheet(new Spreadsheet.SheetData()); + } + + // Act and Assert + using (Package package = Package.Open(stream)) + { + NotSupportedException ex = Assert.Throws(() => + { + using SpreadsheetDocument wpd = SpreadsheetDocument.Open(package, new OpenSettings() { VerifyMinimumPackage = true }); + }); + + Assert.Equal("Validation for SpreadsheetDocument.AddIn (.xlam) is not supported.", ex.Message); + } + } + } + + [Fact] + public void VerifyMinimumPackageTest_ValidDocumentPath_DoesNotThrow_Presentation() + { + // Arrange + string path = string.Concat(Path.GetTempPath(), "valid.pptx"); + + using (PresentationDocument presentationDocument = PresentationDocument.Create(path, PresentationDocumentType.Presentation)) + { + // create presentation part + PresentationPart presentationPart = presentationDocument.AddPresentationPart(); + presentationPart.Presentation = new Presentation.Presentation(); + presentationPart.Presentation.AddChild(new Presentation.NotesSize() { Cx = 913607, Cy = 913607 }); + } + + + // Act + Exception exception = Record.Exception(() => + { + using PresentationDocument pd = PresentationDocument.Open(path, false, new OpenSettings() { VerifyMinimumPackage = true }); + }); + + // Assert + Assert.Null(exception); + } + + [Fact] + public void VerifyMinimumPackageTest_ValidDocumentStream_DoesNotThrow_Presentation() + { + // Arrange + using (Stream stream = new MemoryStream()) + { + using (PresentationDocument presentationDocument = PresentationDocument.Create(stream, PresentationDocumentType.Presentation)) + { + // create presentation part + PresentationPart presentationPart = presentationDocument.AddPresentationPart(); + presentationPart.Presentation = new Presentation.Presentation(); + presentationPart.Presentation.AddChild(new Presentation.NotesSize() { Cx = 913607, Cy = 913607 }); + } + + // Act + Exception exception = Record.Exception(() => + { + using PresentationDocument pd = PresentationDocument.Open(stream, false, new OpenSettings() { VerifyMinimumPackage = true }); + }); + + // Assert + Assert.Null(exception); + } + } + + [Fact] + public void VerifyMinimumPackageTest_ValidDocumentPackage_DoesNotThrow_Presentation() + { + // Arrange + string path = Path.Combine(Path.GetTempPath(), "valid.xlsx"); + + using (SpreadsheetDocument document = SpreadsheetDocument.Create(path, SpreadsheetDocumentType.Template)) + { + WorkbookPart wbp = document.AddWorkbookPart(); + WorksheetPart wsp = wbp.AddNewPart(); + wbp.Workbook = new Spreadsheet.Workbook(new Spreadsheet.Sheets(new Spreadsheet.Sheet() { Id = wbp.GetIdOfPart(wsp), SheetId = 1, Name = "Sheet1" })); + wsp.Worksheet = new Spreadsheet.Worksheet(new Spreadsheet.SheetData()); + } + + // Act + using (Package package = Package.Open(path)) + { + Exception exception = Record.Exception(() => + { + using SpreadsheetDocument ssd = SpreadsheetDocument.Open(package, new OpenSettings() { VerifyMinimumPackage = true }); + }); + + // Assert + Assert.Null(exception); + } + } + + private readonly string presentationExMsg = "The provided package does not conform to the minimum requirements for PowerPoint to open."; + + [Fact] + public void VerifyMinimumPackageTest_InValidDocumentPath_Throws_Presentation() + { + // Arrange + string path = string.Concat(Path.GetTempPath(), "invalid.pptx"); + + using (PresentationDocument presentationDocument = PresentationDocument.Create(path, PresentationDocumentType.Presentation)) + { + // create presentation part + PresentationPart presentationPart = presentationDocument.AddPresentationPart(); + presentationPart.Presentation = new Presentation.Presentation(); + presentationPart.Presentation.AddChild(new Presentation.NotesSize() { Cx = -2, Cy = 913607 }); + } + + // Act and Assert + FileFormatException ex = Assert.Throws(() => + { + using PresentationDocument ssd = PresentationDocument.Open(path, false, new OpenSettings() { VerifyMinimumPackage = true }); + }); + + Assert.Equal(presentationExMsg, ex.Message); + } + + [Fact] + public void VerifyMinimumPackageTest_InValidDocumentStream_Throws_Presentation() + { + // Arrange + using (Stream stream = new MemoryStream()) + { + using (PresentationDocument presentationDocument = PresentationDocument.Create(stream, PresentationDocumentType.Presentation)) + { + // create presentation part + PresentationPart presentationPart = presentationDocument.AddPresentationPart(); + presentationPart.Presentation = new Presentation.Presentation(); + } + + // Act and Assert + FileFormatException ex = Assert.Throws(() => + { + using PresentationDocument ssd = PresentationDocument.Open(stream, false, new OpenSettings() { VerifyMinimumPackage = true }); + }); + + Assert.Equal(presentationExMsg, ex.Message); + } + } + + [Fact] + public void VerifyMinimumPackageTest_InValidDocumentPackage_Throws_Presentation() + { + // Arrange + using (Stream stream = new MemoryStream()) + { + using (PresentationDocument presentationDocument = PresentationDocument.Create(stream, PresentationDocumentType.Presentation)) + { + // create presentation part + PresentationPart presentationPart = presentationDocument.AddPresentationPart(); + presentationPart.Presentation = new Presentation.Presentation(); + } + + // Act and Assert + using (Package package = Package.Open(stream)) + { + FileFormatException ex = Assert.Throws(() => + { + using PresentationDocument pd = PresentationDocument.Open(package, new OpenSettings() { VerifyMinimumPackage = true }); + }); + + Assert.Equal(presentationExMsg, ex.Message); + } + } + } + + [Fact] + public void VerifyMinimumPackageTest_AddInDocumentPackage_Throws_Presentation() + { + // Arrange + using (Stream stream = new MemoryStream()) + { + using (PresentationDocument presentationDocument = PresentationDocument.Create(stream, PresentationDocumentType.AddIn)) + { + // create presentation part + PresentationPart presentationPart = presentationDocument.AddPresentationPart(); + presentationPart.Presentation = new Presentation.Presentation(); + presentationPart.Presentation.AddChild(new Presentation.NotesSize() { Cx = 913607, Cy = 913607 }); + } + + // Act and Assert + NotSupportedException ex = Assert.Throws(() => + { + using PresentationDocument pd = PresentationDocument.Open(stream, false, new OpenSettings() { VerifyMinimumPackage = true }); + }); + + Assert.Equal("Minimum package verification for PresentationDocumentType.AddIn (.ppam) is not supported.", ex.Message); + } + } + + [Fact] + public void VerifyMinimumPackageTest_MacroEnabledSlideshowDocumentPackage_Throws_Presentation() + { + // Arrange + using (Stream stream = new MemoryStream()) + { + using (PresentationDocument presentationDocument = PresentationDocument.Create(stream, PresentationDocumentType.MacroEnabledSlideshow)) + { + // create presentation part + PresentationPart presentationPart = presentationDocument.AddPresentationPart(); + presentationPart.Presentation = new Presentation.Presentation(); + presentationPart.Presentation.AddChild(new Presentation.NotesSize() { Cx = 913607, Cy = 913607 }); + } + + // Act and Assert + NotSupportedException ex = Assert.Throws(() => + { + using PresentationDocument pd = PresentationDocument.Open(stream, false, new OpenSettings() { VerifyMinimumPackage = true }); + }); + + Assert.Equal("Minimum package verification for PresentationDocumentType.MacroEnabledSlideshow (.ppsm) is not supported.", ex.Message); + } + } + + [Fact] + public void VerifyMinimumPackageTest_SlideshowDocumentPackage_Throws_Presentation() + { + // Arrange + using (Stream stream = new MemoryStream()) + { + using (PresentationDocument presentationDocument = PresentationDocument.Create(stream, PresentationDocumentType.Slideshow)) + { + // create presentation part + PresentationPart presentationPart = presentationDocument.AddPresentationPart(); + presentationPart.Presentation = new Presentation.Presentation(); + presentationPart.Presentation.AddChild(new Presentation.NotesSize() { Cx = 913607, Cy = 913607 }); + } + + // Act and Assert + NotSupportedException ex = Assert.Throws(() => + { + using PresentationDocument pd = PresentationDocument.Open(stream, false, new OpenSettings() { VerifyMinimumPackage = true }); + }); + + Assert.Equal("Minimum package verification for PresentationDocumentType.Slideshow (.ppsx) is not supported.", ex.Message); } } } From 8ba0e4d1929174ffd7fb02ca7957318c7bd9ccec Mon Sep 17 00:00:00 2001 From: Michael Bowen <10384982+mikeebowen@users.noreply.github.com> Date: Mon, 5 May 2025 16:18:06 -0700 Subject: [PATCH 09/33] fix linting issues --- .../ofapiTest/OpenXmlPackageTest.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/test/DocumentFormat.OpenXml.Tests/ofapiTest/OpenXmlPackageTest.cs b/test/DocumentFormat.OpenXml.Tests/ofapiTest/OpenXmlPackageTest.cs index 9a59ff7a8..1a0b190c1 100644 --- a/test/DocumentFormat.OpenXml.Tests/ofapiTest/OpenXmlPackageTest.cs +++ b/test/DocumentFormat.OpenXml.Tests/ofapiTest/OpenXmlPackageTest.cs @@ -23,8 +23,14 @@ namespace DocumentFormat.OpenXml.Tests { /// - /// Summary description for OpenXmlPackageTest + /// Contains unit tests for validating the behavior of Open XML packages, including WordprocessingDocument, + /// PresentationDocument, and SpreadsheetDocument. These tests cover scenarios such as auto-save functionality, + /// document type changes, part relationships, and minimum package verification. /// + /// + /// This class uses the xUnit framework for testing and includes tests for various Open XML document types. + /// It ensures the correctness of document operations, relationships, and compliance with Open XML standards. + /// public class OpenXmlPackageTest { [Fact] @@ -888,7 +894,6 @@ public void VerifyMinimumPackageTest_ValidDocumentPath_DoesNotThrow_Wordprocessi using (WordprocessingDocument document = WordprocessingDocument.Create(path, WordprocessingDocumentType.Document)) { document.AddMainDocumentPart().Document = new Document(new Body()); - } // Act @@ -1203,7 +1208,6 @@ public void VerifyMinimumPackageTest_ValidDocumentPath_DoesNotThrow_Presentation presentationPart.Presentation.AddChild(new Presentation.NotesSize() { Cx = 913607, Cy = 913607 }); } - // Act Exception exception = Record.Exception(() => { From 55e3eef16d83a67e680fdeb31f4c8437f17f9d6f Mon Sep 17 00:00:00 2001 From: Michael Bowen <10384982+mikeebowen@users.noreply.github.com> Date: Tue, 6 May 2025 09:02:43 -0700 Subject: [PATCH 10/33] remove trailing whitespace --- src/DocumentFormat.OpenXml/Packaging/SpreadsheetDocument.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/DocumentFormat.OpenXml/Packaging/SpreadsheetDocument.cs b/src/DocumentFormat.OpenXml/Packaging/SpreadsheetDocument.cs index d4e47f170..3a58f7e8a 100644 --- a/src/DocumentFormat.OpenXml/Packaging/SpreadsheetDocument.cs +++ b/src/DocumentFormat.OpenXml/Packaging/SpreadsheetDocument.cs @@ -290,11 +290,11 @@ public static SpreadsheetDocument Open(System.IO.Packaging.Package package) => Open(package, new OpenSettings()); /// - /// Throws a if the current + /// Throws a if the current /// does not meet the minimum requirements for a valid package. /// /// - /// Thrown when the does not conform to the minimum requirements + /// Thrown when the does not conform to the minimum requirements /// for Excel to open. This includes: /// /// The document type is . From 69a5900abd3d057874ce69469c29533e4c543c46 Mon Sep 17 00:00:00 2001 From: Michael Bowen <10384982+mikeebowen@users.noreply.github.com> Date: Tue, 6 May 2025 09:21:31 -0700 Subject: [PATCH 11/33] remove unnecessary $ from string --- .../Packaging/OpenXmlPackage.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DocumentFormat.OpenXml.Framework/Packaging/OpenXmlPackage.cs b/src/DocumentFormat.OpenXml.Framework/Packaging/OpenXmlPackage.cs index 3b70ed3e3..c4deda42a 100644 --- a/src/DocumentFormat.OpenXml.Framework/Packaging/OpenXmlPackage.cs +++ b/src/DocumentFormat.OpenXml.Framework/Packaging/OpenXmlPackage.cs @@ -115,7 +115,7 @@ internal void LoadAllParts() /// This method is intended to be overridden by derived classes to implement specific validation logic /// for different types of Open XML packages (e.g., Wordprocessing, Spreadsheet, or Presentation documents). /// - protected virtual void ThrowIfNotMinimumPackage() => throw new FileFormatException($"The provided package does not conform to the minimum requirements for Office to open."); + protected virtual void ThrowIfNotMinimumPackage() => throw new FileFormatException("The provided package does not conform to the minimum requirements for Office to open."); #region public methods From 9bc42e2b761343423337de3df2b883fd7a4fa618 Mon Sep 17 00:00:00 2001 From: Michael Bowen <10384982+mikeebowen@users.noreply.github.com> Date: Tue, 6 May 2025 09:50:29 -0700 Subject: [PATCH 12/33] remove unnecessary usings --- src/DocumentFormat.OpenXml/Packaging/PresentationDocument.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/DocumentFormat.OpenXml/Packaging/PresentationDocument.cs b/src/DocumentFormat.OpenXml/Packaging/PresentationDocument.cs index 9c251be5b..8574a62e3 100644 --- a/src/DocumentFormat.OpenXml/Packaging/PresentationDocument.cs +++ b/src/DocumentFormat.OpenXml/Packaging/PresentationDocument.cs @@ -2,14 +2,12 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using DocumentFormat.OpenXml.Builder; -using DocumentFormat.OpenXml.Drawing; using DocumentFormat.OpenXml.Features; using DocumentFormat.OpenXml.Presentation; using System; using System.Diagnostics.CodeAnalysis; using System.IO; using System.IO.Packaging; -using System.Linq; using System.Reflection; namespace DocumentFormat.OpenXml.Packaging From 3a0c3679492084ecf0ae99f8ad505021e81bc212 Mon Sep 17 00:00:00 2001 From: Taylor Southwick Date: Mon, 12 May 2025 10:48:56 -0700 Subject: [PATCH 13/33] use feature for minimum document --- .../OpenXmlPackageBuilderExtensions.cs | 10 ++- .../Features/IMinimumDocumentFeature.cs | 9 ++ .../Packaging/PresentationDocument.cs | 87 +++++-------------- .../TypedPackageFeatureCollection.cs | 8 +- 4 files changed, 48 insertions(+), 66 deletions(-) create mode 100644 src/DocumentFormat.OpenXml.Framework/Features/IMinimumDocumentFeature.cs diff --git a/src/DocumentFormat.OpenXml.Framework/Builder/OpenXmlPackageBuilderExtensions.cs b/src/DocumentFormat.OpenXml.Framework/Builder/OpenXmlPackageBuilderExtensions.cs index 51020763d..b546129e5 100644 --- a/src/DocumentFormat.OpenXml.Framework/Builder/OpenXmlPackageBuilderExtensions.cs +++ b/src/DocumentFormat.OpenXml.Framework/Builder/OpenXmlPackageBuilderExtensions.cs @@ -110,7 +110,15 @@ public static IPackageBuilder Use(this IPackageBuilder UseSettings(this IPackageBuilder builder, OpenSettings settings) where TPackage : OpenXmlPackage - => builder.Use(package => package.OpenSettings = settings); + => builder.Use(package => + { + package.OpenSettings = settings; + + if (settings.VerifyMinimumPackage && package.Features.Get() is { } minimumFeature && !minimumFeature.Validate()) + { + throw new FileFormatException("The provided package does not conform to the minimum requirements to open."); + } + }); internal static IPackageBuilder UseDefaultBehaviorAndLockBuilder(this IPackageBuilder builder) where TPackage : OpenXmlPackage diff --git a/src/DocumentFormat.OpenXml.Framework/Features/IMinimumDocumentFeature.cs b/src/DocumentFormat.OpenXml.Framework/Features/IMinimumDocumentFeature.cs new file mode 100644 index 000000000..3f1b725cf --- /dev/null +++ b/src/DocumentFormat.OpenXml.Framework/Features/IMinimumDocumentFeature.cs @@ -0,0 +1,9 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace DocumentFormat.OpenXml.Features; + +internal interface IMinimumDocumentFeature +{ + bool Validate(); +} diff --git a/src/DocumentFormat.OpenXml/Packaging/PresentationDocument.cs b/src/DocumentFormat.OpenXml/Packaging/PresentationDocument.cs index 8574a62e3..5913f9570 100644 --- a/src/DocumentFormat.OpenXml/Packaging/PresentationDocument.cs +++ b/src/DocumentFormat.OpenXml/Packaging/PresentationDocument.cs @@ -232,13 +232,6 @@ public static PresentationDocument Open(Package package) public static PresentationDocument Open(string path, bool isEditable, OpenSettings openSettings) => CreateDefaultBuilder() .UseSettings(openSettings) - .Use(package => - { - if (openSettings.VerifyMinimumPackage) - { - package.ThrowIfNotMinimumPackage(); - } - }) .Build() .Open(path, isEditable); @@ -256,13 +249,6 @@ public static PresentationDocument Open(string path, bool isEditable, OpenSettin public static PresentationDocument Open(Stream stream, bool isEditable, OpenSettings openSettings) => CreateDefaultBuilder() .UseSettings(openSettings) - .Use(package => - { - if (openSettings.VerifyMinimumPackage) - { - package.ThrowIfNotMinimumPackage(); - } - }) .Build() .Open(stream, isEditable); @@ -279,58 +265,9 @@ public static PresentationDocument Open(Stream stream, bool isEditable, OpenSett public static PresentationDocument Open(Package package, OpenSettings openSettings) => CreateDefaultBuilder() .UseSettings(openSettings) - .Use(package => - { - if (openSettings.VerifyMinimumPackage) - { - package.ThrowIfNotMinimumPackage(); - } - }) .Build() .Open(package); - /// - /// Validates that the current meets the minimum requirements for a valid package. - /// - /// - /// Thrown if the is , - /// , or , - /// as validation for these types is not supported. - /// - /// - /// Thrown if the does not contain valid NotesSize dimensions - /// or if the dimensions are outside the acceptable range for PowerPoint to open. - /// - /// - /// This method ensures that the document conforms to the minimum requirements for PowerPoint to open it. - /// - protected override void ThrowIfNotMinimumPackage() - { - if (this.DocumentType == PresentationDocumentType.Slideshow) - { - throw new NotSupportedException("Minimum package verification for PresentationDocumentType.Slideshow (.ppsx) is not supported."); - } - - if (this.DocumentType == PresentationDocumentType.MacroEnabledSlideshow) - { - throw new NotSupportedException("Minimum package verification for PresentationDocumentType.MacroEnabledSlideshow (.ppsm) is not supported."); - } - - if (this.DocumentType == PresentationDocumentType.AddIn) - { - throw new NotSupportedException("Minimum package verification for PresentationDocumentType.AddIn (.ppam) is not supported."); - } - - NotesSize? notesSize = this.PresentationPart?.Presentation?.NotesSize; - - if (!(notesSize is not null && notesSize.Cx is not null && notesSize.Cx.HasValue && - notesSize.Cx >= 0 && notesSize.Cx <= 27273042316900 && notesSize.Cy is not null && - notesSize.Cy.HasValue && notesSize.Cy >= 0 && notesSize.Cy <= 27273042316900)) - { - throw new FileFormatException("The provided package does not conform to the minimum requirements for PowerPoint to open."); - } - } - /// /// Changes the document type. /// @@ -570,7 +507,8 @@ public LabelInfoPart? LabelInfoPart private partial class PresentationDocumentFeatures : TypedPackageFeatureCollection, IApplicationTypeFeature, IMainPartFeature, - IProgrammaticIdentifierFeature + IProgrammaticIdentifierFeature, + IMinimumDocumentFeature { public PresentationDocumentFeatures(OpenXmlPackage package) : base(package) @@ -608,6 +546,27 @@ public PresentationDocumentFeatures(OpenXmlPackage package) "application/vnd.ms-powerpoint.addin.macroEnabled.main+xml" => PresentationDocumentType.AddIn, _ => default, }; + + bool IMinimumDocumentFeature.Validate() + { + if (DocumentType is PresentationDocumentType.Slideshow or PresentationDocumentType.MacroEnabledSlideshow or PresentationDocumentType.AddIn) + { + return false; + } + + return HasValidNotes(); + } + + private bool HasValidNotes() + { + const long MaxSize = 27273042316900; + + return MainPart?.Presentation?.NotesSize is + { + Cy: { HasValue: true, Value: >= 0 and <= MaxSize }, + Cx: { HasValue: true, Value: >= 0 and <= MaxSize } + }; + } } } } diff --git a/src/DocumentFormat.OpenXml/Packaging/TypedPackageFeatureCollection.cs b/src/DocumentFormat.OpenXml/Packaging/TypedPackageFeatureCollection.cs index 768fc5f45..80fe18f32 100644 --- a/src/DocumentFormat.OpenXml/Packaging/TypedPackageFeatureCollection.cs +++ b/src/DocumentFormat.OpenXml/Packaging/TypedPackageFeatureCollection.cs @@ -62,12 +62,18 @@ private TDocumentType EnsureDocumentType() return _documentType.Value; } - TDocumentType IDocumentTypeFeature.Current + protected TDocumentType DocumentType { get => EnsureDocumentType(); set => _documentType = value; } + TDocumentType IDocumentTypeFeature.Current + { + get => DocumentType; + set => DocumentType = value; + } + protected TMainPart? MainPart => Package.GetSubPartOfType(); OpenXmlPart? IMainPartFeature.Part => MainPart; From fca841ac871d77b8c4ee9e7043e4eaf4ef5170d4 Mon Sep 17 00:00:00 2001 From: Taylor Southwick Date: Mon, 12 May 2025 12:31:49 -0700 Subject: [PATCH 14/33] a bit more --- .../Packaging/PresentationDocument.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/DocumentFormat.OpenXml/Packaging/PresentationDocument.cs b/src/DocumentFormat.OpenXml/Packaging/PresentationDocument.cs index 5913f9570..904bdbcbb 100644 --- a/src/DocumentFormat.OpenXml/Packaging/PresentationDocument.cs +++ b/src/DocumentFormat.OpenXml/Packaging/PresentationDocument.cs @@ -559,12 +559,15 @@ bool IMinimumDocumentFeature.Validate() private bool HasValidNotes() { - const long MaxSize = 27273042316900; + const long MaxNoteSize = 27273042316900; - return MainPart?.Presentation?.NotesSize is + return MainPart is { - Cy: { HasValue: true, Value: >= 0 and <= MaxSize }, - Cx: { HasValue: true, Value: >= 0 and <= MaxSize } + Presentation.NotesSize: + { + Cy: { HasValue: true, Value: >= 0 and <= MaxNoteSize }, + Cx: { HasValue: true, Value: >= 0 and <= MaxNoteSize }, + } }; } } From a4b61a73d0b1dd045717eeadf20bfd166b282609 Mon Sep 17 00:00:00 2001 From: Michael Bowen <10384982+mikeebowen@users.noreply.github.com> Date: Mon, 12 May 2025 16:00:36 -0700 Subject: [PATCH 15/33] move VerfiyMinimumPackage to feature --- .../Packaging/OpenXmlPackage.cs | 2 +- .../Packaging/PresentationDocument.cs | 14 +++- .../Packaging/SpreadsheetDocument.cs | 75 +++++-------------- .../Packaging/WordprocessingDocument.cs | 53 ++++--------- .../ofapiTest/OpenXmlPackageTest.cs | 24 +++--- 5 files changed, 56 insertions(+), 112 deletions(-) diff --git a/src/DocumentFormat.OpenXml.Framework/Packaging/OpenXmlPackage.cs b/src/DocumentFormat.OpenXml.Framework/Packaging/OpenXmlPackage.cs index c4deda42a..1ee530103 100644 --- a/src/DocumentFormat.OpenXml.Framework/Packaging/OpenXmlPackage.cs +++ b/src/DocumentFormat.OpenXml.Framework/Packaging/OpenXmlPackage.cs @@ -115,7 +115,7 @@ internal void LoadAllParts() /// This method is intended to be overridden by derived classes to implement specific validation logic /// for different types of Open XML packages (e.g., Wordprocessing, Spreadsheet, or Presentation documents). /// - protected virtual void ThrowIfNotMinimumPackage() => throw new FileFormatException("The provided package does not conform to the minimum requirements for Office to open."); + protected virtual void ThrowIfNotMinimumPackage() => throw new FileFormatException("The provided package does not conform to the minimum requirements to open."); #region public methods diff --git a/src/DocumentFormat.OpenXml/Packaging/PresentationDocument.cs b/src/DocumentFormat.OpenXml/Packaging/PresentationDocument.cs index 904bdbcbb..525c250b2 100644 --- a/src/DocumentFormat.OpenXml/Packaging/PresentationDocument.cs +++ b/src/DocumentFormat.OpenXml/Packaging/PresentationDocument.cs @@ -549,9 +549,19 @@ public PresentationDocumentFeatures(OpenXmlPackage package) bool IMinimumDocumentFeature.Validate() { - if (DocumentType is PresentationDocumentType.Slideshow or PresentationDocumentType.MacroEnabledSlideshow or PresentationDocumentType.AddIn) + if (this.DocumentType == PresentationDocumentType.Slideshow) { - return false; + throw new NotSupportedException("Minimum package verification for PresentationDocumentType.Slideshow (.ppsx) is not supported."); + } + + if (this.DocumentType == PresentationDocumentType.MacroEnabledSlideshow) + { + throw new NotSupportedException("Minimum package verification for PresentationDocumentType.MacroEnabledSlideshow (.ppsm) is not supported."); + } + + if (this.DocumentType == PresentationDocumentType.AddIn) + { + throw new NotSupportedException("Minimum package verification for PresentationDocumentType.AddIn (.ppam) is not supported."); } return HasValidNotes(); diff --git a/src/DocumentFormat.OpenXml/Packaging/SpreadsheetDocument.cs b/src/DocumentFormat.OpenXml/Packaging/SpreadsheetDocument.cs index 3a58f7e8a..f1acfa35a 100644 --- a/src/DocumentFormat.OpenXml/Packaging/SpreadsheetDocument.cs +++ b/src/DocumentFormat.OpenXml/Packaging/SpreadsheetDocument.cs @@ -198,13 +198,6 @@ public static SpreadsheetDocument CreateFromTemplate(string path) public static SpreadsheetDocument Open(string path, bool isEditable, OpenSettings openSettings) => CreateDefaultBuilder() .UseSettings(openSettings) - .Use(package => - { - if (openSettings.VerifyMinimumPackage) - { - package.ThrowIfNotMinimumPackage(); - } - }) .Build() .Open(path, isEditable); @@ -222,13 +215,6 @@ public static SpreadsheetDocument Open(string path, bool isEditable, OpenSetting public static SpreadsheetDocument Open(Stream stream, bool isEditable, OpenSettings openSettings) => CreateDefaultBuilder() .UseSettings(openSettings) - .Use(package => - { - if (openSettings.VerifyMinimumPackage) - { - package.ThrowIfNotMinimumPackage(); - } - }) .Build() .Open(stream, isEditable); @@ -245,13 +231,6 @@ public static SpreadsheetDocument Open(Stream stream, bool isEditable, OpenSetti public static SpreadsheetDocument Open(Package package, OpenSettings openSettings) => CreateDefaultBuilder() .UseSettings(openSettings) - .Use(package => - { - if (openSettings.VerifyMinimumPackage) - { - package.ThrowIfNotMinimumPackage(); - } - }) .Build() .Open(package); @@ -289,39 +268,6 @@ public static SpreadsheetDocument Open(System.IO.Stream stream, bool isEditable) public static SpreadsheetDocument Open(System.IO.Packaging.Package package) => Open(package, new OpenSettings()); - /// - /// Throws a if the current - /// does not meet the minimum requirements for a valid package. - /// - /// - /// Thrown when the does not conform to the minimum requirements - /// for Excel to open. This includes: - /// - /// The document type is . - /// The is missing or does not contain a valid . - /// The in the first is missing. - /// - /// - /// - /// This method ensures that the contains the necessary parts and structure - /// to be opened with Excel. - /// - protected override void ThrowIfNotMinimumPackage() - { - if (this.DocumentType == SpreadsheetDocumentType.AddIn) - { - throw new NotSupportedException("Validation for SpreadsheetDocument.AddIn (.xlam) is not supported."); - } - - Sheet? sheet = this.WorkbookPart?.Workbook?.Sheets?.GetFirstChild(); - SheetData? sheetData = this.WorkbookPart?.WorksheetParts?.FirstOrDefaultAndMaxOne()?.Worksheet?.GetFirstChild(); - - if (sheet is null || sheetData is null) - { - throw new FileFormatException("The provided package does not conform to the minimum requirements for Excel to open."); - } - } - /// /// Changes the document type. /// @@ -560,7 +506,8 @@ public LabelInfoPart? LabelInfoPart [DocumentFormat.OpenXml.Generator.OpenXmlPackage("SpreadsheetDocument")] private partial class SpreadsheetDocumentFeatures : TypedPackageFeatureCollection, IApplicationTypeFeature, - IMainPartFeature + IMainPartFeature, + IMinimumDocumentFeature { public SpreadsheetDocumentFeatures(OpenXmlPackage package) : base(package) @@ -592,6 +539,24 @@ public SpreadsheetDocumentFeatures(OpenXmlPackage package) "application/vnd.ms-excel.addin.macroEnabled.main+xml" => SpreadsheetDocumentType.AddIn, _ => default, }; + + bool IMinimumDocumentFeature.Validate() + { + if (DocumentType == SpreadsheetDocumentType.AddIn) + { + throw new NotSupportedException("Validation for SpreadsheetDocument.AddIn (.xlam) is not supported."); + } + + Sheet? sheet = MainPart?.Workbook?.Sheets?.GetFirstChild(); + SheetData? sheetData = MainPart?.WorksheetParts?.FirstOrDefaultAndMaxOne()?.Worksheet?.GetFirstChild(); + + if (sheet is not null && sheetData is not null) + { + return true; + } + + return false; + } } } } diff --git a/src/DocumentFormat.OpenXml/Packaging/WordprocessingDocument.cs b/src/DocumentFormat.OpenXml/Packaging/WordprocessingDocument.cs index 2422c2d7a..94b9dcb59 100644 --- a/src/DocumentFormat.OpenXml/Packaging/WordprocessingDocument.cs +++ b/src/DocumentFormat.OpenXml/Packaging/WordprocessingDocument.cs @@ -264,13 +264,6 @@ public static WordprocessingDocument Open(Stream stream, bool isEditable) public static WordprocessingDocument Open(string path, bool isEditable, OpenSettings openSettings) => CreateDefaultBuilder() .UseSettings(openSettings) - .Use(package => - { - if (openSettings.VerifyMinimumPackage) - { - package.ThrowIfNotMinimumPackage(); - } - }) .Build() .Open(path, isEditable); @@ -288,13 +281,6 @@ public static WordprocessingDocument Open(string path, bool isEditable, OpenSett public static WordprocessingDocument Open(Stream stream, bool isEditable, OpenSettings openSettings) => CreateDefaultBuilder() .UseSettings(openSettings) - .Use(package => - { - if (openSettings.VerifyMinimumPackage) - { - package.ThrowIfNotMinimumPackage(); - } - }) .Build() .Open(stream, isEditable); @@ -311,13 +297,6 @@ public static WordprocessingDocument Open(Stream stream, bool isEditable, OpenSe public static WordprocessingDocument Open(Package package, OpenSettings openSettings) => CreateDefaultBuilder() .UseSettings(openSettings) - .Use(package => - { - if (openSettings.VerifyMinimumPackage) - { - package.ThrowIfNotMinimumPackage(); - } - }) .Build() .Open(package); @@ -332,24 +311,6 @@ public static WordprocessingDocument Open(Package package, OpenSettings openSett public static WordprocessingDocument Open(Package package) => Open(package, new OpenSettings()); - /// - /// Throws a if the current does not meet the minimum requirements for a valid package. - /// - /// - /// Thrown when the is missing or its is null. - /// - /// - /// This method ensures that the contains the necessary parts and structure - /// to be opened with Word. - /// - protected override void ThrowIfNotMinimumPackage() - { - if (this.MainDocumentPart?.Document?.Body is null) - { - throw new FileFormatException("The provided package does not conform to the minimum requirements for Word to open."); - } - } - /// /// Changes the document type. /// @@ -589,7 +550,8 @@ public LabelInfoPart? LabelInfoPart private partial class WordprocessingDocumentFeatures : TypedPackageFeatureCollection, IApplicationTypeFeature, IMainPartFeature, - IProgrammaticIdentifierFeature + IProgrammaticIdentifierFeature, + IMinimumDocumentFeature { public WordprocessingDocumentFeatures(OpenXmlPackage package) : base(package) @@ -621,6 +583,17 @@ public WordprocessingDocumentFeatures(OpenXmlPackage package) "application/vnd.ms-word.template.macroEnabledTemplate.main+xml" => WordprocessingDocumentType.MacroEnabledTemplate, _ => default, }; + + bool IMinimumDocumentFeature.Validate() + { + return MainPart is + { + Document: + { + Body: { } + } + }; + } } } } diff --git a/test/DocumentFormat.OpenXml.Tests/ofapiTest/OpenXmlPackageTest.cs b/test/DocumentFormat.OpenXml.Tests/ofapiTest/OpenXmlPackageTest.cs index 1a0b190c1..458b36ad9 100644 --- a/test/DocumentFormat.OpenXml.Tests/ofapiTest/OpenXmlPackageTest.cs +++ b/test/DocumentFormat.OpenXml.Tests/ofapiTest/OpenXmlPackageTest.cs @@ -952,7 +952,7 @@ public void VerifyMinimumPackageTest_ValidDocumentPackage_DoesNotThrow_Wordproce } } - private readonly string minPackageWordExMsg = "The provided package does not conform to the minimum requirements for Word to open."; + private readonly string minPackageExMsg = "The provided package does not conform to the minimum requirements to open."; [Fact] public void VerifyMinimumPackageTest_InValidDocumentPath_Throws_Wordprocessing() @@ -971,7 +971,7 @@ public void VerifyMinimumPackageTest_InValidDocumentPath_Throws_Wordprocessing() using WordprocessingDocument wpd = WordprocessingDocument.Open(path, false, new OpenSettings() { VerifyMinimumPackage = true }); }); - Assert.Equal(minPackageWordExMsg, ex.Message); + Assert.Equal(minPackageExMsg, ex.Message); } [Fact] @@ -991,7 +991,7 @@ public void VerifyMinimumPackageTest_InValidDocumentStream_Throws_Wordprocessing using WordprocessingDocument wpd = WordprocessingDocument.Open(stream, false, new OpenSettings() { VerifyMinimumPackage = true }); }); - Assert.Equal(minPackageWordExMsg, ex.Message); + Assert.Equal(minPackageExMsg, ex.Message); } } @@ -1014,7 +1014,7 @@ public void VerifyMinimumPackageTest_InValidDocumentPackage_Throws_Wordprocessin using WordprocessingDocument wpd = WordprocessingDocument.Open(package, new OpenSettings() { VerifyMinimumPackage = true }); }); - Assert.Equal(minPackageWordExMsg, ex.Message); + Assert.Equal(minPackageExMsg, ex.Message); } } } @@ -1095,8 +1095,6 @@ public void VerifyMinimumPackageTest_ValidDocumentPackage_DoesNotThrow_Spreadshe } } - private readonly string spreadsheetExMsg = "The provided package does not conform to the minimum requirements for Excel to open."; - [Fact] public void VerifyMinimumPackageTest_InValidDocumentPath_Throws_Spreadsheet() { @@ -1116,7 +1114,7 @@ public void VerifyMinimumPackageTest_InValidDocumentPath_Throws_Spreadsheet() using SpreadsheetDocument ssd = SpreadsheetDocument.Open(path, false, new OpenSettings() { VerifyMinimumPackage = true }); }); - Assert.Equal(spreadsheetExMsg, ex.Message); + Assert.Equal(minPackageExMsg, ex.Message); } [Fact] @@ -1138,7 +1136,7 @@ public void VerifyMinimumPackageTest_InValidDocumentStream_Throws_Spreadsheet() using SpreadsheetDocument ssd = SpreadsheetDocument.Open(stream, false, new OpenSettings() { VerifyMinimumPackage = true }); }); - Assert.Equal(spreadsheetExMsg, ex.Message); + Assert.Equal(minPackageExMsg, ex.Message); } } @@ -1162,7 +1160,7 @@ public void VerifyMinimumPackageTest_InValidDocumentPackage_Throws_Spreadsheet() using SpreadsheetDocument wpd = SpreadsheetDocument.Open(package, new OpenSettings() { VerifyMinimumPackage = true }); }); - Assert.Equal(spreadsheetExMsg, ex.Message); + Assert.Equal(minPackageExMsg, ex.Message); } } } @@ -1270,8 +1268,6 @@ public void VerifyMinimumPackageTest_ValidDocumentPackage_DoesNotThrow_Presentat } } - private readonly string presentationExMsg = "The provided package does not conform to the minimum requirements for PowerPoint to open."; - [Fact] public void VerifyMinimumPackageTest_InValidDocumentPath_Throws_Presentation() { @@ -1292,7 +1288,7 @@ public void VerifyMinimumPackageTest_InValidDocumentPath_Throws_Presentation() using PresentationDocument ssd = PresentationDocument.Open(path, false, new OpenSettings() { VerifyMinimumPackage = true }); }); - Assert.Equal(presentationExMsg, ex.Message); + Assert.Equal(minPackageExMsg, ex.Message); } [Fact] @@ -1314,7 +1310,7 @@ public void VerifyMinimumPackageTest_InValidDocumentStream_Throws_Presentation() using PresentationDocument ssd = PresentationDocument.Open(stream, false, new OpenSettings() { VerifyMinimumPackage = true }); }); - Assert.Equal(presentationExMsg, ex.Message); + Assert.Equal(minPackageExMsg, ex.Message); } } @@ -1339,7 +1335,7 @@ public void VerifyMinimumPackageTest_InValidDocumentPackage_Throws_Presentation( using PresentationDocument pd = PresentationDocument.Open(package, new OpenSettings() { VerifyMinimumPackage = true }); }); - Assert.Equal(presentationExMsg, ex.Message); + Assert.Equal(minPackageExMsg, ex.Message); } } } From 8e1d50de387a1d9054f16acfb3cceff7db08a709 Mon Sep 17 00:00:00 2001 From: Michael Bowen <10384982+mikeebowen@users.noreply.github.com> Date: Mon, 12 May 2025 16:19:37 -0700 Subject: [PATCH 16/33] remove ThrowIfNotMinimumPackage from OpenXmlPackage --- .../Packaging/OpenXmlPackage.cs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/DocumentFormat.OpenXml.Framework/Packaging/OpenXmlPackage.cs b/src/DocumentFormat.OpenXml.Framework/Packaging/OpenXmlPackage.cs index 1ee530103..c8ce6524a 100644 --- a/src/DocumentFormat.OpenXml.Framework/Packaging/OpenXmlPackage.cs +++ b/src/DocumentFormat.OpenXml.Framework/Packaging/OpenXmlPackage.cs @@ -105,18 +105,6 @@ internal void LoadAllParts() } } - /// - /// Throws a if the current does not meet the minimum requirements for a valid package. - /// - /// - /// Thrown when the package does not conform to the minimum requirements for Office to open. - /// - /// - /// This method is intended to be overridden by derived classes to implement specific validation logic - /// for different types of Open XML packages (e.g., Wordprocessing, Spreadsheet, or Presentation documents). - /// - protected virtual void ThrowIfNotMinimumPackage() => throw new FileFormatException("The provided package does not conform to the minimum requirements to open."); - #region public methods /// From 8d1d6ea904cd10b438a5bc4b27270f7873358f8d Mon Sep 17 00:00:00 2001 From: Michael Bowen <10384982+mikeebowen@users.noreply.github.com> Date: Wed, 14 May 2025 15:24:31 -0700 Subject: [PATCH 17/33] fix merge conflict --- .../Packaging/OpenXmlPackage.cs | 3 +++ .../Validation/DocumentValidator.cs | 7 +++++++ .../Validation/ValidationSettings.cs | 7 +++++++ .../Packaging/WordprocessingDocument.cs | 3 +++ 4 files changed, 20 insertions(+) diff --git a/src/DocumentFormat.OpenXml.Framework/Packaging/OpenXmlPackage.cs b/src/DocumentFormat.OpenXml.Framework/Packaging/OpenXmlPackage.cs index 1ee530103..e370c55ba 100644 --- a/src/DocumentFormat.OpenXml.Framework/Packaging/OpenXmlPackage.cs +++ b/src/DocumentFormat.OpenXml.Framework/Packaging/OpenXmlPackage.cs @@ -3,6 +3,7 @@ using DocumentFormat.OpenXml; using DocumentFormat.OpenXml.Features; +using DocumentFormat.OpenXml.Validation; using System; using System.Collections.Generic; using System.IO; @@ -624,5 +625,7 @@ public void Save() /// public override IFeatureCollection Features => _features ??= new PackageFeatureCollection(this); + + internal virtual IEnumerable VerifyMinimumDocument() => Enumerable.Empty(); } } diff --git a/src/DocumentFormat.OpenXml.Framework/Validation/DocumentValidator.cs b/src/DocumentFormat.OpenXml.Framework/Validation/DocumentValidator.cs index df3ea4e46..b50aa2d60 100644 --- a/src/DocumentFormat.OpenXml.Framework/Validation/DocumentValidator.cs +++ b/src/DocumentFormat.OpenXml.Framework/Validation/DocumentValidator.cs @@ -275,5 +275,12 @@ private static string GetPartUri(OpenXmlPart? part) // Example: WordprocessingCommentsPart{/word/comments.xml} return SR.Format("{0}{1}{2}", '{', part.Uri, '}'); } + + private IEnumerable VerifyMinimumPackage(OpenXmlPackage package) + { + if (package is PresentationDocument) + { + } + } } } diff --git a/src/DocumentFormat.OpenXml.Framework/Validation/ValidationSettings.cs b/src/DocumentFormat.OpenXml.Framework/Validation/ValidationSettings.cs index e4423c5f1..96939a7cd 100644 --- a/src/DocumentFormat.OpenXml.Framework/Validation/ValidationSettings.cs +++ b/src/DocumentFormat.OpenXml.Framework/Validation/ValidationSettings.cs @@ -32,5 +32,12 @@ public ValidationSettings(FileFormatVersions fileFormat) /// Default is 1000. A zero (0) value means no limitation. /// public int MaxNumberOfErrors { get; set; } + + /// + /// Gets or sets a value indicating whether the validator should verify that the package meets the minimum requirements + /// for the specified file format. When set to true, the validation process will include checks to ensure + /// the document structure is sufficient for the target application to open the file. + /// + public bool VerifyMinimumPackage { get; set; } } } diff --git a/src/DocumentFormat.OpenXml/Packaging/WordprocessingDocument.cs b/src/DocumentFormat.OpenXml/Packaging/WordprocessingDocument.cs index 94b9dcb59..52df68b5d 100644 --- a/src/DocumentFormat.OpenXml/Packaging/WordprocessingDocument.cs +++ b/src/DocumentFormat.OpenXml/Packaging/WordprocessingDocument.cs @@ -3,8 +3,11 @@ using DocumentFormat.OpenXml.Builder; using DocumentFormat.OpenXml.Features; +using DocumentFormat.OpenXml.Validation; using DocumentFormat.OpenXml.Wordprocessing; using System; +using System.Collections; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; using System.IO.Packaging; From 04ef42bc4d2344b6ce5627f5ebd9755a95648a8a Mon Sep 17 00:00:00 2001 From: Michael Bowen <10384982+mikeebowen@users.noreply.github.com> Date: Thu, 15 May 2025 10:05:12 -0700 Subject: [PATCH 18/33] verify wordprocessing document --- .../Packaging/OpenXmlPackage.cs | 5 +++- .../Validation/DocumentValidator.cs | 9 ++----- .../ValidationResources.Designer.cs | 9 +++++++ .../Validation/ValidationResources.resx | 3 +++ .../Packaging/WordprocessingDocument.cs | 25 +++++++++++++++++++ 5 files changed, 43 insertions(+), 8 deletions(-) diff --git a/src/DocumentFormat.OpenXml.Framework/Packaging/OpenXmlPackage.cs b/src/DocumentFormat.OpenXml.Framework/Packaging/OpenXmlPackage.cs index e370c55ba..6d4f68217 100644 --- a/src/DocumentFormat.OpenXml.Framework/Packaging/OpenXmlPackage.cs +++ b/src/DocumentFormat.OpenXml.Framework/Packaging/OpenXmlPackage.cs @@ -626,6 +626,9 @@ public void Save() /// public override IFeatureCollection Features => _features ??= new PackageFeatureCollection(this); - internal virtual IEnumerable VerifyMinimumDocument() => Enumerable.Empty(); + internal virtual void VerifyMinimumDocument(ValidationContext validationContext) + { + return; + } } } diff --git a/src/DocumentFormat.OpenXml.Framework/Validation/DocumentValidator.cs b/src/DocumentFormat.OpenXml.Framework/Validation/DocumentValidator.cs index b50aa2d60..14f09f3f3 100644 --- a/src/DocumentFormat.OpenXml.Framework/Validation/DocumentValidator.cs +++ b/src/DocumentFormat.OpenXml.Framework/Validation/DocumentValidator.cs @@ -50,6 +50,8 @@ public List Validate(OpenXmlPackage document, ValidationSet // integrate the package validation. ValidatePackageStructure(document, context); + document.VerifyMinimumDocument(context); + foreach (var part in PartsToBeValidated(document)) { // traverse from the part root element (by DOM or by Reader) in post-order @@ -275,12 +277,5 @@ private static string GetPartUri(OpenXmlPart? part) // Example: WordprocessingCommentsPart{/word/comments.xml} return SR.Format("{0}{1}{2}", '{', part.Uri, '}'); } - - private IEnumerable VerifyMinimumPackage(OpenXmlPackage package) - { - if (package is PresentationDocument) - { - } - } } } diff --git a/src/DocumentFormat.OpenXml.Framework/Validation/ValidationResources.Designer.cs b/src/DocumentFormat.OpenXml.Framework/Validation/ValidationResources.Designer.cs index 0f07d0a74..c7fcd18d8 100644 --- a/src/DocumentFormat.OpenXml.Framework/Validation/ValidationResources.Designer.cs +++ b/src/DocumentFormat.OpenXml.Framework/Validation/ValidationResources.Designer.cs @@ -438,6 +438,15 @@ internal static string Sch_MinLengthConstraintFailed { } } + /// + /// Looks up a localized string similar to The '{0}' part is missing the root element '{1}'. + /// + internal static string Sch_MissingRootElement { + get { + return ResourceManager.GetString("Sch_MissingRootElement", resourceCulture); + } + } + /// /// Looks up a localized string similar to The required attribute '{0}' is missing.. /// diff --git a/src/DocumentFormat.OpenXml.Framework/Validation/ValidationResources.resx b/src/DocumentFormat.OpenXml.Framework/Validation/ValidationResources.resx index ed8489361..7ed9b7698 100644 --- a/src/DocumentFormat.OpenXml.Framework/Validation/ValidationResources.resx +++ b/src/DocumentFormat.OpenXml.Framework/Validation/ValidationResources.resx @@ -342,4 +342,7 @@ Cell contents have invalid value '{0}' for type '{1}'. + + The '{0}' part is missing the root element '{1}' + \ No newline at end of file diff --git a/src/DocumentFormat.OpenXml/Packaging/WordprocessingDocument.cs b/src/DocumentFormat.OpenXml/Packaging/WordprocessingDocument.cs index 52df68b5d..f26b8c2b8 100644 --- a/src/DocumentFormat.OpenXml/Packaging/WordprocessingDocument.cs +++ b/src/DocumentFormat.OpenXml/Packaging/WordprocessingDocument.cs @@ -546,6 +546,31 @@ public LabelInfoPart? LabelInfoPart get { return GetSubPartOfType(); } } + internal override void VerifyMinimumDocument(ValidationContext validationContext) + { + if (this.MainDocumentPart?.Document is null) + { + validationContext.AddError(new() + { + ErrorType = ValidationErrorType.Schema, + Id = "Sch_MissingRootElement", + Part = this.MainDocumentPart, + Description = SR.Format(ValidationResources.Sch_MissingRootElement, typeof(MainDocumentPart), typeof(Document)), + }); + } + + if (this.MainDocumentPart?.Document is not null && this.MainDocumentPart.Document?.Body is null) + { + validationContext.AddError(new() + { + ErrorType = ValidationErrorType.Schema, + Id = "Sch_IncompleteContentExpectingComplex", + Part = this.MainDocumentPart, + Description = SR.Format(ValidationResources.Sch_IncompleteContentExpectingComplex, typeof(Document)), + }); + } + } + /// public override IFeatureCollection Features => _features ??= new WordprocessingDocumentFeatures(this); From a23656688add6740be6d53f01ab6247043bcb5f1 Mon Sep 17 00:00:00 2001 From: Michael Bowen <10384982+mikeebowen@users.noreply.github.com> Date: Thu, 15 May 2025 10:58:42 -0700 Subject: [PATCH 19/33] add minimum document validation for PowerPoint and Excel --- .../ValidationResources.Designer.cs | 18 ++++++------- .../Validation/ValidationResources.resx | 4 +-- .../Packaging/PresentationDocument.cs | 26 +++++++++++++++++++ .../Packaging/SpreadsheetDocument.cs | 21 +++++++++++++++ .../Packaging/WordprocessingDocument.cs | 21 ++++++--------- 5 files changed, 66 insertions(+), 24 deletions(-) diff --git a/src/DocumentFormat.OpenXml.Framework/Validation/ValidationResources.Designer.cs b/src/DocumentFormat.OpenXml.Framework/Validation/ValidationResources.Designer.cs index c7fcd18d8..2a19d601b 100644 --- a/src/DocumentFormat.OpenXml.Framework/Validation/ValidationResources.Designer.cs +++ b/src/DocumentFormat.OpenXml.Framework/Validation/ValidationResources.Designer.cs @@ -348,6 +348,15 @@ internal static string Sch_IncompleteContentExpectingComplex { } } + /// + /// Looks up a localized string similar to The provided package does not conform to the minimum requirements for {0} to open.. + /// + internal static string Sch_IncompletePackage { + get { + return ResourceManager.GetString("Sch_IncompletePackage", resourceCulture); + } + } + /// /// Looks up a localized string similar to The element '{0}' is a leaf element and cannot contain children.. /// @@ -438,15 +447,6 @@ internal static string Sch_MinLengthConstraintFailed { } } - /// - /// Looks up a localized string similar to The '{0}' part is missing the root element '{1}'. - /// - internal static string Sch_MissingRootElement { - get { - return ResourceManager.GetString("Sch_MissingRootElement", resourceCulture); - } - } - /// /// Looks up a localized string similar to The required attribute '{0}' is missing.. /// diff --git a/src/DocumentFormat.OpenXml.Framework/Validation/ValidationResources.resx b/src/DocumentFormat.OpenXml.Framework/Validation/ValidationResources.resx index 7ed9b7698..0ab938c17 100644 --- a/src/DocumentFormat.OpenXml.Framework/Validation/ValidationResources.resx +++ b/src/DocumentFormat.OpenXml.Framework/Validation/ValidationResources.resx @@ -342,7 +342,7 @@ Cell contents have invalid value '{0}' for type '{1}'. - - The '{0}' part is missing the root element '{1}' + + The provided package does not conform to the minimum requirements for {0} to open. \ No newline at end of file diff --git a/src/DocumentFormat.OpenXml/Packaging/PresentationDocument.cs b/src/DocumentFormat.OpenXml/Packaging/PresentationDocument.cs index 525c250b2..e79ddbfc7 100644 --- a/src/DocumentFormat.OpenXml/Packaging/PresentationDocument.cs +++ b/src/DocumentFormat.OpenXml/Packaging/PresentationDocument.cs @@ -4,6 +4,7 @@ using DocumentFormat.OpenXml.Builder; using DocumentFormat.OpenXml.Features; using DocumentFormat.OpenXml.Presentation; +using DocumentFormat.OpenXml.Validation; using System; using System.Diagnostics.CodeAnalysis; using System.IO; @@ -500,6 +501,31 @@ public LabelInfoPart? LabelInfoPart get { return GetSubPartOfType(); } } + internal override void VerifyMinimumDocument(ValidationContext validationContext) + { + if (this.DocumentType is not PresentationDocumentType.Slideshow && + this.DocumentType is not PresentationDocumentType.MacroEnabledTemplate && + this.DocumentType is not PresentationDocumentType.AddIn) + { + if (this.PresentationPart is not { + Presentation.NotesSize: + { + Cx: { HasValue: true }, + Cy: { HasValue: true }, + } + }) + { + validationContext.AddError(new() + { + ErrorType = ValidationErrorType.Schema, + Id = "Sch_IncompletePackage", + Part = this.PresentationPart, + Description = SR.Format(ValidationResources.Sch_IncompletePackage, "PowerPoint"), + }); + } + } + } + /// public override IFeatureCollection Features => _features ??= new PresentationDocumentFeatures(this); diff --git a/src/DocumentFormat.OpenXml/Packaging/SpreadsheetDocument.cs b/src/DocumentFormat.OpenXml/Packaging/SpreadsheetDocument.cs index f1acfa35a..aa5fa4fbd 100644 --- a/src/DocumentFormat.OpenXml/Packaging/SpreadsheetDocument.cs +++ b/src/DocumentFormat.OpenXml/Packaging/SpreadsheetDocument.cs @@ -4,6 +4,7 @@ using DocumentFormat.OpenXml.Builder; using DocumentFormat.OpenXml.Features; using DocumentFormat.OpenXml.Spreadsheet; +using DocumentFormat.OpenXml.Validation; using System; using System.Diagnostics.CodeAnalysis; using System.IO; @@ -500,6 +501,26 @@ public LabelInfoPart? LabelInfoPart get { return GetSubPartOfType(); } } + internal override void VerifyMinimumDocument(ValidationContext validationContext) + { + if (this.DocumentType != SpreadsheetDocumentType.AddIn) + { + Sheet? sheet = this.WorkbookPart?.Workbook?.Sheets?.GetFirstChild(); + SheetData? sheetData = this.WorkbookPart?.WorksheetParts?.FirstOrDefaultAndMaxOne()?.Worksheet?.GetFirstChild(); + + if (sheet is not null && sheetData is not null) + { + validationContext.AddError(new() + { + ErrorType = ValidationErrorType.Schema, + Id = "Sch_IncompletePackage", + Part = this.WorkbookPart, + Description = SR.Format(ValidationResources.Sch_IncompletePackage, "Excel"), + }); + } + } + } + /// public override IFeatureCollection Features => _features ??= new SpreadsheetDocumentFeatures(this); diff --git a/src/DocumentFormat.OpenXml/Packaging/WordprocessingDocument.cs b/src/DocumentFormat.OpenXml/Packaging/WordprocessingDocument.cs index f26b8c2b8..435ba9702 100644 --- a/src/DocumentFormat.OpenXml/Packaging/WordprocessingDocument.cs +++ b/src/DocumentFormat.OpenXml/Packaging/WordprocessingDocument.cs @@ -548,25 +548,20 @@ public LabelInfoPart? LabelInfoPart internal override void VerifyMinimumDocument(ValidationContext validationContext) { - if (this.MainDocumentPart?.Document is null) - { - validationContext.AddError(new() + if (this.MainDocumentPart is not { - ErrorType = ValidationErrorType.Schema, - Id = "Sch_MissingRootElement", - Part = this.MainDocumentPart, - Description = SR.Format(ValidationResources.Sch_MissingRootElement, typeof(MainDocumentPart), typeof(Document)), - }); - } - - if (this.MainDocumentPart?.Document is not null && this.MainDocumentPart.Document?.Body is null) + Document: + { + Body: { } + } + }) { validationContext.AddError(new() { ErrorType = ValidationErrorType.Schema, - Id = "Sch_IncompleteContentExpectingComplex", + Id = "Sch_IncompletePackage", Part = this.MainDocumentPart, - Description = SR.Format(ValidationResources.Sch_IncompleteContentExpectingComplex, typeof(Document)), + Description = SR.Format(ValidationResources.Sch_IncompletePackage, "Word"), }); } } From af392cc4bd3e35b27f466dc057ff12ead649bb6b Mon Sep 17 00:00:00 2001 From: Michael Bowen <10384982+mikeebowen@users.noreply.github.com> Date: Thu, 15 May 2025 14:00:03 -0700 Subject: [PATCH 20/33] add tests --- .../Packaging/PresentationDocument.cs | 14 +- .../Packaging/SpreadsheetDocument.cs | 2 +- .../ofapiTest/OpenXmlValidatorTest.cs | 328 ++++++++++++++++++ 3 files changed, 338 insertions(+), 6 deletions(-) diff --git a/src/DocumentFormat.OpenXml/Packaging/PresentationDocument.cs b/src/DocumentFormat.OpenXml/Packaging/PresentationDocument.cs index e79ddbfc7..b91fddeac 100644 --- a/src/DocumentFormat.OpenXml/Packaging/PresentationDocument.cs +++ b/src/DocumentFormat.OpenXml/Packaging/PresentationDocument.cs @@ -9,6 +9,7 @@ using System.Diagnostics.CodeAnalysis; using System.IO; using System.IO.Packaging; +using System.Linq; using System.Reflection; namespace DocumentFormat.OpenXml.Packaging @@ -507,13 +508,16 @@ internal override void VerifyMinimumDocument(ValidationContext validationContext this.DocumentType is not PresentationDocumentType.MacroEnabledTemplate && this.DocumentType is not PresentationDocumentType.AddIn) { - if (this.PresentationPart is not { - Presentation.NotesSize: + if (this.PresentationPart is not { - Cx: { HasValue: true }, - Cy: { HasValue: true }, + Presentation.NotesSize: + { + Cx: { HasValue: true }, + Cy: { HasValue: true }, + } } - }) + + || !(this.PresentationPart.SlideMasterParts?.Any() ?? false)) { validationContext.AddError(new() { diff --git a/src/DocumentFormat.OpenXml/Packaging/SpreadsheetDocument.cs b/src/DocumentFormat.OpenXml/Packaging/SpreadsheetDocument.cs index aa5fa4fbd..be5602de8 100644 --- a/src/DocumentFormat.OpenXml/Packaging/SpreadsheetDocument.cs +++ b/src/DocumentFormat.OpenXml/Packaging/SpreadsheetDocument.cs @@ -508,7 +508,7 @@ internal override void VerifyMinimumDocument(ValidationContext validationContext Sheet? sheet = this.WorkbookPart?.Workbook?.Sheets?.GetFirstChild(); SheetData? sheetData = this.WorkbookPart?.WorksheetParts?.FirstOrDefaultAndMaxOne()?.Worksheet?.GetFirstChild(); - if (sheet is not null && sheetData is not null) + if (sheet is null || sheetData is null) { validationContext.AddError(new() { diff --git a/test/DocumentFormat.OpenXml.Tests/ofapiTest/OpenXmlValidatorTest.cs b/test/DocumentFormat.OpenXml.Tests/ofapiTest/OpenXmlValidatorTest.cs index 4fdeb0daa..51a8fcb82 100644 --- a/test/DocumentFormat.OpenXml.Tests/ofapiTest/OpenXmlValidatorTest.cs +++ b/test/DocumentFormat.OpenXml.Tests/ofapiTest/OpenXmlValidatorTest.cs @@ -9,12 +9,14 @@ using System; using System.Collections.Generic; using System.IO; +using System.IO.Packaging; using System.Linq; using System.Threading; using System.Xml; using Xunit; using static DocumentFormat.OpenXml.Tests.TestAssets; +using Path = System.IO.Path; namespace DocumentFormat.OpenXml.Tests { @@ -3832,5 +3834,331 @@ public void VersionMismatchPartValidatingTest() Assert.Throws(() => O14Validator.Validate(wordTestDocument.MainDocumentPart, TestContext.Current.CancellationToken)); } } + + [Fact] + public void VerifyMinimumPackageTest_ValidDocumentPath_NoErrors_Wordprocessing() + { + // Arrange + string path = string.Concat(Path.GetTempPath(), "valid.docx"); + + using (WordprocessingDocument document = WordprocessingDocument.Create(path, WordprocessingDocumentType.Document)) + { + document.AddMainDocumentPart().Document = new Document(new Body()); + + OpenXmlValidator validator = new(); + + // Act + IEnumerable errors = validator.Validate(document, TestContext.Current.CancellationToken); + + // Assert + Assert.Empty(errors); + } + } + + [Fact] + public void VerifyMinimumPackageTest_InValidDocumentPath_HasError_Wordprocessing() + { + // Arrange + string path = string.Concat(Path.GetTempPath(), "invalid.docx"); + + using (WordprocessingDocument document = WordprocessingDocument.Create(path, WordprocessingDocumentType.Document)) + { + document.AddMainDocumentPart().Document = new Document(); + OpenXmlValidator validator = new(); + + // Act + IEnumerable errors = validator.Validate(document, TestContext.Current.CancellationToken); + + // Assert + Assert.Single(errors); + Assert.Equal(errors.FirstOrDefault()?.Description, "The provided package does not conform to the minimum requirements for Word to open."); + } + } + + [Fact] + public void VerifyMinimumPackageTest_ValidDocumentPath_NoErrors_Spreadsheet() + { + // Arrange + string path = string.Concat(Path.GetTempPath(), "valid.xlsx"); + + using (SpreadsheetDocument document = SpreadsheetDocument.Create(path, SpreadsheetDocumentType.Workbook)) + { + WorkbookPart wbp = document.AddWorkbookPart(); + WorksheetPart wsp = wbp.AddNewPart(); + wbp.Workbook = new Spreadsheet.Workbook(new Spreadsheet.Sheets(new Spreadsheet.Sheet() { Id = wbp.GetIdOfPart(wsp), SheetId = 1, Name = "Sheet1" })); + wsp.Worksheet = new Spreadsheet.Worksheet(new Spreadsheet.SheetData()); + OpenXmlValidator validator = new(); + + // Act + IEnumerable errors = validator.Validate(document, TestContext.Current.CancellationToken); + + // Assert + Assert.Empty(errors); + } + } + + [Fact] + public void VerifyMinimumPackageTest_InValidDocumentPath_HasError_Spreadsheet() + { + // Arrange + string path = string.Concat(Path.GetTempPath(), "invalid.xlsx"); + + using (SpreadsheetDocument document = SpreadsheetDocument.Create(path, SpreadsheetDocumentType.MacroEnabledWorkbook)) + { + WorkbookPart wbp = document.AddWorkbookPart(); + WorksheetPart wsp = wbp.AddNewPart(); + wbp.Workbook = new Spreadsheet.Workbook(new Spreadsheet.Sheets(new Spreadsheet.Sheet() { Id = wbp.GetIdOfPart(wsp), SheetId = 1, Name = "Sheet1" })); + OpenXmlValidator validator = new(); + + // Act + IEnumerable errors = validator.Validate(document, TestContext.Current.CancellationToken); + + // Assert + Assert.Single(errors); + Assert.Equal("The provided package does not conform to the minimum requirements for Excel to open.", errors.FirstOrDefault()?.Description); + } + } + + [Fact] + public void VerifyMinimumPackageTest_ValidDocumentPath_NoErrors_Presentation() + { + // Arrange + string path = string.Concat(Path.GetTempPath(), "valid.pptx"); + + using (PresentationDocument document = PresentationDocument.Create(path, PresentationDocumentType.Presentation)) + { + // create presentation part + PresentationPart presentationPart = document.AddPresentationPart(); + presentationPart.Presentation = new DocumentFormat.OpenXml.Presentation.Presentation(); + presentationPart.Presentation.AddChild(new DocumentFormat.OpenXml.Presentation.NotesSize() { Cx = 913607, Cy = 913607 }); + SlideMasterPart slideMasterPart = presentationPart.AddNewPart(); + slideMasterPart.SlideMaster = new Presentation.SlideMaster( + new Presentation.CommonSlideData( + new Presentation.ShapeTree( + new DocumentFormat.OpenXml.Presentation.NonVisualGroupShapeProperties( + new DocumentFormat.OpenXml.Presentation.NonVisualDrawingProperties() { Id = (UInt32Value)1U, Name = string.Empty }, + new DocumentFormat.OpenXml.Presentation.NonVisualGroupShapeDrawingProperties(), + new DocumentFormat.OpenXml.Presentation.ApplicationNonVisualDrawingProperties()), + new DocumentFormat.OpenXml.Presentation.GroupShapeProperties())), + new DocumentFormat.OpenXml.Presentation.ColorMap() + { + Background1 = DocumentFormat.OpenXml.Drawing.ColorSchemeIndexValues.Light1, + Background2 = DocumentFormat.OpenXml.Drawing.ColorSchemeIndexValues.Light2, + Text1 = DocumentFormat.OpenXml.Drawing.ColorSchemeIndexValues.Dark1, + Text2 = DocumentFormat.OpenXml.Drawing.ColorSchemeIndexValues.Dark2, + Accent1 = DocumentFormat.OpenXml.Drawing.ColorSchemeIndexValues.Accent1, + Accent2 = DocumentFormat.OpenXml.Drawing.ColorSchemeIndexValues.Accent2, + Accent3 = DocumentFormat.OpenXml.Drawing.ColorSchemeIndexValues.Accent3, + Accent4 = DocumentFormat.OpenXml.Drawing.ColorSchemeIndexValues.Accent4, + Accent5 = DocumentFormat.OpenXml.Drawing.ColorSchemeIndexValues.Accent5, + Accent6 = DocumentFormat.OpenXml.Drawing.ColorSchemeIndexValues.Accent6, + Hyperlink = DocumentFormat.OpenXml.Drawing.ColorSchemeIndexValues.Hyperlink, + FollowedHyperlink = DocumentFormat.OpenXml.Drawing.ColorSchemeIndexValues.FollowedHyperlink, + }); + OpenXmlValidator validator = new(); + + // Act + IEnumerable errors = validator.Validate(document, TestContext.Current.CancellationToken); + + // Assert + Assert.Empty(errors); + } + } + + //[Fact] + //public void VerifyMinimumPackageTest_ValidDocumentStream_NoErrors_Presentation() + //{ + // // Arrange + // using (Stream stream = new MemoryStream()) + // { + // using (PresentationDocument presentationDocument = PresentationDocument.Create(stream, PresentationDocumentType.Presentation)) + // { + // // create presentation part + // PresentationPart presentationPart = presentationDocument.AddPresentationPart(); + // presentationPart.Presentation = new Presentation.Presentation(); + // presentationPart.Presentation.AddChild(new Presentation.NotesSize() { Cx = 913607, Cy = 913607 }); + // } + + // // Act + // Exception exception = Record.Exception(() => + // { + // using PresentationDocument pd = PresentationDocument.Open(stream, false, new OpenSettings() { VerifyMinimumPackage = true }); + // }); + + // // Assert + // Assert.Null(exception); + // } + //} + + //[Fact] + //public void VerifyMinimumPackageTest_ValidDocumentPackage_NoErrors_Presentation() + //{ + // // Arrange + // string path = Path.Combine(Path.GetTempPath(), "valid.xlsx"); + + // using (SpreadsheetDocument document = SpreadsheetDocument.Create(path, SpreadsheetDocumentType.Template)) + // { + // WorkbookPart wbp = document.AddWorkbookPart(); + // WorksheetPart wsp = wbp.AddNewPart(); + // wbp.Workbook = new Spreadsheet.Workbook(new Spreadsheet.Sheets(new Spreadsheet.Sheet() { Id = wbp.GetIdOfPart(wsp), SheetId = 1, Name = "Sheet1" })); + // wsp.Worksheet = new Spreadsheet.Worksheet(new Spreadsheet.SheetData()); + // } + + // // Act + // using (Package package = Package.Open(path)) + // { + // Exception exception = Record.Exception(() => + // { + // using SpreadsheetDocument ssd = SpreadsheetDocument.Open(package, new OpenSettings() { VerifyMinimumPackage = true }); + // }); + + // // Assert + // Assert.Null(exception); + // } + //} + + //[Fact] + //public void VerifyMinimumPackageTest_InValidDocumentPath_HasError_Presentation() + //{ + // // Arrange + // string path = string.Concat(Path.GetTempPath(), "invalid.pptx"); + + // using (PresentationDocument presentationDocument = PresentationDocument.Create(path, PresentationDocumentType.Presentation)) + // { + // // create presentation part + // PresentationPart presentationPart = presentationDocument.AddPresentationPart(); + // presentationPart.Presentation = new Presentation.Presentation(); + // presentationPart.Presentation.AddChild(new Presentation.NotesSize() { Cx = -2, Cy = 913607 }); + // } + + // // Act and Assert + // FileFormatException ex = Assert.Throws(() => + // { + // using PresentationDocument ssd = PresentationDocument.Open(path, false, new OpenSettings() { VerifyMinimumPackage = true }); + // }); + + // Assert.Equal(minPackageExMsg, ex.Message); + //} + + //[Fact] + //public void VerifyMinimumPackageTest_InValidDocumentStream_HasError_Presentation() + //{ + // // Arrange + // using (Stream stream = new MemoryStream()) + // { + // using (PresentationDocument presentationDocument = PresentationDocument.Create(stream, PresentationDocumentType.Presentation)) + // { + // // create presentation part + // PresentationPart presentationPart = presentationDocument.AddPresentationPart(); + // presentationPart.Presentation = new Presentation.Presentation(); + // } + + // // Act and Assert + // FileFormatException ex = Assert.Throws(() => + // { + // using PresentationDocument ssd = PresentationDocument.Open(stream, false, new OpenSettings() { VerifyMinimumPackage = true }); + // }); + + // Assert.Equal(minPackageExMsg, ex.Message); + // } + //} + + //[Fact] + //public void VerifyMinimumPackageTest_InValidDocumentPackage_HasError_Presentation() + //{ + // // Arrange + // using (Stream stream = new MemoryStream()) + // { + // using (PresentationDocument presentationDocument = PresentationDocument.Create(stream, PresentationDocumentType.Presentation)) + // { + // // create presentation part + // PresentationPart presentationPart = presentationDocument.AddPresentationPart(); + // presentationPart.Presentation = new Presentation.Presentation(); + // } + + // // Act and Assert + // using (Package package = Package.Open(stream)) + // { + // FileFormatException ex = Assert.Throws(() => + // { + // using PresentationDocument pd = PresentationDocument.Open(package, new OpenSettings() { VerifyMinimumPackage = true }); + // }); + + // Assert.Equal(minPackageExMsg, ex.Message); + // } + // } + //} + + //[Fact] + //public void VerifyMinimumPackageTest_AddInDocumentPackage_HasError_Presentation() + //{ + // // Arrange + // using (Stream stream = new MemoryStream()) + // { + // using (PresentationDocument presentationDocument = PresentationDocument.Create(stream, PresentationDocumentType.AddIn)) + // { + // // create presentation part + // PresentationPart presentationPart = presentationDocument.AddPresentationPart(); + // presentationPart.Presentation = new Presentation.Presentation(); + // presentationPart.Presentation.AddChild(new Presentation.NotesSize() { Cx = 913607, Cy = 913607 }); + // } + + // // Act and Assert + // NotSupportedException ex = Assert.Throws(() => + // { + // using PresentationDocument pd = PresentationDocument.Open(stream, false, new OpenSettings() { VerifyMinimumPackage = true }); + // }); + + // Assert.Equal("Minimum package verification for PresentationDocumentType.AddIn (.ppam) is not supported.", ex.Message); + // } + //} + + //[Fact] + //public void VerifyMinimumPackageTest_MacroEnabledSlideshowDocumentPackage_HasError_Presentation() + //{ + // // Arrange + // using (Stream stream = new MemoryStream()) + // { + // using (PresentationDocument presentationDocument = PresentationDocument.Create(stream, PresentationDocumentType.MacroEnabledSlideshow)) + // { + // // create presentation part + // PresentationPart presentationPart = presentationDocument.AddPresentationPart(); + // presentationPart.Presentation = new Presentation.Presentation(); + // presentationPart.Presentation.AddChild(new Presentation.NotesSize() { Cx = 913607, Cy = 913607 }); + // } + + // // Act and Assert + // NotSupportedException ex = Assert.Throws(() => + // { + // using PresentationDocument pd = PresentationDocument.Open(stream, false, new OpenSettings() { VerifyMinimumPackage = true }); + // }); + + // Assert.Equal("Minimum package verification for PresentationDocumentType.MacroEnabledSlideshow (.ppsm) is not supported.", ex.Message); + // } + //} + + //[Fact] + //public void VerifyMinimumPackageTest_SlideshowDocumentPackage_HasError_Presentation() + //{ + // // Arrange + // using (Stream stream = new MemoryStream()) + // { + // using (PresentationDocument presentationDocument = PresentationDocument.Create(stream, PresentationDocumentType.Slideshow)) + // { + // // create presentation part + // PresentationPart presentationPart = presentationDocument.AddPresentationPart(); + // presentationPart.Presentation = new Presentation.Presentation(); + // presentationPart.Presentation.AddChild(new Presentation.NotesSize() { Cx = 913607, Cy = 913607 }); + // } + + // // Act and Assert + // NotSupportedException ex = Assert.Throws(() => + // { + // using PresentationDocument pd = PresentationDocument.Open(stream, false, new OpenSettings() { VerifyMinimumPackage = true }); + // }); + + // Assert.Equal("Minimum package verification for PresentationDocumentType.Slideshow (.ppsx) is not supported.", ex.Message); + // } + //} } } From 7d4c2b400bc8bdb831171857f37a752ea4432808 Mon Sep 17 00:00:00 2001 From: Michael Bowen <10384982+mikeebowen@users.noreply.github.com> Date: Thu, 15 May 2025 14:16:44 -0700 Subject: [PATCH 21/33] revert to match main --- .../OpenXmlPackageBuilderExtensions.cs | 10 +- .../Packaging/OpenSettings.cs | 12 - .../Packaging/OpenXmlPackage.cs | 6 - .../PublicAPI/PublicAPI.Shipped.txt | 2 - .../Validation/DocumentValidator.cs | 2 - .../ValidationResources.Designer.cs | 9 - .../Validation/ValidationResources.resx | 3 - .../Validation/ValidationSettings.cs | 7 - .../Packaging/PresentationDocument.cs | 68 +-- .../Packaging/SpreadsheetDocument.cs | 43 +- .../TypedPackageFeatureCollection.cs | 8 +- .../Packaging/WordprocessingDocument.cs | 37 +- .../ofapiTest/OpenXmlPackageTest.cs | 537 +----------------- .../ofapiTest/OpenXmlValidatorTest.cs | 328 ----------- 14 files changed, 6 insertions(+), 1066 deletions(-) diff --git a/src/DocumentFormat.OpenXml.Framework/Builder/OpenXmlPackageBuilderExtensions.cs b/src/DocumentFormat.OpenXml.Framework/Builder/OpenXmlPackageBuilderExtensions.cs index b546129e5..51020763d 100644 --- a/src/DocumentFormat.OpenXml.Framework/Builder/OpenXmlPackageBuilderExtensions.cs +++ b/src/DocumentFormat.OpenXml.Framework/Builder/OpenXmlPackageBuilderExtensions.cs @@ -110,15 +110,7 @@ public static IPackageBuilder Use(this IPackageBuilder UseSettings(this IPackageBuilder builder, OpenSettings settings) where TPackage : OpenXmlPackage - => builder.Use(package => - { - package.OpenSettings = settings; - - if (settings.VerifyMinimumPackage && package.Features.Get() is { } minimumFeature && !minimumFeature.Validate()) - { - throw new FileFormatException("The provided package does not conform to the minimum requirements to open."); - } - }); + => builder.Use(package => package.OpenSettings = settings); internal static IPackageBuilder UseDefaultBehaviorAndLockBuilder(this IPackageBuilder builder) where TPackage : OpenXmlPackage diff --git a/src/DocumentFormat.OpenXml.Framework/Packaging/OpenSettings.cs b/src/DocumentFormat.OpenXml.Framework/Packaging/OpenSettings.cs index a9e147c7b..8e637b432 100644 --- a/src/DocumentFormat.OpenXml.Framework/Packaging/OpenSettings.cs +++ b/src/DocumentFormat.OpenXml.Framework/Packaging/OpenSettings.cs @@ -74,17 +74,5 @@ public MarkupCompatibilityProcessSettings MarkupCompatibilityProcessSettings /// This property allows you to mitigate denial of service attacks where the attacker submits a package with an extremely large Open XML part. By limiting the size of the part, you can detect the attack and recover reliably. /// public long MaxCharactersInPart { get; set; } - - /// - /// Gets or sets a value indicating whether to validate that the document meets the minimum requirements for a valid package. - /// - /// - /// true if the document should be validated for minimum requirements; otherwise, false. - /// - /// - /// When set to true, the document will be checked to ensure it contains the necessary parts and structure - /// to be considered a valid Open XML package. - /// - public bool VerifyMinimumPackage { get; set; } } } diff --git a/src/DocumentFormat.OpenXml.Framework/Packaging/OpenXmlPackage.cs b/src/DocumentFormat.OpenXml.Framework/Packaging/OpenXmlPackage.cs index 34ef6fa9c..c8ce6524a 100644 --- a/src/DocumentFormat.OpenXml.Framework/Packaging/OpenXmlPackage.cs +++ b/src/DocumentFormat.OpenXml.Framework/Packaging/OpenXmlPackage.cs @@ -3,7 +3,6 @@ using DocumentFormat.OpenXml; using DocumentFormat.OpenXml.Features; -using DocumentFormat.OpenXml.Validation; using System; using System.Collections.Generic; using System.IO; @@ -613,10 +612,5 @@ public void Save() /// public override IFeatureCollection Features => _features ??= new PackageFeatureCollection(this); - - internal virtual void VerifyMinimumDocument(ValidationContext validationContext) - { - return; - } } } diff --git a/src/DocumentFormat.OpenXml.Framework/PublicAPI/PublicAPI.Shipped.txt b/src/DocumentFormat.OpenXml.Framework/PublicAPI/PublicAPI.Shipped.txt index f1c172980..c7d7da820 100644 --- a/src/DocumentFormat.OpenXml.Framework/PublicAPI/PublicAPI.Shipped.txt +++ b/src/DocumentFormat.OpenXml.Framework/PublicAPI/PublicAPI.Shipped.txt @@ -1009,5 +1009,3 @@ DocumentFormat.OpenXml.OpenXmlPartWriterSettings.Encoding.set -> void DocumentFormat.OpenXml.OpenXmlPartWriterSettings.OpenXmlPartWriterSettings() -> void DocumentFormat.OpenXml.OpenXmlPartWriter.OpenXmlPartWriter(DocumentFormat.OpenXml.Packaging.OpenXmlPart! openXmlPart, DocumentFormat.OpenXml.OpenXmlPartWriterSettings! settings) -> void DocumentFormat.OpenXml.OpenXmlPartWriter.OpenXmlPartWriter(System.IO.Stream! partStream, DocumentFormat.OpenXml.OpenXmlPartWriterSettings! settings) -> void -DocumentFormat.OpenXml.Packaging.OpenSettings.VerifyMinimumPackage.get -> bool -DocumentFormat.OpenXml.Packaging.OpenSettings.VerifyMinimumPackage.set -> void diff --git a/src/DocumentFormat.OpenXml.Framework/Validation/DocumentValidator.cs b/src/DocumentFormat.OpenXml.Framework/Validation/DocumentValidator.cs index 14f09f3f3..df3ea4e46 100644 --- a/src/DocumentFormat.OpenXml.Framework/Validation/DocumentValidator.cs +++ b/src/DocumentFormat.OpenXml.Framework/Validation/DocumentValidator.cs @@ -50,8 +50,6 @@ public List Validate(OpenXmlPackage document, ValidationSet // integrate the package validation. ValidatePackageStructure(document, context); - document.VerifyMinimumDocument(context); - foreach (var part in PartsToBeValidated(document)) { // traverse from the part root element (by DOM or by Reader) in post-order diff --git a/src/DocumentFormat.OpenXml.Framework/Validation/ValidationResources.Designer.cs b/src/DocumentFormat.OpenXml.Framework/Validation/ValidationResources.Designer.cs index 2a19d601b..0f07d0a74 100644 --- a/src/DocumentFormat.OpenXml.Framework/Validation/ValidationResources.Designer.cs +++ b/src/DocumentFormat.OpenXml.Framework/Validation/ValidationResources.Designer.cs @@ -348,15 +348,6 @@ internal static string Sch_IncompleteContentExpectingComplex { } } - /// - /// Looks up a localized string similar to The provided package does not conform to the minimum requirements for {0} to open.. - /// - internal static string Sch_IncompletePackage { - get { - return ResourceManager.GetString("Sch_IncompletePackage", resourceCulture); - } - } - /// /// Looks up a localized string similar to The element '{0}' is a leaf element and cannot contain children.. /// diff --git a/src/DocumentFormat.OpenXml.Framework/Validation/ValidationResources.resx b/src/DocumentFormat.OpenXml.Framework/Validation/ValidationResources.resx index 0ab938c17..ed8489361 100644 --- a/src/DocumentFormat.OpenXml.Framework/Validation/ValidationResources.resx +++ b/src/DocumentFormat.OpenXml.Framework/Validation/ValidationResources.resx @@ -342,7 +342,4 @@ Cell contents have invalid value '{0}' for type '{1}'. - - The provided package does not conform to the minimum requirements for {0} to open. - \ No newline at end of file diff --git a/src/DocumentFormat.OpenXml.Framework/Validation/ValidationSettings.cs b/src/DocumentFormat.OpenXml.Framework/Validation/ValidationSettings.cs index 96939a7cd..e4423c5f1 100644 --- a/src/DocumentFormat.OpenXml.Framework/Validation/ValidationSettings.cs +++ b/src/DocumentFormat.OpenXml.Framework/Validation/ValidationSettings.cs @@ -32,12 +32,5 @@ public ValidationSettings(FileFormatVersions fileFormat) /// Default is 1000. A zero (0) value means no limitation. /// public int MaxNumberOfErrors { get; set; } - - /// - /// Gets or sets a value indicating whether the validator should verify that the package meets the minimum requirements - /// for the specified file format. When set to true, the validation process will include checks to ensure - /// the document structure is sufficient for the target application to open the file. - /// - public bool VerifyMinimumPackage { get; set; } } } diff --git a/src/DocumentFormat.OpenXml/Packaging/PresentationDocument.cs b/src/DocumentFormat.OpenXml/Packaging/PresentationDocument.cs index b91fddeac..fe7995fa2 100644 --- a/src/DocumentFormat.OpenXml/Packaging/PresentationDocument.cs +++ b/src/DocumentFormat.OpenXml/Packaging/PresentationDocument.cs @@ -3,13 +3,10 @@ using DocumentFormat.OpenXml.Builder; using DocumentFormat.OpenXml.Features; -using DocumentFormat.OpenXml.Presentation; -using DocumentFormat.OpenXml.Validation; using System; using System.Diagnostics.CodeAnalysis; using System.IO; using System.IO.Packaging; -using System.Linq; using System.Reflection; namespace DocumentFormat.OpenXml.Packaging @@ -502,34 +499,6 @@ public LabelInfoPart? LabelInfoPart get { return GetSubPartOfType(); } } - internal override void VerifyMinimumDocument(ValidationContext validationContext) - { - if (this.DocumentType is not PresentationDocumentType.Slideshow && - this.DocumentType is not PresentationDocumentType.MacroEnabledTemplate && - this.DocumentType is not PresentationDocumentType.AddIn) - { - if (this.PresentationPart is not - { - Presentation.NotesSize: - { - Cx: { HasValue: true }, - Cy: { HasValue: true }, - } - } - - || !(this.PresentationPart.SlideMasterParts?.Any() ?? false)) - { - validationContext.AddError(new() - { - ErrorType = ValidationErrorType.Schema, - Id = "Sch_IncompletePackage", - Part = this.PresentationPart, - Description = SR.Format(ValidationResources.Sch_IncompletePackage, "PowerPoint"), - }); - } - } - } - /// public override IFeatureCollection Features => _features ??= new PresentationDocumentFeatures(this); @@ -537,8 +506,7 @@ this.DocumentType is not PresentationDocumentType.MacroEnabledTemplate && private partial class PresentationDocumentFeatures : TypedPackageFeatureCollection, IApplicationTypeFeature, IMainPartFeature, - IProgrammaticIdentifierFeature, - IMinimumDocumentFeature + IProgrammaticIdentifierFeature { public PresentationDocumentFeatures(OpenXmlPackage package) : base(package) @@ -576,40 +544,6 @@ public PresentationDocumentFeatures(OpenXmlPackage package) "application/vnd.ms-powerpoint.addin.macroEnabled.main+xml" => PresentationDocumentType.AddIn, _ => default, }; - - bool IMinimumDocumentFeature.Validate() - { - if (this.DocumentType == PresentationDocumentType.Slideshow) - { - throw new NotSupportedException("Minimum package verification for PresentationDocumentType.Slideshow (.ppsx) is not supported."); - } - - if (this.DocumentType == PresentationDocumentType.MacroEnabledSlideshow) - { - throw new NotSupportedException("Minimum package verification for PresentationDocumentType.MacroEnabledSlideshow (.ppsm) is not supported."); - } - - if (this.DocumentType == PresentationDocumentType.AddIn) - { - throw new NotSupportedException("Minimum package verification for PresentationDocumentType.AddIn (.ppam) is not supported."); - } - - return HasValidNotes(); - } - - private bool HasValidNotes() - { - const long MaxNoteSize = 27273042316900; - - return MainPart is - { - Presentation.NotesSize: - { - Cy: { HasValue: true, Value: >= 0 and <= MaxNoteSize }, - Cx: { HasValue: true, Value: >= 0 and <= MaxNoteSize }, - } - }; - } } } } diff --git a/src/DocumentFormat.OpenXml/Packaging/SpreadsheetDocument.cs b/src/DocumentFormat.OpenXml/Packaging/SpreadsheetDocument.cs index be5602de8..f950048c1 100644 --- a/src/DocumentFormat.OpenXml/Packaging/SpreadsheetDocument.cs +++ b/src/DocumentFormat.OpenXml/Packaging/SpreadsheetDocument.cs @@ -3,8 +3,6 @@ using DocumentFormat.OpenXml.Builder; using DocumentFormat.OpenXml.Features; -using DocumentFormat.OpenXml.Spreadsheet; -using DocumentFormat.OpenXml.Validation; using System; using System.Diagnostics.CodeAnalysis; using System.IO; @@ -501,34 +499,13 @@ public LabelInfoPart? LabelInfoPart get { return GetSubPartOfType(); } } - internal override void VerifyMinimumDocument(ValidationContext validationContext) - { - if (this.DocumentType != SpreadsheetDocumentType.AddIn) - { - Sheet? sheet = this.WorkbookPart?.Workbook?.Sheets?.GetFirstChild(); - SheetData? sheetData = this.WorkbookPart?.WorksheetParts?.FirstOrDefaultAndMaxOne()?.Worksheet?.GetFirstChild(); - - if (sheet is null || sheetData is null) - { - validationContext.AddError(new() - { - ErrorType = ValidationErrorType.Schema, - Id = "Sch_IncompletePackage", - Part = this.WorkbookPart, - Description = SR.Format(ValidationResources.Sch_IncompletePackage, "Excel"), - }); - } - } - } - /// public override IFeatureCollection Features => _features ??= new SpreadsheetDocumentFeatures(this); [DocumentFormat.OpenXml.Generator.OpenXmlPackage("SpreadsheetDocument")] private partial class SpreadsheetDocumentFeatures : TypedPackageFeatureCollection, IApplicationTypeFeature, - IMainPartFeature, - IMinimumDocumentFeature + IMainPartFeature { public SpreadsheetDocumentFeatures(OpenXmlPackage package) : base(package) @@ -560,24 +537,6 @@ public SpreadsheetDocumentFeatures(OpenXmlPackage package) "application/vnd.ms-excel.addin.macroEnabled.main+xml" => SpreadsheetDocumentType.AddIn, _ => default, }; - - bool IMinimumDocumentFeature.Validate() - { - if (DocumentType == SpreadsheetDocumentType.AddIn) - { - throw new NotSupportedException("Validation for SpreadsheetDocument.AddIn (.xlam) is not supported."); - } - - Sheet? sheet = MainPart?.Workbook?.Sheets?.GetFirstChild(); - SheetData? sheetData = MainPart?.WorksheetParts?.FirstOrDefaultAndMaxOne()?.Worksheet?.GetFirstChild(); - - if (sheet is not null && sheetData is not null) - { - return true; - } - - return false; - } } } } diff --git a/src/DocumentFormat.OpenXml/Packaging/TypedPackageFeatureCollection.cs b/src/DocumentFormat.OpenXml/Packaging/TypedPackageFeatureCollection.cs index 80fe18f32..768fc5f45 100644 --- a/src/DocumentFormat.OpenXml/Packaging/TypedPackageFeatureCollection.cs +++ b/src/DocumentFormat.OpenXml/Packaging/TypedPackageFeatureCollection.cs @@ -62,18 +62,12 @@ private TDocumentType EnsureDocumentType() return _documentType.Value; } - protected TDocumentType DocumentType + TDocumentType IDocumentTypeFeature.Current { get => EnsureDocumentType(); set => _documentType = value; } - TDocumentType IDocumentTypeFeature.Current - { - get => DocumentType; - set => DocumentType = value; - } - protected TMainPart? MainPart => Package.GetSubPartOfType(); OpenXmlPart? IMainPartFeature.Part => MainPart; diff --git a/src/DocumentFormat.OpenXml/Packaging/WordprocessingDocument.cs b/src/DocumentFormat.OpenXml/Packaging/WordprocessingDocument.cs index 435ba9702..b3bd6384d 100644 --- a/src/DocumentFormat.OpenXml/Packaging/WordprocessingDocument.cs +++ b/src/DocumentFormat.OpenXml/Packaging/WordprocessingDocument.cs @@ -3,11 +3,8 @@ using DocumentFormat.OpenXml.Builder; using DocumentFormat.OpenXml.Features; -using DocumentFormat.OpenXml.Validation; using DocumentFormat.OpenXml.Wordprocessing; using System; -using System.Collections; -using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; using System.IO.Packaging; @@ -546,26 +543,6 @@ public LabelInfoPart? LabelInfoPart get { return GetSubPartOfType(); } } - internal override void VerifyMinimumDocument(ValidationContext validationContext) - { - if (this.MainDocumentPart is not - { - Document: - { - Body: { } - } - }) - { - validationContext.AddError(new() - { - ErrorType = ValidationErrorType.Schema, - Id = "Sch_IncompletePackage", - Part = this.MainDocumentPart, - Description = SR.Format(ValidationResources.Sch_IncompletePackage, "Word"), - }); - } - } - /// public override IFeatureCollection Features => _features ??= new WordprocessingDocumentFeatures(this); @@ -573,8 +550,7 @@ internal override void VerifyMinimumDocument(ValidationContext validationContext private partial class WordprocessingDocumentFeatures : TypedPackageFeatureCollection, IApplicationTypeFeature, IMainPartFeature, - IProgrammaticIdentifierFeature, - IMinimumDocumentFeature + IProgrammaticIdentifierFeature { public WordprocessingDocumentFeatures(OpenXmlPackage package) : base(package) @@ -606,17 +582,6 @@ public WordprocessingDocumentFeatures(OpenXmlPackage package) "application/vnd.ms-word.template.macroEnabledTemplate.main+xml" => WordprocessingDocumentType.MacroEnabledTemplate, _ => default, }; - - bool IMinimumDocumentFeature.Validate() - { - return MainPart is - { - Document: - { - Body: { } - } - }; - } } } } diff --git a/test/DocumentFormat.OpenXml.Tests/ofapiTest/OpenXmlPackageTest.cs b/test/DocumentFormat.OpenXml.Tests/ofapiTest/OpenXmlPackageTest.cs index 458b36ad9..cbd1eeca6 100644 --- a/test/DocumentFormat.OpenXml.Tests/ofapiTest/OpenXmlPackageTest.cs +++ b/test/DocumentFormat.OpenXml.Tests/ofapiTest/OpenXmlPackageTest.cs @@ -3,10 +3,8 @@ using DocumentFormat.OpenXml.Packaging; using DocumentFormat.OpenXml.Wordprocessing; -using Microsoft.Testing.Platform.MSBuild; using System; using System.IO; -using System.IO.Packaging; using System.Linq; using Xunit; @@ -23,14 +21,8 @@ namespace DocumentFormat.OpenXml.Tests { /// - /// Contains unit tests for validating the behavior of Open XML packages, including WordprocessingDocument, - /// PresentationDocument, and SpreadsheetDocument. These tests cover scenarios such as auto-save functionality, - /// document type changes, part relationships, and minimum package verification. + /// Summary description for OpenXmlPackageTest /// - /// - /// This class uses the xUnit framework for testing and includes tests for various Open XML document types. - /// It ensures the correctness of document operations, relationships, and compliance with Open XML standards. - /// public class OpenXmlPackageTest { [Fact] @@ -884,532 +876,5 @@ public void O15FileOpenTest() Assert.NotNull(webExtensionPart); } } - - [Fact] - public void VerifyMinimumPackageTest_ValidDocumentPath_DoesNotThrow_Wordprocessing() - { - // Arrange - string path = string.Concat(Path.GetTempPath(), "valid.docx"); - - using (WordprocessingDocument document = WordprocessingDocument.Create(path, WordprocessingDocumentType.Document)) - { - document.AddMainDocumentPart().Document = new Document(new Body()); - } - - // Act - Exception exception = Record.Exception(() => - { - using WordprocessingDocument wpd = WordprocessingDocument.Open(path, false, new OpenSettings() { VerifyMinimumPackage = true }); - }); - - // Assert - Assert.Null(exception); - } - - [Fact] - public void VerifyMinimumPackageTest_ValidDocumentStream_DoesNotThrow_Wordprocessing() - { - // Arrange - using (Stream stream = new MemoryStream()) - { - using (WordprocessingDocument document = WordprocessingDocument.Create(stream, WordprocessingDocumentType.Document)) - { - document.AddMainDocumentPart().Document = new Document(new Body()); - } - - // Act - Exception exception = Record.Exception(() => - { - using WordprocessingDocument wpd = WordprocessingDocument.Open(stream, false, new OpenSettings() { VerifyMinimumPackage = true }); - }); - - // Assert - Assert.Null(exception); - } - } - - [Fact] - public void VerifyMinimumPackageTest_ValidDocumentPackage_DoesNotThrow_Wordprocessing() - { - // Arrange - string path = Path.Combine(Path.GetTempPath(), "valid.docx"); - - using (WordprocessingDocument document = WordprocessingDocument.Create(path, WordprocessingDocumentType.Document)) - { - document.AddMainDocumentPart().Document = new Document(new Body()); - } - - // Act - using (Package package = Package.Open(path)) - { - Exception exception = Record.Exception(() => - { - using WordprocessingDocument wpd = WordprocessingDocument.Open(package, new OpenSettings() { VerifyMinimumPackage = true }); - }); - - // Assert - Assert.Null(exception); - } - } - - private readonly string minPackageExMsg = "The provided package does not conform to the minimum requirements to open."; - - [Fact] - public void VerifyMinimumPackageTest_InValidDocumentPath_Throws_Wordprocessing() - { - // Arrange - string path = string.Concat(Path.GetTempPath(), "invalid.docx"); - - using (WordprocessingDocument document = WordprocessingDocument.Create(path, WordprocessingDocumentType.Document)) - { - document.AddMainDocumentPart().Document = new Document(); - } - - // Act and Assert - FileFormatException ex = Assert.Throws(() => - { - using WordprocessingDocument wpd = WordprocessingDocument.Open(path, false, new OpenSettings() { VerifyMinimumPackage = true }); - }); - - Assert.Equal(minPackageExMsg, ex.Message); - } - - [Fact] - public void VerifyMinimumPackageTest_InValidDocumentStream_Throws_Wordprocessing() - { - // Arrange - using (Stream stream = new MemoryStream()) - { - using (WordprocessingDocument document = WordprocessingDocument.Create(stream, WordprocessingDocumentType.Document)) - { - document.AddMainDocumentPart().Document = new Document(); - } - - // Act and Assert - FileFormatException ex = Assert.Throws(() => - { - using WordprocessingDocument wpd = WordprocessingDocument.Open(stream, false, new OpenSettings() { VerifyMinimumPackage = true }); - }); - - Assert.Equal(minPackageExMsg, ex.Message); - } - } - - [Fact] - public void VerifyMinimumPackageTest_InValidDocumentPackage_Throws_Wordprocessing() - { - // Arrange - using (Stream stream = new MemoryStream()) - { - using (WordprocessingDocument document = WordprocessingDocument.Create(stream, WordprocessingDocumentType.Document)) - { - document.AddMainDocumentPart().Document = new Document(); - } - - // Act and Assert - using (Package package = Package.Open(stream)) - { - FileFormatException ex = Assert.Throws(() => - { - using WordprocessingDocument wpd = WordprocessingDocument.Open(package, new OpenSettings() { VerifyMinimumPackage = true }); - }); - - Assert.Equal(minPackageExMsg, ex.Message); - } - } - } - - [Fact] - public void VerifyMinimumPackageTest_ValidDocumentPath_DoesNotThrow_Spreadsheet() - { - // Arrange - string path = string.Concat(Path.GetTempPath(), "valid.xlsx"); - - using (SpreadsheetDocument document = SpreadsheetDocument.Create(path, SpreadsheetDocumentType.Workbook)) - { - WorkbookPart wbp = document.AddWorkbookPart(); - WorksheetPart wsp = wbp.AddNewPart(); - wbp.Workbook = new Spreadsheet.Workbook(new Spreadsheet.Sheets(new Spreadsheet.Sheet() { Id = wbp.GetIdOfPart(wsp), SheetId = 1, Name = "Sheet1" })); - wsp.Worksheet = new Spreadsheet.Worksheet(new Spreadsheet.SheetData()); - } - - // Act - Exception exception = Record.Exception(() => - { - using SpreadsheetDocument ssd = SpreadsheetDocument.Open(path, false, new OpenSettings() { VerifyMinimumPackage = true }); - }); - - // Assert - Assert.Null(exception); - } - - [Fact] - public void VerifyMinimumPackageTest_ValidDocumentStream_DoesNotThrow_Spreadsheet() - { - // Arrange - using (Stream stream = new MemoryStream()) - { - using (SpreadsheetDocument document = SpreadsheetDocument.Create(stream, SpreadsheetDocumentType.MacroEnabledTemplate)) - { - WorkbookPart wbp = document.AddWorkbookPart(); - WorksheetPart wsp = wbp.AddNewPart(); - wbp.Workbook = new Spreadsheet.Workbook(new Spreadsheet.Sheets(new Spreadsheet.Sheet() { Id = wbp.GetIdOfPart(wsp), SheetId = 1, Name = "Sheet1" })); - wsp.Worksheet = new Spreadsheet.Worksheet(new Spreadsheet.SheetData()); - } - - // Act - Exception exception = Record.Exception(() => - { - using SpreadsheetDocument ssd = SpreadsheetDocument.Open(stream, false, new OpenSettings() { VerifyMinimumPackage = true }); - }); - - // Assert - Assert.Null(exception); - } - } - - [Fact] - public void VerifyMinimumPackageTest_ValidDocumentPackage_DoesNotThrow_Spreadsheet() - { - // Arrange - string path = Path.Combine(Path.GetTempPath(), "valid.xlsx"); - - using (SpreadsheetDocument document = SpreadsheetDocument.Create(path, SpreadsheetDocumentType.Template)) - { - WorkbookPart wbp = document.AddWorkbookPart(); - WorksheetPart wsp = wbp.AddNewPart(); - wbp.Workbook = new Spreadsheet.Workbook(new Spreadsheet.Sheets(new Spreadsheet.Sheet() { Id = wbp.GetIdOfPart(wsp), SheetId = 1, Name = "Sheet1" })); - wsp.Worksheet = new Spreadsheet.Worksheet(new Spreadsheet.SheetData()); - } - - // Act - using (Package package = Package.Open(path)) - { - Exception exception = Record.Exception(() => - { - using SpreadsheetDocument ssd = SpreadsheetDocument.Open(package, new OpenSettings() { VerifyMinimumPackage = true }); - }); - - // Assert - Assert.Null(exception); - } - } - - [Fact] - public void VerifyMinimumPackageTest_InValidDocumentPath_Throws_Spreadsheet() - { - // Arrange - string path = string.Concat(Path.GetTempPath(), "invalid.xlsx"); - - using (SpreadsheetDocument document = SpreadsheetDocument.Create(path, SpreadsheetDocumentType.MacroEnabledWorkbook)) - { - WorkbookPart wbp = document.AddWorkbookPart(); - WorksheetPart wsp = wbp.AddNewPart(); - wbp.Workbook = new Spreadsheet.Workbook(new Spreadsheet.Sheets(new Spreadsheet.Sheet() { Id = wbp.GetIdOfPart(wsp), SheetId = 1, Name = "Sheet1" })); - } - - // Act and Assert - FileFormatException ex = Assert.Throws(() => - { - using SpreadsheetDocument ssd = SpreadsheetDocument.Open(path, false, new OpenSettings() { VerifyMinimumPackage = true }); - }); - - Assert.Equal(minPackageExMsg, ex.Message); - } - - [Fact] - public void VerifyMinimumPackageTest_InValidDocumentStream_Throws_Spreadsheet() - { - // Arrange - using (Stream stream = new MemoryStream()) - { - using (SpreadsheetDocument document = SpreadsheetDocument.Create(stream, SpreadsheetDocumentType.Workbook)) - { - WorkbookPart wbp = document.AddWorkbookPart(); - WorksheetPart wsp = wbp.AddNewPart(); - wsp.Worksheet = new Spreadsheet.Worksheet(new Spreadsheet.SheetData()); - } - - // Act and Assert - FileFormatException ex = Assert.Throws(() => - { - using SpreadsheetDocument ssd = SpreadsheetDocument.Open(stream, false, new OpenSettings() { VerifyMinimumPackage = true }); - }); - - Assert.Equal(minPackageExMsg, ex.Message); - } - } - - [Fact] - public void VerifyMinimumPackageTest_InValidDocumentPackage_Throws_Spreadsheet() - { - // Arrange - using (Stream stream = new MemoryStream()) - { - using (SpreadsheetDocument document = SpreadsheetDocument.Create(stream, SpreadsheetDocumentType.Workbook)) - { - WorkbookPart wbp = document.AddWorkbookPart(); - WorksheetPart wsp = wbp.AddNewPart(); - } - - // Act and Assert - using (Package package = Package.Open(stream)) - { - FileFormatException ex = Assert.Throws(() => - { - using SpreadsheetDocument wpd = SpreadsheetDocument.Open(package, new OpenSettings() { VerifyMinimumPackage = true }); - }); - - Assert.Equal(minPackageExMsg, ex.Message); - } - } - } - - [Fact] - public void VerifyMinimumPackageTest_AddInDocumentPackage_Throws_Spreadsheet() - { - // Arrange - using (Stream stream = new MemoryStream()) - { - using (SpreadsheetDocument document = SpreadsheetDocument.Create(stream, SpreadsheetDocumentType.AddIn)) - { - WorkbookPart wbp = document.AddWorkbookPart(); - WorksheetPart wsp = wbp.AddNewPart(); - wbp.Workbook = new Spreadsheet.Workbook(new Spreadsheet.Sheets(new Spreadsheet.Sheet() { Id = wbp.GetIdOfPart(wsp), SheetId = 1, Name = "Sheet1" })); - wsp.Worksheet = new Spreadsheet.Worksheet(new Spreadsheet.SheetData()); - } - - // Act and Assert - using (Package package = Package.Open(stream)) - { - NotSupportedException ex = Assert.Throws(() => - { - using SpreadsheetDocument wpd = SpreadsheetDocument.Open(package, new OpenSettings() { VerifyMinimumPackage = true }); - }); - - Assert.Equal("Validation for SpreadsheetDocument.AddIn (.xlam) is not supported.", ex.Message); - } - } - } - - [Fact] - public void VerifyMinimumPackageTest_ValidDocumentPath_DoesNotThrow_Presentation() - { - // Arrange - string path = string.Concat(Path.GetTempPath(), "valid.pptx"); - - using (PresentationDocument presentationDocument = PresentationDocument.Create(path, PresentationDocumentType.Presentation)) - { - // create presentation part - PresentationPart presentationPart = presentationDocument.AddPresentationPart(); - presentationPart.Presentation = new Presentation.Presentation(); - presentationPart.Presentation.AddChild(new Presentation.NotesSize() { Cx = 913607, Cy = 913607 }); - } - - // Act - Exception exception = Record.Exception(() => - { - using PresentationDocument pd = PresentationDocument.Open(path, false, new OpenSettings() { VerifyMinimumPackage = true }); - }); - - // Assert - Assert.Null(exception); - } - - [Fact] - public void VerifyMinimumPackageTest_ValidDocumentStream_DoesNotThrow_Presentation() - { - // Arrange - using (Stream stream = new MemoryStream()) - { - using (PresentationDocument presentationDocument = PresentationDocument.Create(stream, PresentationDocumentType.Presentation)) - { - // create presentation part - PresentationPart presentationPart = presentationDocument.AddPresentationPart(); - presentationPart.Presentation = new Presentation.Presentation(); - presentationPart.Presentation.AddChild(new Presentation.NotesSize() { Cx = 913607, Cy = 913607 }); - } - - // Act - Exception exception = Record.Exception(() => - { - using PresentationDocument pd = PresentationDocument.Open(stream, false, new OpenSettings() { VerifyMinimumPackage = true }); - }); - - // Assert - Assert.Null(exception); - } - } - - [Fact] - public void VerifyMinimumPackageTest_ValidDocumentPackage_DoesNotThrow_Presentation() - { - // Arrange - string path = Path.Combine(Path.GetTempPath(), "valid.xlsx"); - - using (SpreadsheetDocument document = SpreadsheetDocument.Create(path, SpreadsheetDocumentType.Template)) - { - WorkbookPart wbp = document.AddWorkbookPart(); - WorksheetPart wsp = wbp.AddNewPart(); - wbp.Workbook = new Spreadsheet.Workbook(new Spreadsheet.Sheets(new Spreadsheet.Sheet() { Id = wbp.GetIdOfPart(wsp), SheetId = 1, Name = "Sheet1" })); - wsp.Worksheet = new Spreadsheet.Worksheet(new Spreadsheet.SheetData()); - } - - // Act - using (Package package = Package.Open(path)) - { - Exception exception = Record.Exception(() => - { - using SpreadsheetDocument ssd = SpreadsheetDocument.Open(package, new OpenSettings() { VerifyMinimumPackage = true }); - }); - - // Assert - Assert.Null(exception); - } - } - - [Fact] - public void VerifyMinimumPackageTest_InValidDocumentPath_Throws_Presentation() - { - // Arrange - string path = string.Concat(Path.GetTempPath(), "invalid.pptx"); - - using (PresentationDocument presentationDocument = PresentationDocument.Create(path, PresentationDocumentType.Presentation)) - { - // create presentation part - PresentationPart presentationPart = presentationDocument.AddPresentationPart(); - presentationPart.Presentation = new Presentation.Presentation(); - presentationPart.Presentation.AddChild(new Presentation.NotesSize() { Cx = -2, Cy = 913607 }); - } - - // Act and Assert - FileFormatException ex = Assert.Throws(() => - { - using PresentationDocument ssd = PresentationDocument.Open(path, false, new OpenSettings() { VerifyMinimumPackage = true }); - }); - - Assert.Equal(minPackageExMsg, ex.Message); - } - - [Fact] - public void VerifyMinimumPackageTest_InValidDocumentStream_Throws_Presentation() - { - // Arrange - using (Stream stream = new MemoryStream()) - { - using (PresentationDocument presentationDocument = PresentationDocument.Create(stream, PresentationDocumentType.Presentation)) - { - // create presentation part - PresentationPart presentationPart = presentationDocument.AddPresentationPart(); - presentationPart.Presentation = new Presentation.Presentation(); - } - - // Act and Assert - FileFormatException ex = Assert.Throws(() => - { - using PresentationDocument ssd = PresentationDocument.Open(stream, false, new OpenSettings() { VerifyMinimumPackage = true }); - }); - - Assert.Equal(minPackageExMsg, ex.Message); - } - } - - [Fact] - public void VerifyMinimumPackageTest_InValidDocumentPackage_Throws_Presentation() - { - // Arrange - using (Stream stream = new MemoryStream()) - { - using (PresentationDocument presentationDocument = PresentationDocument.Create(stream, PresentationDocumentType.Presentation)) - { - // create presentation part - PresentationPart presentationPart = presentationDocument.AddPresentationPart(); - presentationPart.Presentation = new Presentation.Presentation(); - } - - // Act and Assert - using (Package package = Package.Open(stream)) - { - FileFormatException ex = Assert.Throws(() => - { - using PresentationDocument pd = PresentationDocument.Open(package, new OpenSettings() { VerifyMinimumPackage = true }); - }); - - Assert.Equal(minPackageExMsg, ex.Message); - } - } - } - - [Fact] - public void VerifyMinimumPackageTest_AddInDocumentPackage_Throws_Presentation() - { - // Arrange - using (Stream stream = new MemoryStream()) - { - using (PresentationDocument presentationDocument = PresentationDocument.Create(stream, PresentationDocumentType.AddIn)) - { - // create presentation part - PresentationPart presentationPart = presentationDocument.AddPresentationPart(); - presentationPart.Presentation = new Presentation.Presentation(); - presentationPart.Presentation.AddChild(new Presentation.NotesSize() { Cx = 913607, Cy = 913607 }); - } - - // Act and Assert - NotSupportedException ex = Assert.Throws(() => - { - using PresentationDocument pd = PresentationDocument.Open(stream, false, new OpenSettings() { VerifyMinimumPackage = true }); - }); - - Assert.Equal("Minimum package verification for PresentationDocumentType.AddIn (.ppam) is not supported.", ex.Message); - } - } - - [Fact] - public void VerifyMinimumPackageTest_MacroEnabledSlideshowDocumentPackage_Throws_Presentation() - { - // Arrange - using (Stream stream = new MemoryStream()) - { - using (PresentationDocument presentationDocument = PresentationDocument.Create(stream, PresentationDocumentType.MacroEnabledSlideshow)) - { - // create presentation part - PresentationPart presentationPart = presentationDocument.AddPresentationPart(); - presentationPart.Presentation = new Presentation.Presentation(); - presentationPart.Presentation.AddChild(new Presentation.NotesSize() { Cx = 913607, Cy = 913607 }); - } - - // Act and Assert - NotSupportedException ex = Assert.Throws(() => - { - using PresentationDocument pd = PresentationDocument.Open(stream, false, new OpenSettings() { VerifyMinimumPackage = true }); - }); - - Assert.Equal("Minimum package verification for PresentationDocumentType.MacroEnabledSlideshow (.ppsm) is not supported.", ex.Message); - } - } - - [Fact] - public void VerifyMinimumPackageTest_SlideshowDocumentPackage_Throws_Presentation() - { - // Arrange - using (Stream stream = new MemoryStream()) - { - using (PresentationDocument presentationDocument = PresentationDocument.Create(stream, PresentationDocumentType.Slideshow)) - { - // create presentation part - PresentationPart presentationPart = presentationDocument.AddPresentationPart(); - presentationPart.Presentation = new Presentation.Presentation(); - presentationPart.Presentation.AddChild(new Presentation.NotesSize() { Cx = 913607, Cy = 913607 }); - } - - // Act and Assert - NotSupportedException ex = Assert.Throws(() => - { - using PresentationDocument pd = PresentationDocument.Open(stream, false, new OpenSettings() { VerifyMinimumPackage = true }); - }); - - Assert.Equal("Minimum package verification for PresentationDocumentType.Slideshow (.ppsx) is not supported.", ex.Message); - } - } } } diff --git a/test/DocumentFormat.OpenXml.Tests/ofapiTest/OpenXmlValidatorTest.cs b/test/DocumentFormat.OpenXml.Tests/ofapiTest/OpenXmlValidatorTest.cs index 51a8fcb82..4fdeb0daa 100644 --- a/test/DocumentFormat.OpenXml.Tests/ofapiTest/OpenXmlValidatorTest.cs +++ b/test/DocumentFormat.OpenXml.Tests/ofapiTest/OpenXmlValidatorTest.cs @@ -9,14 +9,12 @@ using System; using System.Collections.Generic; using System.IO; -using System.IO.Packaging; using System.Linq; using System.Threading; using System.Xml; using Xunit; using static DocumentFormat.OpenXml.Tests.TestAssets; -using Path = System.IO.Path; namespace DocumentFormat.OpenXml.Tests { @@ -3834,331 +3832,5 @@ public void VersionMismatchPartValidatingTest() Assert.Throws(() => O14Validator.Validate(wordTestDocument.MainDocumentPart, TestContext.Current.CancellationToken)); } } - - [Fact] - public void VerifyMinimumPackageTest_ValidDocumentPath_NoErrors_Wordprocessing() - { - // Arrange - string path = string.Concat(Path.GetTempPath(), "valid.docx"); - - using (WordprocessingDocument document = WordprocessingDocument.Create(path, WordprocessingDocumentType.Document)) - { - document.AddMainDocumentPart().Document = new Document(new Body()); - - OpenXmlValidator validator = new(); - - // Act - IEnumerable errors = validator.Validate(document, TestContext.Current.CancellationToken); - - // Assert - Assert.Empty(errors); - } - } - - [Fact] - public void VerifyMinimumPackageTest_InValidDocumentPath_HasError_Wordprocessing() - { - // Arrange - string path = string.Concat(Path.GetTempPath(), "invalid.docx"); - - using (WordprocessingDocument document = WordprocessingDocument.Create(path, WordprocessingDocumentType.Document)) - { - document.AddMainDocumentPart().Document = new Document(); - OpenXmlValidator validator = new(); - - // Act - IEnumerable errors = validator.Validate(document, TestContext.Current.CancellationToken); - - // Assert - Assert.Single(errors); - Assert.Equal(errors.FirstOrDefault()?.Description, "The provided package does not conform to the minimum requirements for Word to open."); - } - } - - [Fact] - public void VerifyMinimumPackageTest_ValidDocumentPath_NoErrors_Spreadsheet() - { - // Arrange - string path = string.Concat(Path.GetTempPath(), "valid.xlsx"); - - using (SpreadsheetDocument document = SpreadsheetDocument.Create(path, SpreadsheetDocumentType.Workbook)) - { - WorkbookPart wbp = document.AddWorkbookPart(); - WorksheetPart wsp = wbp.AddNewPart(); - wbp.Workbook = new Spreadsheet.Workbook(new Spreadsheet.Sheets(new Spreadsheet.Sheet() { Id = wbp.GetIdOfPart(wsp), SheetId = 1, Name = "Sheet1" })); - wsp.Worksheet = new Spreadsheet.Worksheet(new Spreadsheet.SheetData()); - OpenXmlValidator validator = new(); - - // Act - IEnumerable errors = validator.Validate(document, TestContext.Current.CancellationToken); - - // Assert - Assert.Empty(errors); - } - } - - [Fact] - public void VerifyMinimumPackageTest_InValidDocumentPath_HasError_Spreadsheet() - { - // Arrange - string path = string.Concat(Path.GetTempPath(), "invalid.xlsx"); - - using (SpreadsheetDocument document = SpreadsheetDocument.Create(path, SpreadsheetDocumentType.MacroEnabledWorkbook)) - { - WorkbookPart wbp = document.AddWorkbookPart(); - WorksheetPart wsp = wbp.AddNewPart(); - wbp.Workbook = new Spreadsheet.Workbook(new Spreadsheet.Sheets(new Spreadsheet.Sheet() { Id = wbp.GetIdOfPart(wsp), SheetId = 1, Name = "Sheet1" })); - OpenXmlValidator validator = new(); - - // Act - IEnumerable errors = validator.Validate(document, TestContext.Current.CancellationToken); - - // Assert - Assert.Single(errors); - Assert.Equal("The provided package does not conform to the minimum requirements for Excel to open.", errors.FirstOrDefault()?.Description); - } - } - - [Fact] - public void VerifyMinimumPackageTest_ValidDocumentPath_NoErrors_Presentation() - { - // Arrange - string path = string.Concat(Path.GetTempPath(), "valid.pptx"); - - using (PresentationDocument document = PresentationDocument.Create(path, PresentationDocumentType.Presentation)) - { - // create presentation part - PresentationPart presentationPart = document.AddPresentationPart(); - presentationPart.Presentation = new DocumentFormat.OpenXml.Presentation.Presentation(); - presentationPart.Presentation.AddChild(new DocumentFormat.OpenXml.Presentation.NotesSize() { Cx = 913607, Cy = 913607 }); - SlideMasterPart slideMasterPart = presentationPart.AddNewPart(); - slideMasterPart.SlideMaster = new Presentation.SlideMaster( - new Presentation.CommonSlideData( - new Presentation.ShapeTree( - new DocumentFormat.OpenXml.Presentation.NonVisualGroupShapeProperties( - new DocumentFormat.OpenXml.Presentation.NonVisualDrawingProperties() { Id = (UInt32Value)1U, Name = string.Empty }, - new DocumentFormat.OpenXml.Presentation.NonVisualGroupShapeDrawingProperties(), - new DocumentFormat.OpenXml.Presentation.ApplicationNonVisualDrawingProperties()), - new DocumentFormat.OpenXml.Presentation.GroupShapeProperties())), - new DocumentFormat.OpenXml.Presentation.ColorMap() - { - Background1 = DocumentFormat.OpenXml.Drawing.ColorSchemeIndexValues.Light1, - Background2 = DocumentFormat.OpenXml.Drawing.ColorSchemeIndexValues.Light2, - Text1 = DocumentFormat.OpenXml.Drawing.ColorSchemeIndexValues.Dark1, - Text2 = DocumentFormat.OpenXml.Drawing.ColorSchemeIndexValues.Dark2, - Accent1 = DocumentFormat.OpenXml.Drawing.ColorSchemeIndexValues.Accent1, - Accent2 = DocumentFormat.OpenXml.Drawing.ColorSchemeIndexValues.Accent2, - Accent3 = DocumentFormat.OpenXml.Drawing.ColorSchemeIndexValues.Accent3, - Accent4 = DocumentFormat.OpenXml.Drawing.ColorSchemeIndexValues.Accent4, - Accent5 = DocumentFormat.OpenXml.Drawing.ColorSchemeIndexValues.Accent5, - Accent6 = DocumentFormat.OpenXml.Drawing.ColorSchemeIndexValues.Accent6, - Hyperlink = DocumentFormat.OpenXml.Drawing.ColorSchemeIndexValues.Hyperlink, - FollowedHyperlink = DocumentFormat.OpenXml.Drawing.ColorSchemeIndexValues.FollowedHyperlink, - }); - OpenXmlValidator validator = new(); - - // Act - IEnumerable errors = validator.Validate(document, TestContext.Current.CancellationToken); - - // Assert - Assert.Empty(errors); - } - } - - //[Fact] - //public void VerifyMinimumPackageTest_ValidDocumentStream_NoErrors_Presentation() - //{ - // // Arrange - // using (Stream stream = new MemoryStream()) - // { - // using (PresentationDocument presentationDocument = PresentationDocument.Create(stream, PresentationDocumentType.Presentation)) - // { - // // create presentation part - // PresentationPart presentationPart = presentationDocument.AddPresentationPart(); - // presentationPart.Presentation = new Presentation.Presentation(); - // presentationPart.Presentation.AddChild(new Presentation.NotesSize() { Cx = 913607, Cy = 913607 }); - // } - - // // Act - // Exception exception = Record.Exception(() => - // { - // using PresentationDocument pd = PresentationDocument.Open(stream, false, new OpenSettings() { VerifyMinimumPackage = true }); - // }); - - // // Assert - // Assert.Null(exception); - // } - //} - - //[Fact] - //public void VerifyMinimumPackageTest_ValidDocumentPackage_NoErrors_Presentation() - //{ - // // Arrange - // string path = Path.Combine(Path.GetTempPath(), "valid.xlsx"); - - // using (SpreadsheetDocument document = SpreadsheetDocument.Create(path, SpreadsheetDocumentType.Template)) - // { - // WorkbookPart wbp = document.AddWorkbookPart(); - // WorksheetPart wsp = wbp.AddNewPart(); - // wbp.Workbook = new Spreadsheet.Workbook(new Spreadsheet.Sheets(new Spreadsheet.Sheet() { Id = wbp.GetIdOfPart(wsp), SheetId = 1, Name = "Sheet1" })); - // wsp.Worksheet = new Spreadsheet.Worksheet(new Spreadsheet.SheetData()); - // } - - // // Act - // using (Package package = Package.Open(path)) - // { - // Exception exception = Record.Exception(() => - // { - // using SpreadsheetDocument ssd = SpreadsheetDocument.Open(package, new OpenSettings() { VerifyMinimumPackage = true }); - // }); - - // // Assert - // Assert.Null(exception); - // } - //} - - //[Fact] - //public void VerifyMinimumPackageTest_InValidDocumentPath_HasError_Presentation() - //{ - // // Arrange - // string path = string.Concat(Path.GetTempPath(), "invalid.pptx"); - - // using (PresentationDocument presentationDocument = PresentationDocument.Create(path, PresentationDocumentType.Presentation)) - // { - // // create presentation part - // PresentationPart presentationPart = presentationDocument.AddPresentationPart(); - // presentationPart.Presentation = new Presentation.Presentation(); - // presentationPart.Presentation.AddChild(new Presentation.NotesSize() { Cx = -2, Cy = 913607 }); - // } - - // // Act and Assert - // FileFormatException ex = Assert.Throws(() => - // { - // using PresentationDocument ssd = PresentationDocument.Open(path, false, new OpenSettings() { VerifyMinimumPackage = true }); - // }); - - // Assert.Equal(minPackageExMsg, ex.Message); - //} - - //[Fact] - //public void VerifyMinimumPackageTest_InValidDocumentStream_HasError_Presentation() - //{ - // // Arrange - // using (Stream stream = new MemoryStream()) - // { - // using (PresentationDocument presentationDocument = PresentationDocument.Create(stream, PresentationDocumentType.Presentation)) - // { - // // create presentation part - // PresentationPart presentationPart = presentationDocument.AddPresentationPart(); - // presentationPart.Presentation = new Presentation.Presentation(); - // } - - // // Act and Assert - // FileFormatException ex = Assert.Throws(() => - // { - // using PresentationDocument ssd = PresentationDocument.Open(stream, false, new OpenSettings() { VerifyMinimumPackage = true }); - // }); - - // Assert.Equal(minPackageExMsg, ex.Message); - // } - //} - - //[Fact] - //public void VerifyMinimumPackageTest_InValidDocumentPackage_HasError_Presentation() - //{ - // // Arrange - // using (Stream stream = new MemoryStream()) - // { - // using (PresentationDocument presentationDocument = PresentationDocument.Create(stream, PresentationDocumentType.Presentation)) - // { - // // create presentation part - // PresentationPart presentationPart = presentationDocument.AddPresentationPart(); - // presentationPart.Presentation = new Presentation.Presentation(); - // } - - // // Act and Assert - // using (Package package = Package.Open(stream)) - // { - // FileFormatException ex = Assert.Throws(() => - // { - // using PresentationDocument pd = PresentationDocument.Open(package, new OpenSettings() { VerifyMinimumPackage = true }); - // }); - - // Assert.Equal(minPackageExMsg, ex.Message); - // } - // } - //} - - //[Fact] - //public void VerifyMinimumPackageTest_AddInDocumentPackage_HasError_Presentation() - //{ - // // Arrange - // using (Stream stream = new MemoryStream()) - // { - // using (PresentationDocument presentationDocument = PresentationDocument.Create(stream, PresentationDocumentType.AddIn)) - // { - // // create presentation part - // PresentationPart presentationPart = presentationDocument.AddPresentationPart(); - // presentationPart.Presentation = new Presentation.Presentation(); - // presentationPart.Presentation.AddChild(new Presentation.NotesSize() { Cx = 913607, Cy = 913607 }); - // } - - // // Act and Assert - // NotSupportedException ex = Assert.Throws(() => - // { - // using PresentationDocument pd = PresentationDocument.Open(stream, false, new OpenSettings() { VerifyMinimumPackage = true }); - // }); - - // Assert.Equal("Minimum package verification for PresentationDocumentType.AddIn (.ppam) is not supported.", ex.Message); - // } - //} - - //[Fact] - //public void VerifyMinimumPackageTest_MacroEnabledSlideshowDocumentPackage_HasError_Presentation() - //{ - // // Arrange - // using (Stream stream = new MemoryStream()) - // { - // using (PresentationDocument presentationDocument = PresentationDocument.Create(stream, PresentationDocumentType.MacroEnabledSlideshow)) - // { - // // create presentation part - // PresentationPart presentationPart = presentationDocument.AddPresentationPart(); - // presentationPart.Presentation = new Presentation.Presentation(); - // presentationPart.Presentation.AddChild(new Presentation.NotesSize() { Cx = 913607, Cy = 913607 }); - // } - - // // Act and Assert - // NotSupportedException ex = Assert.Throws(() => - // { - // using PresentationDocument pd = PresentationDocument.Open(stream, false, new OpenSettings() { VerifyMinimumPackage = true }); - // }); - - // Assert.Equal("Minimum package verification for PresentationDocumentType.MacroEnabledSlideshow (.ppsm) is not supported.", ex.Message); - // } - //} - - //[Fact] - //public void VerifyMinimumPackageTest_SlideshowDocumentPackage_HasError_Presentation() - //{ - // // Arrange - // using (Stream stream = new MemoryStream()) - // { - // using (PresentationDocument presentationDocument = PresentationDocument.Create(stream, PresentationDocumentType.Slideshow)) - // { - // // create presentation part - // PresentationPart presentationPart = presentationDocument.AddPresentationPart(); - // presentationPart.Presentation = new Presentation.Presentation(); - // presentationPart.Presentation.AddChild(new Presentation.NotesSize() { Cx = 913607, Cy = 913607 }); - // } - - // // Act and Assert - // NotSupportedException ex = Assert.Throws(() => - // { - // using PresentationDocument pd = PresentationDocument.Open(stream, false, new OpenSettings() { VerifyMinimumPackage = true }); - // }); - - // Assert.Equal("Minimum package verification for PresentationDocumentType.Slideshow (.ppsx) is not supported.", ex.Message); - // } - //} } } From 8c4d993bb4addff418d41f424444f96526b76bc8 Mon Sep 17 00:00:00 2001 From: Michael Bowen <10384982+mikeebowen@users.noreply.github.com> Date: Thu, 15 May 2025 16:42:42 -0700 Subject: [PATCH 22/33] add validation error if part is empty --- .../Validation/DocumentValidator.cs | 13 +++++++++++++ .../Validation/ValidationResources.Designer.cs | 9 +++++++++ .../Validation/ValidationResources.resx | 3 +++ .../ofapiTest/OpenXmlValidatorTest.cs | 17 +++++++++++++++++ 4 files changed, 42 insertions(+) diff --git a/src/DocumentFormat.OpenXml.Framework/Validation/DocumentValidator.cs b/src/DocumentFormat.OpenXml.Framework/Validation/DocumentValidator.cs index df3ea4e46..1a6647d8f 100644 --- a/src/DocumentFormat.OpenXml.Framework/Validation/DocumentValidator.cs +++ b/src/DocumentFormat.OpenXml.Framework/Validation/DocumentValidator.cs @@ -106,6 +106,19 @@ private void ValidatePart(OpenXmlPart part, ValidationContext context) { Validate(context); } + else + { + context.AddError(new ValidationErrorInfo + { + ErrorType = ValidationErrorType.Schema, + Id = "Sch_PartRootElementMissing", + 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) { diff --git a/src/DocumentFormat.OpenXml.Framework/Validation/ValidationResources.Designer.cs b/src/DocumentFormat.OpenXml.Framework/Validation/ValidationResources.Designer.cs index 0f07d0a74..2b484e862 100644 --- a/src/DocumentFormat.OpenXml.Framework/Validation/ValidationResources.Designer.cs +++ b/src/DocumentFormat.OpenXml.Framework/Validation/ValidationResources.Designer.cs @@ -438,6 +438,15 @@ internal static string Sch_MinLengthConstraintFailed { } } + /// + /// Looks up a localized string similar to The '{0}' part is missing its root element.. + /// + internal static string Sch_MissingPartRootElement { + get { + return ResourceManager.GetString("Sch_MissingPartRootElement", resourceCulture); + } + } + /// /// Looks up a localized string similar to The required attribute '{0}' is missing.. /// diff --git a/src/DocumentFormat.OpenXml.Framework/Validation/ValidationResources.resx b/src/DocumentFormat.OpenXml.Framework/Validation/ValidationResources.resx index ed8489361..836e53611 100644 --- a/src/DocumentFormat.OpenXml.Framework/Validation/ValidationResources.resx +++ b/src/DocumentFormat.OpenXml.Framework/Validation/ValidationResources.resx @@ -342,4 +342,7 @@ Cell contents have invalid value '{0}' for type '{1}'. + + The '{0}' part is missing its root element. + \ No newline at end of file diff --git a/test/DocumentFormat.OpenXml.Tests/ofapiTest/OpenXmlValidatorTest.cs b/test/DocumentFormat.OpenXml.Tests/ofapiTest/OpenXmlValidatorTest.cs index 4fdeb0daa..0a91d4bd3 100644 --- a/test/DocumentFormat.OpenXml.Tests/ofapiTest/OpenXmlValidatorTest.cs +++ b/test/DocumentFormat.OpenXml.Tests/ofapiTest/OpenXmlValidatorTest.cs @@ -3832,5 +3832,22 @@ public void VersionMismatchPartValidatingTest() Assert.Throws(() => 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 errors = O14Validator.Validate(document, TestContext.Current.CancellationToken); + ValidationErrorInfo info = errors.FirstOrDefault(); + + Assert.Single(errors); + Assert.Equal("Sch_PartRootElementMissing", info.Id); + Assert.Equal("The '/word/document.xml' part is missing its root element.", info.Description); + } + } } } From 12699db4234672f4f21f625c656225aceb748acc Mon Sep 17 00:00:00 2001 From: Michael Bowen <10384982+mikeebowen@users.noreply.github.com> Date: Fri, 16 May 2025 16:03:46 -0700 Subject: [PATCH 23/33] update how to check for empty parts --- .../Validation/DocumentValidator.cs | 6 ++++-- .../ofapiTest/OpenXmlValidatorTest.cs | 3 ++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/DocumentFormat.OpenXml.Framework/Validation/DocumentValidator.cs b/src/DocumentFormat.OpenXml.Framework/Validation/DocumentValidator.cs index 1a6647d8f..67df02935 100644 --- a/src/DocumentFormat.OpenXml.Framework/Validation/DocumentValidator.cs +++ b/src/DocumentFormat.OpenXml.Framework/Validation/DocumentValidator.cs @@ -3,6 +3,7 @@ using DocumentFormat.OpenXml.Packaging; using DocumentFormat.OpenXml.Validation.Schema; +using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; @@ -106,12 +107,13 @@ private void ValidatePart(OpenXmlPart part, ValidationContext context) { Validate(context); } - else + else if (part.Uri.ToString().EndsWith(".xml", System.StringComparison.InvariantCultureIgnoreCase) && + part.GetStream().Length == 0) { context.AddError(new ValidationErrorInfo { ErrorType = ValidationErrorType.Schema, - Id = "Sch_PartRootElementMissing", + Id = "Sch_MissingPartRootElement", Part = part, Description = SR.Format(ValidationResources.Sch_MissingPartRootElement, part.Uri), }); diff --git a/test/DocumentFormat.OpenXml.Tests/ofapiTest/OpenXmlValidatorTest.cs b/test/DocumentFormat.OpenXml.Tests/ofapiTest/OpenXmlValidatorTest.cs index 0a91d4bd3..4ee2f6a20 100644 --- a/test/DocumentFormat.OpenXml.Tests/ofapiTest/OpenXmlValidatorTest.cs +++ b/test/DocumentFormat.OpenXml.Tests/ofapiTest/OpenXmlValidatorTest.cs @@ -3845,8 +3845,9 @@ public void EmptyPartRootElementValidatingTest() ValidationErrorInfo info = errors.FirstOrDefault(); Assert.Single(errors); - Assert.Equal("Sch_PartRootElementMissing", info.Id); + 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); } } } From 87728823433d2e745387917d7739682e5ba6b512 Mon Sep 17 00:00:00 2001 From: Michael Bowen <10384982+mikeebowen@users.noreply.github.com> Date: Fri, 16 May 2025 16:04:05 -0700 Subject: [PATCH 24/33] fix tests --- .../DocxTests01.cs | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/test/DocumentFormat.OpenXml.Tests/DocxTests01.cs b/test/DocumentFormat.OpenXml.Tests/DocxTests01.cs index f69761522..2f54afdd0 100644 --- a/test/DocumentFormat.OpenXml.Tests/DocxTests01.cs +++ b/test/DocumentFormat.OpenXml.Tests/DocxTests01.cs @@ -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; @@ -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 = "hello"; + 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"); @@ -180,6 +193,7 @@ public void W049_AddNewPart_ToPackage() using (var doc = WordprocessingDocument.Open(stream, true)) { var wpcp = doc.AddNewPart("application/xml", "rid1232131"); + wpcp.CustomUI = new Office.CustomUI.CustomUI(); var v = new OpenXmlValidator(FileFormatVersions.Office2013); var errs = v.Validate(doc, TestContext.Current.CancellationToken); @@ -207,6 +221,7 @@ public void W047_AddNewPart_ToPackage() using (var doc = WordprocessingDocument.Open(stream, true)) { var wpcp = doc.AddNewPart("rid123123"); + wpcp.CustomUI = new Office.CustomUI.CustomUI(); var v = new OpenXmlValidator(FileFormatVersions.Office2013); var errs = v.Validate(doc, TestContext.Current.CancellationToken); @@ -239,6 +254,7 @@ public void W045_AddNewPart_ToPart() using (var doc = WordprocessingDocument.Open(stream, true)) { var wpcp = doc.MainDocumentPart.AddNewPart("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); @@ -266,6 +282,7 @@ public void W043_AddNewPart() using (var doc = WordprocessingDocument.Open(stream, true)) { var wpcp = doc.MainDocumentPart.AddNewPart("rid123123"); + wpcp.Comments = new W.Comments(); var v = new OpenXmlValidator(FileFormatVersions.Office2013); var errs = v.Validate(doc, TestContext.Current.CancellationToken); @@ -280,6 +297,7 @@ public void W042_AddNewPart() using (var doc = WordprocessingDocument.Open(stream, true)) { var wpcp = doc.MainDocumentPart.AddNewPart(); + wpcp.Comments = new W.Comments(); var v = new OpenXmlValidator(FileFormatVersions.Office2013); var errs = v.Validate(doc, TestContext.Current.CancellationToken); From a1847d7028e52a85de46295f055b40327e95473f Mon Sep 17 00:00:00 2001 From: Michael Bowen <10384982+mikeebowen@users.noreply.github.com> Date: Thu, 12 Jun 2025 14:30:57 -0700 Subject: [PATCH 25/33] custom file property part test passes --- .../PptxTests01.cs | 24 ++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/test/DocumentFormat.OpenXml.Tests/PptxTests01.cs b/test/DocumentFormat.OpenXml.Tests/PptxTests01.cs index fb888cde3..395e9edca 100644 --- a/test/DocumentFormat.OpenXml.Tests/PptxTests01.cs +++ b/test/DocumentFormat.OpenXml.Tests/PptxTests01.cs @@ -388,7 +388,8 @@ public void P002_Pptx_DeleteAdd_CoreExtendedProperties() { var corePart = doc.CoreFilePropertiesPart; var appPart = doc.ExtendedFilePropertiesPart; - var custFilePropsPart = doc.CustomFilePropertiesPart; + CustomFilePropertiesPart custFilePropsPart = doc.CustomFilePropertiesPart; + //custFilePropsPart. var thumbNailPart = doc.ThumbnailPart; doc.DeletePart(corePart); @@ -401,7 +402,23 @@ public void P002_Pptx_DeleteAdd_CoreExtendedProperties() doc.AddCoreFilePropertiesPart(); doc.AddExtendedFilePropertiesPart(); - doc.AddCustomFilePropertiesPart(); + var custFPP = doc.AddCustomFilePropertiesPart(); + var custFPPStream = custFPP.GetStream(); + using (var writer = new System.Xml.XmlTextWriter(custFPPStream, System.Text.Encoding.UTF8)) + { + //writer.WriteRaw("Mary2010-12-21T00:00:00Z"); + writer.WriteRaw(""" + + + + foobar + + + """); + writer.Flush(); + //custFPPStream.Seek(0, SeekOrigin.Begin); + } + doc.AddDigitalSignatureOriginPart(); doc.AddExtendedPart("relType", "contentType/xml", ".xml"); @@ -410,8 +427,9 @@ public void P002_Pptx_DeleteAdd_CoreExtendedProperties() tnPart = doc.AddThumbnailPart("image/jpg"); var v = new OpenXmlValidator(FileFormatVersions.Office2013); + var w = v.Validate(doc, TestContext.Current.CancellationToken); - Assert.Empty(v.Validate(doc, TestContext.Current.CancellationToken)); + Assert.Empty(w); } } From 92dcc742ff52cdfc49a1efa44acce787ff132b6e Mon Sep 17 00:00:00 2001 From: Michael Bowen <10384982+mikeebowen@users.noreply.github.com> Date: Thu, 12 Jun 2025 15:57:30 -0700 Subject: [PATCH 26/33] fix P002_Pptx_DeleteAdd_CoreExtendedProperties test --- .../PptxTests01.cs | 85 +++++++++++++++++-- 1 file changed, 77 insertions(+), 8 deletions(-) diff --git a/test/DocumentFormat.OpenXml.Tests/PptxTests01.cs b/test/DocumentFormat.OpenXml.Tests/PptxTests01.cs index 395e9edca..f90629260 100644 --- a/test/DocumentFormat.OpenXml.Tests/PptxTests01.cs +++ b/test/DocumentFormat.OpenXml.Tests/PptxTests01.cs @@ -388,8 +388,8 @@ public void P002_Pptx_DeleteAdd_CoreExtendedProperties() { var corePart = doc.CoreFilePropertiesPart; var appPart = doc.ExtendedFilePropertiesPart; - CustomFilePropertiesPart custFilePropsPart = doc.CustomFilePropertiesPart; - //custFilePropsPart. + var custFilePropsPart = doc.CustomFilePropertiesPart; + var thumbNailPart = doc.ThumbnailPart; doc.DeletePart(corePart); @@ -400,13 +400,84 @@ public void P002_Pptx_DeleteAdd_CoreExtendedProperties() doc.DeletePart(thumbNailPart); } - doc.AddCoreFilePropertiesPart(); - doc.AddExtendedFilePropertiesPart(); + var coreFPP = doc.AddCoreFilePropertiesPart(); + var coreFPPStream = coreFPP.GetStream(); + using (var writer = new System.Xml.XmlTextWriter(coreFPPStream, System.Text.Encoding.UTF8)) + { + writer.WriteRaw(""" + + + + Joey Daccord + Joey Daccord + 2 + 2025-06-12T19:21:34Z + 2025-06-12T21:23:11Z + + """); + writer.Flush(); + } + + var appFPP = doc.AddExtendedFilePropertiesPart(); + var appFPPStream = appFPP.GetStream(); + using (var writer = new System.Xml.XmlTextWriter(appFPPStream, System.Text.Encoding.UTF8)) + { + writer.WriteRaw(""" + + + 12 + 0 + Microsoft Office PowerPoint + Widescreen + 0 + 1 + 0 + 0 + 0 + false + + + + Fonts Used + + + 3 + + + Theme + + + 1 + + + Slide Titles + + + 1 + + + + + + Aptos + Aptos Display + Arial + Office Theme + PowerPoint Presentation + + + + false + false + false + 16.0000 + + """); + } var custFPP = doc.AddCustomFilePropertiesPart(); var custFPPStream = custFPP.GetStream(); using (var writer = new System.Xml.XmlTextWriter(custFPPStream, System.Text.Encoding.UTF8)) { - //writer.WriteRaw("Mary2010-12-21T00:00:00Z"); writer.WriteRaw(""" @@ -416,7 +487,6 @@ public void P002_Pptx_DeleteAdd_CoreExtendedProperties() """); writer.Flush(); - //custFPPStream.Seek(0, SeekOrigin.Begin); } doc.AddDigitalSignatureOriginPart(); @@ -427,9 +497,8 @@ public void P002_Pptx_DeleteAdd_CoreExtendedProperties() tnPart = doc.AddThumbnailPart("image/jpg"); var v = new OpenXmlValidator(FileFormatVersions.Office2013); - var w = v.Validate(doc, TestContext.Current.CancellationToken); - Assert.Empty(w); + Assert.Empty(v.Validate(doc, TestContext.Current.CancellationToken)); } } From 1df4b0b56ca13f8f05743faf5f12279a036891c2 Mon Sep 17 00:00:00 2001 From: Michael Bowen <10384982+mikeebowen@users.noreply.github.com> Date: Fri, 13 Jun 2025 10:55:24 -0700 Subject: [PATCH 27/33] fix X006_Xlsx_DeleteAdd_CoreExtendedProperties and W051_AddNewPart_ToOpenXmlPackage tests --- .../DocxTests01.cs | 4 +- .../XlsxTests01.cs | 75 ++++++++++++++++++- 2 files changed, 74 insertions(+), 5 deletions(-) diff --git a/test/DocumentFormat.OpenXml.Tests/DocxTests01.cs b/test/DocumentFormat.OpenXml.Tests/DocxTests01.cs index 2f54afdd0..48321869f 100644 --- a/test/DocumentFormat.OpenXml.Tests/DocxTests01.cs +++ b/test/DocumentFormat.OpenXml.Tests/DocxTests01.cs @@ -137,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("application/xml", "rid1232131"); + var footer = doc.MainDocumentPart.AddNewPart(); + footer.Footer = new W.Footer(); var v = new OpenXmlValidator(FileFormatVersions.Office2013); var errs = v.Validate(doc, TestContext.Current.CancellationToken); diff --git a/test/DocumentFormat.OpenXml.Tests/XlsxTests01.cs b/test/DocumentFormat.OpenXml.Tests/XlsxTests01.cs index 21f91112b..53c50fa1f 100644 --- a/test/DocumentFormat.OpenXml.Tests/XlsxTests01.cs +++ b/test/DocumentFormat.OpenXml.Tests/XlsxTests01.cs @@ -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(""" + + + Shohei Ohtani + Shohei Ohtani + 2015-06-05T18:17:20Z + 2025-06-13T17:11:50Z + + """); + + writer.Flush(); + } + + var eFPP = doc.AddExtendedFilePropertiesPart(); + var eFPPStream = eFPP.GetStream(); + + using (var writer = new System.Xml.XmlTextWriter(eFPPStream, System.Text.Encoding.UTF8)) + { + writer.WriteRaw(""" + + + Microsoft Excel + 0 + false + + + + Worksheets + + + 1 + + + + + + Sheet1 + + + + false + false + false + 16.0300 + + """); + + writer.Flush(); + } + + var custFPP = doc.AddCustomFilePropertiesPart(); + var custFPPStream = custFPP.GetStream(); + + using (var writer = new System.Xml.XmlTextWriter(custFPPStream, System.Text.Encoding.UTF8)) + { + writer.WriteRaw(""" + + + + tacocat + + + """); + + writer.Flush(); + } + doc.AddDigitalSignatureOriginPart(); doc.AddExtendedPart("relType", "contentType/xml", ".xml"); var tnPart = doc.AddThumbnailPart(ThumbnailPartType.Jpeg); From d7ee41babbf5ec1be5c29662078f0a2c2b94f781 Mon Sep 17 00:00:00 2001 From: Michael Bowen <10384982+mikeebowen@users.noreply.github.com> Date: Fri, 13 Jun 2025 11:09:18 -0700 Subject: [PATCH 28/33] add blank line --- test/DocumentFormat.OpenXml.Tests/PptxTests01.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/test/DocumentFormat.OpenXml.Tests/PptxTests01.cs b/test/DocumentFormat.OpenXml.Tests/PptxTests01.cs index f90629260..3c516554c 100644 --- a/test/DocumentFormat.OpenXml.Tests/PptxTests01.cs +++ b/test/DocumentFormat.OpenXml.Tests/PptxTests01.cs @@ -474,6 +474,7 @@ public void P002_Pptx_DeleteAdd_CoreExtendedProperties() """); } + var custFPP = doc.AddCustomFilePropertiesPart(); var custFPPStream = custFPP.GetStream(); using (var writer = new System.Xml.XmlTextWriter(custFPPStream, System.Text.Encoding.UTF8)) From 2097f050535ba3fc2e4ba7a6bc6d138e58e0895a Mon Sep 17 00:00:00 2001 From: Michael Bowen <10384982+mikeebowen@users.noreply.github.com> Date: Mon, 16 Jun 2025 08:42:53 -0700 Subject: [PATCH 29/33] remove file from earlier version of API --- .../Features/IMinimumDocumentFeature.cs | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 src/DocumentFormat.OpenXml.Framework/Features/IMinimumDocumentFeature.cs diff --git a/src/DocumentFormat.OpenXml.Framework/Features/IMinimumDocumentFeature.cs b/src/DocumentFormat.OpenXml.Framework/Features/IMinimumDocumentFeature.cs deleted file mode 100644 index 3f1b725cf..000000000 --- a/src/DocumentFormat.OpenXml.Framework/Features/IMinimumDocumentFeature.cs +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -namespace DocumentFormat.OpenXml.Features; - -internal interface IMinimumDocumentFeature -{ - bool Validate(); -} From 31aec5a8c78c16fb2afca2f7322363162a43adad Mon Sep 17 00:00:00 2001 From: Michael Bowen <10384982+mikeebowen@users.noreply.github.com> Date: Mon, 16 Jun 2025 15:03:01 -0700 Subject: [PATCH 30/33] add IsEmptyPart method to ensure stream is disposed --- .../Packaging/OpenXmlPart.cs | 12 ++++++++++++ .../Packaging/OpenXmlPartContainer.cs | 5 +++++ .../Validation/DocumentValidator.cs | 3 +-- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/DocumentFormat.OpenXml.Framework/Packaging/OpenXmlPart.cs b/src/DocumentFormat.OpenXml.Framework/Packaging/OpenXmlPart.cs index 873e15107..bf9caa1c7 100644 --- a/src/DocumentFormat.OpenXml.Framework/Packaging/OpenXmlPart.cs +++ b/src/DocumentFormat.OpenXml.Framework/Packaging/OpenXmlPart.cs @@ -448,6 +448,18 @@ internal virtual bool IsInVersion(FileFormatVersions version) return true; } + internal override bool IsEmptyPart() + { + bool isEmptyPart; + + using (Stream stream = GetStream()) + { + isEmptyPart = stream.Length == 0; + } + + return isEmptyPart; + } + #endregion #region internal methods diff --git a/src/DocumentFormat.OpenXml.Framework/Packaging/OpenXmlPartContainer.cs b/src/DocumentFormat.OpenXml.Framework/Packaging/OpenXmlPartContainer.cs index 442154644..bd9d8e78c 100644 --- a/src/DocumentFormat.OpenXml.Framework/Packaging/OpenXmlPartContainer.cs +++ b/src/DocumentFormat.OpenXml.Framework/Packaging/OpenXmlPartContainer.cs @@ -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 reachableParts); + internal virtual bool IsEmptyPart() + { + return false; + } + #endregion // Checks if the target part is in the same OpenXmlPackage as this part. diff --git a/src/DocumentFormat.OpenXml.Framework/Validation/DocumentValidator.cs b/src/DocumentFormat.OpenXml.Framework/Validation/DocumentValidator.cs index 67df02935..7a68888fc 100644 --- a/src/DocumentFormat.OpenXml.Framework/Validation/DocumentValidator.cs +++ b/src/DocumentFormat.OpenXml.Framework/Validation/DocumentValidator.cs @@ -107,8 +107,7 @@ private void ValidatePart(OpenXmlPart part, ValidationContext context) { Validate(context); } - else if (part.Uri.ToString().EndsWith(".xml", System.StringComparison.InvariantCultureIgnoreCase) && - part.GetStream().Length == 0) + else if (part.Uri.ToString().EndsWith(".xml", System.StringComparison.InvariantCultureIgnoreCase) && part.IsEmptyPart()) { context.AddError(new ValidationErrorInfo { From 2d5dd09e694762884990603b86a62aae900d1560 Mon Sep 17 00:00:00 2001 From: Michael Bowen <10384982+mikeebowen@users.noreply.github.com> Date: Mon, 30 Jun 2025 10:25:16 -0700 Subject: [PATCH 31/33] Update src/DocumentFormat.OpenXml.Framework/Packaging/OpenXmlPart.cs Co-authored-by: Taylor Southwick --- .../Packaging/OpenXmlPart.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/DocumentFormat.OpenXml.Framework/Packaging/OpenXmlPart.cs b/src/DocumentFormat.OpenXml.Framework/Packaging/OpenXmlPart.cs index bf9caa1c7..9fbc1d04a 100644 --- a/src/DocumentFormat.OpenXml.Framework/Packaging/OpenXmlPart.cs +++ b/src/DocumentFormat.OpenXml.Framework/Packaging/OpenXmlPart.cs @@ -450,14 +450,10 @@ internal virtual bool IsInVersion(FileFormatVersions version) internal override bool IsEmptyPart() { - bool isEmptyPart; - using (Stream stream = GetStream()) { - isEmptyPart = stream.Length == 0; + return stream.Length == 0; } - - return isEmptyPart; } #endregion From 4861f13f507a6743e2e401949420a1e6ef34c034 Mon Sep 17 00:00:00 2001 From: Michael Bowen <10384982+mikeebowen@users.noreply.github.com> Date: Mon, 30 Jun 2025 12:42:32 -0700 Subject: [PATCH 32/33] move file extension check to IsEmptyPartMethod --- .../Packaging/OpenXmlPart.cs | 5 +++++ .../Validation/DocumentValidator.cs | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/DocumentFormat.OpenXml.Framework/Packaging/OpenXmlPart.cs b/src/DocumentFormat.OpenXml.Framework/Packaging/OpenXmlPart.cs index 9fbc1d04a..7ae133f85 100644 --- a/src/DocumentFormat.OpenXml.Framework/Packaging/OpenXmlPart.cs +++ b/src/DocumentFormat.OpenXml.Framework/Packaging/OpenXmlPart.cs @@ -450,6 +450,11 @@ internal virtual bool IsInVersion(FileFormatVersions version) internal override bool IsEmptyPart() { + if (!Uri.ToString().EndsWith(".xml", System.StringComparison.InvariantCultureIgnoreCase)) + { + return false; + } + using (Stream stream = GetStream()) { return stream.Length == 0; diff --git a/src/DocumentFormat.OpenXml.Framework/Validation/DocumentValidator.cs b/src/DocumentFormat.OpenXml.Framework/Validation/DocumentValidator.cs index 7a68888fc..1da84c31e 100644 --- a/src/DocumentFormat.OpenXml.Framework/Validation/DocumentValidator.cs +++ b/src/DocumentFormat.OpenXml.Framework/Validation/DocumentValidator.cs @@ -107,7 +107,7 @@ private void ValidatePart(OpenXmlPart part, ValidationContext context) { Validate(context); } - else if (part.Uri.ToString().EndsWith(".xml", System.StringComparison.InvariantCultureIgnoreCase) && part.IsEmptyPart()) + else if (part.IsEmptyPart()) { context.AddError(new ValidationErrorInfo { From 25e5c67b43557dbc8b7328b1b7b65d3a4142738d Mon Sep 17 00:00:00 2001 From: Michael Bowen <10384982+mikeebowen@users.noreply.github.com> Date: Mon, 30 Jun 2025 14:02:19 -0700 Subject: [PATCH 33/33] removed unnecessary using --- .../Validation/DocumentValidator.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/DocumentFormat.OpenXml.Framework/Validation/DocumentValidator.cs b/src/DocumentFormat.OpenXml.Framework/Validation/DocumentValidator.cs index 1da84c31e..c66225b5b 100644 --- a/src/DocumentFormat.OpenXml.Framework/Validation/DocumentValidator.cs +++ b/src/DocumentFormat.OpenXml.Framework/Validation/DocumentValidator.cs @@ -3,7 +3,6 @@ using DocumentFormat.OpenXml.Packaging; using DocumentFormat.OpenXml.Validation.Schema; -using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq;