{"id":18937533,"url":"https://github.com/verifytests/verify.openxml","last_synced_at":"2026-05-26T08:04:03.624Z","repository":{"id":264978156,"uuid":"822886420","full_name":"VerifyTests/Verify.OpenXml","owner":"VerifyTests","description":"Extends Verify to allow verification of Word and Excel documents via OpenXML.","archived":false,"fork":false,"pushed_at":"2026-05-18T02:14:42.000Z","size":366,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-05-18T02:50:34.107Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/VerifyTests.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"license.txt","code_of_conduct":"code_of_conduct.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":"VerifyTests"}},"created_at":"2024-07-02T03:01:12.000Z","updated_at":"2026-05-18T02:14:46.000Z","dependencies_parsed_at":"2024-11-27T06:35:34.711Z","dependency_job_id":null,"html_url":"https://github.com/VerifyTests/Verify.OpenXml","commit_stats":null,"previous_names":["verifytests/verify.openxml"],"tags_count":19,"template":false,"template_full_name":null,"purl":"pkg:github/VerifyTests/Verify.OpenXml","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/VerifyTests%2FVerify.OpenXml","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/VerifyTests%2FVerify.OpenXml/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/VerifyTests%2FVerify.OpenXml/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/VerifyTests%2FVerify.OpenXml/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/VerifyTests","download_url":"https://codeload.github.com/VerifyTests/Verify.OpenXml/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/VerifyTests%2FVerify.OpenXml/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33508317,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T03:12:49.672Z","status":"ssl_error","status_checked_at":"2026-05-26T03:12:47.976Z","response_time":63,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-08T12:11:35.421Z","updated_at":"2026-05-26T08:04:03.575Z","avatar_url":"https://github.com/VerifyTests.png","language":"C#","funding_links":["https://github.com/sponsors/VerifyTests"],"categories":[],"sub_categories":[],"readme":"# \u003cimg src=\"/src/icon.png\" height=\"30px\"\u003e Verify.OpenXML\n\n[![Discussions](https://img.shields.io/badge/Verify-Discussions-yellow?svg=true\u0026label=)](https://github.com/orgs/VerifyTests/discussions)\n[![Build status](https://img.shields.io/appveyor/build/SimonCropp/verify-openxml)](https://ci.appveyor.com/project/SimonCropp/verify-openxml)\n[![NuGet Status](https://img.shields.io/nuget/v/Verify.OpenXML.svg)](https://www.nuget.org/packages/Verify.OpenXML/)\n\nExtends [Verify](https://github.com/VerifyTests/Verify) to allow verification of Word, Excel, and PowerPoint documents via [OpenXML](https://github.com/dotnet/Open-XML-SDK/).\u003c!-- singleLineInclude: intro. path: /docs/intro.include.md --\u003e\n\nSupports Excel (xlsx), Word (docx), and PowerPoint (pptx) documents.\n\n\n## Features\n\n\n### Excel (xlsx)\n\n * Converts workbooks to CSV format for each worksheet\n * Extracts formulas and displays them alongside cell values\n * Captures document metadata (title, subject, creator, keywords, category, etc.)\n * Supports date scrubbing and GUID scrubbing for deterministic tests\n * Generates deterministic XLSX output using DeterministicIoPackaging\n\n\n### Word (docx)\n\n * Extracts document text content from paragraphs and tables\n * Captures document properties (title, subject, creator, keywords, etc.)\n * Captures custom document properties\n * Extracts font information\n * Generates deterministic DOCX output using DeterministicIoPackaging\n * Optionally renders each page to PNG via [Morph](https://github.com/SimonCropp/Morph) (opt-in)\n\n\n### PowerPoint (pptx)\n\n * Extracts slide text from every slide, separated by `---`\n * Captures document properties (title, subject, creator, keywords, etc.)\n * Reports slide count\n * Generates deterministic PPTX output using DeterministicIoPackaging\n\n**See [Milestones](../../milestones?state=closed) for release notes.**\n\n\n## Sponsors\n\n\n### Entity Framework Extensions\u003c!-- include: sponsors. path: /docs/sponsors.include.md --\u003e\n\n[Entity Framework Extensions](https://entityframework-extensions.net/?utm_source=simoncropp\u0026utm_medium=Verify.OpenXML) is a major sponsor and is proud to contribute to the development this project.\n\n[![Entity Framework Extensions](https://raw.githubusercontent.com/VerifyTests/Verify.OpenXML/refs/heads/main/docs/zzz.png)](https://entityframework-extensions.net/?utm_source=simoncropp\u0026utm_medium=Verify.OpenXML)\n\n### Developed using JetBrains IDEs\n\n[![JetBrains logo.](https://raw.githubusercontent.com/VerifyTests/Verify.OpenXml/main/docs/jetbrains.png)](https://jb.gg/OpenSourceSupport)\u003c!-- endInclude --\u003e\n\n\n## NuGet\n\n * https://nuget.org/packages/Verify.OpenXML\n\n\n## Usage\n\n\n### Enable Verify.OpenXml\n\n\u003c!-- snippet: enable --\u003e\n\u003ca id='snippet-enable'\u003e\u003c/a\u003e\n```cs\n[ModuleInitializer]\npublic static void Initialize() =\u003e\n    VerifyOpenXml.Initialize();\n```\n\u003csup\u003e\u003ca href='/src/Tests/ModuleInitializer.cs#L3-L9' title='Snippet source file'\u003esnippet source\u003c/a\u003e | \u003ca href='#snippet-enable' title='Start of snippet'\u003eanchor\u003c/a\u003e\u003c/sup\u003e\n\u003c!-- endSnippet --\u003e\n\n\n### Excel\n\n\n#### Verify a file\n\n\u003c!-- snippet: VerifyExcel --\u003e\n\u003ca id='snippet-VerifyExcel'\u003e\u003c/a\u003e\n```cs\n[Test]\npublic Task VerifyExcel() =\u003e\n    VerifyFile(\"sample.xlsx\");\n```\n\u003csup\u003e\u003ca href='/src/Tests/Samples.cs#L4-L10' title='Snippet source file'\u003esnippet source\u003c/a\u003e | \u003ca href='#snippet-VerifyExcel' title='Start of snippet'\u003eanchor\u003c/a\u003e\u003c/sup\u003e\n\u003c!-- endSnippet --\u003e\n\n\n#### Verify a Stream\n\n\u003c!-- snippet: VerifyExcelStream --\u003e\n\u003ca id='snippet-VerifyExcelStream'\u003e\u003c/a\u003e\n```cs\n[Test]\npublic Task VerifyExcelStream()\n{\n    var stream = new MemoryStream(File.ReadAllBytes(\"sample.xlsx\"));\n    return Verify(stream, \"xlsx\");\n}\n```\n\u003csup\u003e\u003ca href='/src/Tests/Samples.cs#L33-L42' title='Snippet source file'\u003esnippet source\u003c/a\u003e | \u003ca href='#snippet-VerifyExcelStream' title='Start of snippet'\u003eanchor\u003c/a\u003e\u003c/sup\u003e\n\u003c!-- endSnippet --\u003e\n\n\n#### Verify a SpreadsheetDocument\n\n\u003c!-- snippet: SpreadsheetDocument --\u003e\n\u003ca id='snippet-SpreadsheetDocument'\u003e\u003c/a\u003e\n```cs\n[Test]\npublic async Task VerifySpreadsheetDocument()\n{\n    await using var stream = File.OpenRead(\"sample.xlsx\");\n    using var reader = SpreadsheetDocument.Open(stream, false);\n    await Verify(reader);\n}\n```\n\u003csup\u003e\u003ca href='/src/Tests/Samples.cs#L21-L31' title='Snippet source file'\u003esnippet source\u003c/a\u003e | \u003ca href='#snippet-SpreadsheetDocument' title='Start of snippet'\u003eanchor\u003c/a\u003e\u003c/sup\u003e\n\u003c!-- endSnippet --\u003e\n\n\n#### Example snapshot\n\n\u003c!-- snippet: Samples.VerifyExcel.verified.csv --\u003e\n\u003ca id='snippet-Samples.VerifyExcel.verified.csv'\u003e\u003c/a\u003e\n```csv\n0,First Name,Last Name,Gender,Country,Date,Age,Id,Formula\n1,Dulce,Abril,Female,United States,2017-10-15,32,1562,G2+H21594 (G2+H2)\n2,Mara,Hashimoto,Female,Great Britain,2016-08-16,25,1582,1607\n3,Philip,Gent,Male,France,2015-05-21,36,2587,2623\n4,Kathleen,Hanner,Female,United States,2017-10-15,25,3549,3574\n5,Nereida,Magwood,Female,United States,2016-08-16,58,2468,2526\n6,Gaston,Brumm,Male,United States,2015-05-21,24,2554,2578\n```\n\u003csup\u003e\u003ca href='/src/Tests/Samples.VerifyExcel.verified.csv#L1-L7' title='Snippet source file'\u003esnippet source\u003c/a\u003e | \u003ca href='#snippet-Samples.VerifyExcel.verified.csv' title='Start of snippet'\u003eanchor\u003c/a\u003e\u003c/sup\u003e\n\u003c!-- endSnippet --\u003e\n\n\n### Word\n\n\n#### Verify a file\n\n\u003c!-- snippet: VerifyWord --\u003e\n\u003ca id='snippet-VerifyWord'\u003e\u003c/a\u003e\n```cs\n[Test]\npublic Task VerifyWord() =\u003e\n    VerifyFile(\"sample.docx\");\n```\n\u003csup\u003e\u003ca href='/src/Tests/Samples.cs#L44-L50' title='Snippet source file'\u003esnippet source\u003c/a\u003e | \u003ca href='#snippet-VerifyWord' title='Start of snippet'\u003eanchor\u003c/a\u003e\u003c/sup\u003e\n\u003c!-- endSnippet --\u003e\n\n\n#### Verify a Stream\n\n\u003c!-- snippet: VerifyWordStream --\u003e\n\u003ca id='snippet-VerifyWordStream'\u003e\u003c/a\u003e\n```cs\n[Test]\npublic Task VerifyWordStream()\n{\n    var stream = new MemoryStream(File.ReadAllBytes(\"sample.docx\"));\n    return Verify(stream, \"docx\");\n}\n```\n\u003csup\u003e\u003ca href='/src/Tests/Samples.cs#L64-L73' title='Snippet source file'\u003esnippet source\u003c/a\u003e | \u003ca href='#snippet-VerifyWordStream' title='Start of snippet'\u003eanchor\u003c/a\u003e\u003c/sup\u003e\n\u003c!-- endSnippet --\u003e\n\n\n### Binary output across .NET frameworks\n\nWhen verifying binary package output (xlsx, docx, nupkg, etc.) across multiple target frameworks (e.g. net48 and net10.0), the binary output may differ due to Deflate compression implementation differences. The XML content within entries is identical — only the compressed bytes differ. Use `UniqueForRuntime` to generate framework-specific verified files:\n\n```cs\nawait Verify(stream, extension: \"xlsx\")\n    .UniqueForRuntime();\n```\n\nSee [Verify Naming docs](https://github.com/VerifyTests/Verify/blob/main/docs/naming.md) for more details.\n\n\n#### Verify a WordprocessingDocument\n\n\u003c!-- snippet: WordprocessingDocument --\u003e\n\u003ca id='snippet-WordprocessingDocument'\u003e\u003c/a\u003e\n```cs\n[Test]\npublic async Task VerifyWordprocessingDocument()\n{\n    await using var stream = File.OpenRead(\"sample.docx\");\n    using var reader = WordprocessingDocument.Open(stream, false);\n    await Verify(reader);\n}\n```\n\u003csup\u003e\u003ca href='/src/Tests/Samples.cs#L52-L62' title='Snippet source file'\u003esnippet source\u003c/a\u003e | \u003ca href='#snippet-WordprocessingDocument' title='Start of snippet'\u003eanchor\u003c/a\u003e\u003c/sup\u003e\n\u003c!-- endSnippet --\u003e\n\n\n#### Render pages to PNG (opt-in)\n\nVerify.OpenXml can additionally snapshot a rendered PNG of every page of a `.docx` using the [Morph](https://github.com/SimonCropp/Morph) renderer. This catches visual regressions (layout, fonts, images, tables) that the text-based snapshot would miss.\n\n##### Enabling rendering\n\nThe base [`Morph.OpenXml`](https://nuget.org/packages/Morph.OpenXml) package is referenced automatically by Verify.OpenXml on `net10.0`. To turn on rendering, add **exactly one** backend package to the test project:\n\n[`Morph.OpenXml.Skia`](https://nuget.org/packages/Morph.OpenXml.Skia) — uses [SkiaSharp](https://github.com/mono/SkiaSharp):\n\n```xml\n\u003cPackageReference Include=\"Morph.OpenXml.Skia\" /\u003e\n```\n\nor [`Morph.OpenXml.ImageSharp`](https://nuget.org/packages/Morph.OpenXml.ImageSharp) — uses [ImageSharp](https://github.com/SixLabors/ImageSharp), fully managed:\n\n```xml\n\u003cPackageReference Include=\"Morph.OpenXml.ImageSharp\" /\u003e\n```\n\nThe backend is detected at runtime by probing for the assembly. No code changes are needed in `ModuleInitializer.cs` — the existing `VerifyOpenXml.Initialize()` call picks it up automatically.\n\n##### Output\n\nWhen a backend is present, every Word verification (file, stream, or `WordprocessingDocument`) produces additional PNG targets — one per page — alongside the existing `.verified.docx` and `.verified.txt` files. For example, a two-page `VerifyWord` test produces:\n\n```\nSamples.VerifyWord.verified.docx\nSamples.VerifyWord#00.verified.txt\nSamples.VerifyWord#01.verified.txt\nSamples.VerifyWord#page01.verified.png\nSamples.VerifyWord#page02.verified.png\n```\n\nPages are zero-padded to two digits (`page01`, `page02`, ...) so file ordering matches page order.\n\n##### Backend selection rules\n\n * **Neither backend referenced** — rendering is silently skipped. The text and binary targets are still produced. This is the default for consumers who do not opt in.\n * **One backend referenced** — that backend is used for all Word verifications.\n * **Both backends referenced** — an exception is thrown on the first Word verification with a clear message. Pick one.\n\n##### Target framework support\n\nRendering is only available on `net10.0` because Morph targets `net10.0` only. On `net472`, `net48`, `net8.0`, and `net9.0`, Word verification continues to produce only the existing text and binary targets — the rendering code is conditionally compiled out.\n\n##### Cross-platform PNG stability\n\nPNG output from Skia and ImageSharp depends on installed fonts and platform-specific rasterization. A `.verified.png` generated on one machine may not be byte-identical on another OS or with different fonts installed. Recommendations:\n\n * Generate and commit `.verified.png` files from a single canonical machine (often a CI agent).\n * For cross-platform CI, combine with `UniqueForOSPlatform()` so each OS gets its own `.verified.png`:\n\n```cs\nawait Verify(stream, \"docx\")\n    .UniqueForOSPlatform();\n```\n\n * Consider [PNG SSIM comparer](https://github.com/VerifyTests/Verify/blob/main/docs/comparer.md#png-ssim-comparer) for tolerance-based image diffing.\n\nSee [Verify Naming docs](https://github.com/VerifyTests/Verify/blob/main/docs/naming.md) for the full list of `UniqueFor*` modifiers.\n\n\n##### Sharing one test suite across both backends\n\nFor a worked example of running the same test suite against both backends side-by-side, see the [`Tests.Skia`](/src/Tests.Skia) and [`Tests.ImageSharp`](/src/Tests.ImageSharp) projects in this repository. Both projects link the source files from [`Tests`](/src/Tests) and use `DerivePathInfo` in their `ModuleInitializer` to redirect snapshots into the per-backend project directory:\n\n```cs\n[ModuleInitializer]\npublic static void Initialize()\n{\n    VerifyOpenXml.Initialize();\n\n    var projectDir = ProjectDir();\n    Verifier.DerivePathInfo(\n        (sourceFile, projectDirectory, type, method) =\u003e\n            new(directory: projectDir, typeName: type.Name, methodName: method.Name));\n}\n\nstatic string ProjectDir([CallerFilePath] string here = \"\") =\u003e\n    Path.GetDirectoryName(here)!;\n```\n\nThis pattern lets a single set of tests produce two parallel sets of `.verified.*` snapshots — one per rendering backend.\n\n\n### PowerPoint\n\n\n#### Verify a file\n\n\u003c!-- snippet: VerifyPowerpoint --\u003e\n\u003ca id='snippet-VerifyPowerpoint'\u003e\u003c/a\u003e\n```cs\n[Test]\npublic Task VerifyPowerpoint() =\u003e\n    VerifyFile(\"sample.pptx\");\n```\n\u003csup\u003e\u003ca href='/src/Tests/Samples.cs#L75-L81' title='Snippet source file'\u003esnippet source\u003c/a\u003e | \u003ca href='#snippet-VerifyPowerpoint' title='Start of snippet'\u003eanchor\u003c/a\u003e\u003c/sup\u003e\n\u003c!-- endSnippet --\u003e\n\n\n#### Verify a Stream\n\n\u003c!-- snippet: VerifyPowerpointStream --\u003e\n\u003ca id='snippet-VerifyPowerpointStream'\u003e\u003c/a\u003e\n```cs\n[Test]\npublic Task VerifyPowerpointStream()\n{\n    var stream = new MemoryStream(File.ReadAllBytes(\"sample.pptx\"));\n    return Verify(stream, \"pptx\");\n}\n```\n\u003csup\u003e\u003ca href='/src/Tests/Samples.cs#L95-L104' title='Snippet source file'\u003esnippet source\u003c/a\u003e | \u003ca href='#snippet-VerifyPowerpointStream' title='Start of snippet'\u003eanchor\u003c/a\u003e\u003c/sup\u003e\n\u003c!-- endSnippet --\u003e\n\n\n#### Verify a PresentationDocument\n\n\u003c!-- snippet: PresentationDocument --\u003e\n\u003ca id='snippet-PresentationDocument'\u003e\u003c/a\u003e\n```cs\n[Test]\npublic async Task VerifyPresentationDocument()\n{\n    await using var stream = File.OpenRead(\"sample.pptx\");\n    using var reader = PresentationDocument.Open(stream, false);\n    await Verify(reader);\n}\n```\n\u003csup\u003e\u003ca href='/src/Tests/Samples.cs#L83-L93' title='Snippet source file'\u003esnippet source\u003c/a\u003e | \u003ca href='#snippet-PresentationDocument' title='Start of snippet'\u003eanchor\u003c/a\u003e\u003c/sup\u003e\n\u003c!-- endSnippet --\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fverifytests%2Fverify.openxml","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fverifytests%2Fverify.openxml","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fverifytests%2Fverify.openxml/lists"}