diff --git a/src/DeterministicIoPackaging/DeterministicPackage.cs b/src/DeterministicIoPackaging/DeterministicPackage.cs index 0d1fad9..b91bb65 100644 --- a/src/DeterministicIoPackaging/DeterministicPackage.cs +++ b/src/DeterministicIoPackaging/DeterministicPackage.cs @@ -5,7 +5,7 @@ public static partial class DeterministicPackage public static DateTime StableDate { get; } = new(2020, 1, 1, 0, 0, 0, DateTimeKind.Utc); public static DateTimeOffset StableDateOffset { get; } = new(StableDate); - static PatcherSet CreatePatchers() + static PatcherSet CreatePatchers(IReadOnlyCollection partNames) { var workbookRelsPatcher = new WorkbookRelationshipPatcher(); var documentRelsPatcher = new DocumentRelationshipPatcher(); @@ -14,7 +14,7 @@ static PatcherSet CreatePatchers() var pptxRelsPatcher = new PptxRelationshipPatcher(); IReadOnlyList patchers = [ - new ContentTypesPatcher(), + new ContentTypesPatcher(partNames), new RelationshipPatcher(), sheetRelsPatcher, new SheetPatcher(sheetRelsPatcher), diff --git a/src/DeterministicIoPackaging/DeterministicPackage_Convert.cs b/src/DeterministicIoPackaging/DeterministicPackage_Convert.cs index e091ed3..e9128c7 100644 --- a/src/DeterministicIoPackaging/DeterministicPackage_Convert.cs +++ b/src/DeterministicIoPackaging/DeterministicPackage_Convert.cs @@ -9,11 +9,14 @@ public static partial class DeterministicPackage // and patched in place with no extra copy. public static MemoryStream Convert(Stream source) { - var patchers = CreatePatchers(); var target = new MemoryStream(); using (var sourceArchive = ReadArchive(source)) using (var targetArchive = CreateArchive(target)) { + // Part names must be known before patching begins so the + // ContentTypesPatcher can canonicalize the content-type map against + // every part in the package, not just the ones the input map lists. + var patchers = CreatePatchers(CollectPartNames(sourceArchive)); foreach (var sourceEntry in sourceArchive.OrderedEntries()) { DuplicateEntry(sourceEntry, targetArchive, patchers); @@ -27,11 +30,11 @@ public static MemoryStream Convert(Stream source) public static async Task ConvertAsync(Stream source, Cancel token = default) { - var patchers = CreatePatchers(); var target = new MemoryStream(); using (var sourceArchive = ReadArchive(source)) using (var targetArchive = CreateArchive(target)) { + var patchers = CreatePatchers(CollectPartNames(sourceArchive)); foreach (var sourceEntry in sourceArchive.OrderedEntries()) { await DuplicateEntryAsync(sourceEntry, targetArchive, patchers, token); @@ -43,6 +46,26 @@ public static async Task ConvertAsync(Stream source, Cancel token return target; } + // Every part in the package, as leading-slash PartName values, for the + // ContentTypesPatcher. [Content_Types].xml is not itself a part, and skipped + // entries are dropped from the output, so neither belongs in the map. + static IReadOnlyCollection CollectPartNames(Archive archive) + { + var partNames = new List(); + foreach (var entry in archive.Entries) + { + if (entry.FullName is "[Content_Types].xml" || + IsSkippedEntry(entry)) + { + continue; + } + + partNames.Add(ContentTypesPatcher.ToPartName(entry.FullName)); + } + + return partNames; + } + // ZIP local file header signature ("PK\x03\x04"). // Used to detect nested ZIP packages (e.g. xlsx/docx/pptx embedded inside // word/embeddings/, ppt/embeddings/, xl/embeddings/) so they can be diff --git a/src/DeterministicIoPackaging/Patching/ContentTypesPatcher.cs b/src/DeterministicIoPackaging/Patching/ContentTypesPatcher.cs index cb4e941..8d5a106 100644 --- a/src/DeterministicIoPackaging/Patching/ContentTypesPatcher.cs +++ b/src/DeterministicIoPackaging/Patching/ContentTypesPatcher.cs @@ -1,5 +1,42 @@ -class ContentTypesPatcher : IExactMatchPatcher +// Canonicalizes [Content_Types].xml so it is byte-identical run-to-run and +// producer-independent. +// +// The OPC content-type map lets a part's content type be declared two equivalent +// ways: a that applies to every part with that +// extension, or a per-part . For an extension whose +// parts carry several different content types (e.g. "xml", shared by workbook / +// worksheet / styles / sharedStrings / core-properties), the producer must pick +// ONE content type to be the extension's Default and emit Overrides for the rest. +// +// System.IO.Packaging (used by DocumentFormat.OpenXml's Clone, and by producers +// such as Aspose.Cells) makes that pick via internal collection ordering, so +// which content type wins the "xml" Default — and therefore which parts become +// Overrides — is not stable across producers, SDK versions, or runtimes. Merely +// sorting the entries (the previous behavior here) preserves whatever split the +// input happened to use, so the non-determinism survives. +// +// This patcher removes the ambiguity by recomputing the split from scratch: +// 1. Determine every part's effective content type from the input map +// (its Override if present, otherwise the Default for its extension). +// 2. For each extension, choose a canonical Default deterministically: +// the content type shared by the MOST parts of that extension, with the +// lexicographically-smallest (Ordinal) content type breaking ties. +// 3. Emit an for every part whose effective content type differs +// from its extension's chosen Default. +// 4. Sort Defaults by Extension and Overrides by PartName (both Ordinal). +// +// The rewrite is OPC-preserving: every part still resolves to exactly the content +// type it had in the input. It needs the full set of part names — not just what +// the input map lists — because changing an extension's Default turns previously +// implicit parts into ones that must now be spelled out as Overrides. Those names +// come from the package's zip entries, supplied by DeterministicPackage.Convert. +class ContentTypesPatcher(IReadOnlyCollection partNames) : + IExactMatchPatcher { + static XNamespace ns = "http://schemas.openxmlformats.org/package/2006/content-types"; + static XName defaultName = ns + "Default"; + static XName overrideName = ns + "Override"; + public string ExactMatch => "[Content_Types].xml"; public bool IsMatch(Entry entry) => @@ -8,12 +45,158 @@ public bool IsMatch(Entry entry) => public void PatchXml(XDocument xml, string entryName) { var root = xml.Root!; - var elements = root.Elements() - .OrderBy(_ => _.Name.LocalName) - .ThenBy(_ => (string?)_.Attribute("Extension") ?? "") - .ThenBy(_ => (string?)_.Attribute("PartName") ?? "") + + var defaults = ReadDefaults(root); + var overrides = ReadOverrides(root); + + // Group each part by its extension, tracking its effective content type, + // so every extension can be assigned a single canonical Default below. + var byExtension = new Dictionary>(StringComparer.Ordinal); + foreach (var partName in partNames) + { + var extension = Extension(partName); + if (extension.Length == 0) + { + continue; + } + + var contentType = EffectiveContentType(partName, extension, defaults, overrides); + if (contentType == null) + { + // No Override and no Default covers this extension. The part has no + // declared content type in the input; leave the map untouched for it. + continue; + } + + if (!byExtension.TryGetValue(extension, out var parts)) + { + parts = []; + byExtension.Add(extension, parts); + } + + parts.Add(new(partName, contentType)); + } + + var newDefaults = new List(); + var newOverrides = new List(); + foreach (var (extension, parts) in byExtension) + { + var canonicalDefault = ChooseDefault(parts); + newDefaults.Add( + new(defaultName, + new XAttribute("Extension", extension), + new XAttribute("ContentType", canonicalDefault))); + + foreach (var part in parts) + { + if (!string.Equals(part.ContentType, canonicalDefault, StringComparison.Ordinal)) + { + newOverrides.Add( + new(overrideName, + new XAttribute("PartName", part.PartName), + new XAttribute("ContentType", part.ContentType))); + } + } + } + + var ordered = newDefaults + .OrderBy(_ => (string) _.Attribute("Extension")!, StringComparer.Ordinal) + .Concat( + newOverrides + .OrderBy(_ => (string) _.Attribute("PartName")!, StringComparer.Ordinal)) .ToList(); - root.ReplaceAll(elements); + root.ReplaceAll(ordered); + } + + // The content type shared by the most parts wins the extension's Default; + // the lexicographically-smallest content type (Ordinal) breaks ties. Both + // criteria are total and input-order-independent, so the choice is stable + // regardless of how the producer ordered its parts. + static string ChooseDefault(List parts) => + parts + .GroupBy(_ => _.ContentType, StringComparer.Ordinal) + .OrderByDescending(_ => _.Count()) + .ThenBy(_ => _.Key, StringComparer.Ordinal) + .First() + .Key; + + static string? EffectiveContentType( + string partName, + string extension, + Dictionary defaults, + Dictionary overrides) + { + if (overrides.TryGetValue(partName, out var overridden)) + { + return overridden; + } + + if (defaults.TryGetValue(extension, out var byDefault)) + { + return byDefault; + } + + return null; } + + static Dictionary ReadDefaults(XElement root) + { + // Extensions are matched case-insensitively per the OPC spec; normalize to + // lower-case so a part's extension always finds its Default. + var defaults = new Dictionary(StringComparer.Ordinal); + foreach (var element in root.Elements(defaultName)) + { + var extension = (string?) element.Attribute("Extension"); + var contentType = (string?) element.Attribute("ContentType"); + if (extension != null && contentType != null) + { + defaults[extension.ToLowerInvariant()] = contentType; + } + } + + return defaults; + } + + static Dictionary ReadOverrides(XElement root) + { + var overrides = new Dictionary(StringComparer.Ordinal); + foreach (var element in root.Elements(overrideName)) + { + var partName = (string?) element.Attribute("PartName"); + var contentType = (string?) element.Attribute("ContentType"); + if (partName != null && contentType != null) + { + overrides[partName] = contentType; + } + } + + return overrides; + } + + // Part names in the content-type map are absolute ("/xl/workbook.xml"); the + // zip entry names are relative ("xl/workbook.xml"). Normalize entry names to + // the leading-slash form so they line up with the map's PartName values. + internal static string ToPartName(string entryFullName) => + entryFullName.StartsWith('/') ? entryFullName : "/" + entryFullName; + + static string Extension(string partName) + { + var lastDot = partName.LastIndexOf('.'); + if (lastDot < 0 || lastDot == partName.Length - 1) + { + return ""; + } + + // A dot must be in the final segment to be an extension. + var lastSlash = partName.LastIndexOf('/'); + if (lastDot < lastSlash) + { + return ""; + } + + return partName.Substring(lastDot + 1).ToLowerInvariant(); + } + + readonly record struct PartContentType(string PartName, string ContentType); } diff --git a/src/Directory.Build.props b/src/Directory.Build.props index bbb6c2d..154edb7 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -2,7 +2,7 @@ CS1591;CS0649;CA1416;NU1608;NU1109;NU1510 - 0.29.0 + 0.30.0 preview 1.0.0 Modify System.IO.Packaging (https://learn.microsoft.com/en-us/dotnet/api/system.io.packaging) files to ensure they are deterministic. Helpful for testing, build reproducibility, security verification, and ensuring package integrity across different build environments. diff --git a/src/Tests/Patching/ContentTypesPatcherTests.MostCommonBecomesDefault.verified.txt b/src/Tests/Patching/ContentTypesPatcherTests.MostCommonBecomesDefault.verified.txt new file mode 100644 index 0000000..e562852 --- /dev/null +++ b/src/Tests/Patching/ContentTypesPatcherTests.MostCommonBecomesDefault.verified.txt @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/Tests/Patching/ContentTypesPatcherTests.cs b/src/Tests/Patching/ContentTypesPatcherTests.cs new file mode 100644 index 0000000..4a13a16 --- /dev/null +++ b/src/Tests/Patching/ContentTypesPatcherTests.cs @@ -0,0 +1,97 @@ +[TestFixture] +public class ContentTypesPatcherTests +{ + const string variantDefaultIsWorkbook = + """ + + + + + + + + """; + + // Same package, but the producer chose a different content type for the "xml" + // Default and moved the workbook to an Override. Semantically identical. + const string variantDefaultIsWorksheet = + """ + + + + + + + """; + + static string[] partNames = + [ + "/_rels/.rels", + "/xl/workbook.xml", + "/xl/worksheets/sheet1.xml", + "/xl/worksheets/sheet2.xml", + "/xl/styles.xml" + ]; + + static string Patch(string xml) => + PatchHelper + .Patch(new ContentTypesPatcher(partNames), xml, "[Content_Types].xml") + .ToString(); + + // The two equivalent producer splits must canonicalize to identical output. + [Test] + public void SplitChoiceIsCanonicalized() => + Assert.That(Patch(variantDefaultIsWorksheet), Is.EqualTo(Patch(variantDefaultIsWorkbook))); + + // The most-common content type for an extension becomes its Default; here two + // worksheets vs one each of workbook/styles, so worksheet+xml wins the "xml" + // Default and the two worksheet parts carry no Override. + [Test] + public Task MostCommonBecomesDefault() => + Verify(Patch(variantDefaultIsWorkbook)); + + // Every part still resolves to the content type it had in the input: the + // rewrite must be OPC-preserving, only relocating declarations between + // Default and Override. + [Test] + public void ContentTypesArePreserved() + { + var patched = XDocument.Parse(Patch(variantDefaultIsWorkbook)); + + var expected = new Dictionary + { + ["/_rels/.rels"] = "application/vnd.openxmlformats-package.relationships+xml", + ["/xl/workbook.xml"] = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml", + ["/xl/worksheets/sheet1.xml"] = "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml", + ["/xl/worksheets/sheet2.xml"] = "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml", + ["/xl/styles.xml"] = "application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml" + }; + + foreach (var (partName, contentType) in expected) + { + Assert.That(Resolve(patched, partName), Is.EqualTo(contentType), + $"'{partName}' resolved to the wrong content type"); + } + } + + // Resolves a part's content type the way an OPC reader would: an Override for + // that exact part wins, otherwise the Default for its extension. + static string? Resolve(XDocument contentTypes, string partName) + { + XNamespace ns = "http://schemas.openxmlformats.org/package/2006/content-types"; + var root = contentTypes.Root!; + + var over = root.Elements(ns + "Override") + .FirstOrDefault(_ => (string?) _.Attribute("PartName") == partName); + if (over != null) + { + return (string?) over.Attribute("ContentType"); + } + + var extension = partName[(partName.LastIndexOf('.') + 1)..]; + var def = root.Elements(ns + "Default") + .FirstOrDefault(_ => + string.Equals((string?) _.Attribute("Extension"), extension, StringComparison.OrdinalIgnoreCase)); + return (string?) def?.Attribute("ContentType"); + } +} diff --git a/src/Tests/Tests.AbsPath.DotNet.verified.xlsx b/src/Tests/Tests.AbsPath.DotNet.verified.xlsx index e1cfb1f..a6c9a21 100644 Binary files a/src/Tests/Tests.AbsPath.DotNet.verified.xlsx and b/src/Tests/Tests.AbsPath.DotNet.verified.xlsx differ diff --git a/src/Tests/Tests.AbsPath.Net.verified.xlsx b/src/Tests/Tests.AbsPath.Net.verified.xlsx index e1bebff..2853331 100644 Binary files a/src/Tests/Tests.AbsPath.Net.verified.xlsx and b/src/Tests/Tests.AbsPath.Net.verified.xlsx differ diff --git a/src/Tests/Tests.AbsPathZip/[Content_Types].verified.xml b/src/Tests/Tests.AbsPathZip/[Content_Types].verified.xml index 1cb0201..54ab818 100644 --- a/src/Tests/Tests.AbsPathZip/[Content_Types].verified.xml +++ b/src/Tests/Tests.AbsPathZip/[Content_Types].verified.xml @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/Tests/Tests.Numbering.DotNet.verified.docx b/src/Tests/Tests.Numbering.DotNet.verified.docx index 1762fdc..2cbdb23 100644 Binary files a/src/Tests/Tests.Numbering.DotNet.verified.docx and b/src/Tests/Tests.Numbering.DotNet.verified.docx differ diff --git a/src/Tests/Tests.Numbering.Net.verified.docx b/src/Tests/Tests.Numbering.Net.verified.docx index 2ed2e4a..2e4de97 100644 Binary files a/src/Tests/Tests.Numbering.Net.verified.docx and b/src/Tests/Tests.Numbering.Net.verified.docx differ diff --git a/src/Tests/Tests.RunAsync_extension=docx/[Content_Types].verified.xml b/src/Tests/Tests.RunAsync_extension=docx/[Content_Types].verified.xml index 7dad325..2d007b1 100644 --- a/src/Tests/Tests.RunAsync_extension=docx/[Content_Types].verified.xml +++ b/src/Tests/Tests.RunAsync_extension=docx/[Content_Types].verified.xml @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/Tests/Tests.RunAsync_extension=nupkg/[Content_Types].verified.xml b/src/Tests/Tests.RunAsync_extension=nupkg/[Content_Types].verified.xml index 9525a5f..7881007 100644 --- a/src/Tests/Tests.RunAsync_extension=nupkg/[Content_Types].verified.xml +++ b/src/Tests/Tests.RunAsync_extension=nupkg/[Content_Types].verified.xml @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/Tests/Tests.RunAsync_extension=xlsx/[Content_Types].verified.xml b/src/Tests/Tests.RunAsync_extension=xlsx/[Content_Types].verified.xml index ebb83d2..63dc9cd 100644 --- a/src/Tests/Tests.RunAsync_extension=xlsx/[Content_Types].verified.xml +++ b/src/Tests/Tests.RunAsync_extension=xlsx/[Content_Types].verified.xml @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/Tests/Tests.RunBinaryAsync_extension=docx.DotNet.verified.docx b/src/Tests/Tests.RunBinaryAsync_extension=docx.DotNet.verified.docx index f76ce4c..faf4f65 100644 Binary files a/src/Tests/Tests.RunBinaryAsync_extension=docx.DotNet.verified.docx and b/src/Tests/Tests.RunBinaryAsync_extension=docx.DotNet.verified.docx differ diff --git a/src/Tests/Tests.RunBinaryAsync_extension=docx.Net.verified.docx b/src/Tests/Tests.RunBinaryAsync_extension=docx.Net.verified.docx index e7175d0..d76fc61 100644 Binary files a/src/Tests/Tests.RunBinaryAsync_extension=docx.Net.verified.docx and b/src/Tests/Tests.RunBinaryAsync_extension=docx.Net.verified.docx differ diff --git a/src/Tests/Tests.RunBinaryAsync_extension=nupkg.DotNet.verified.nupkg b/src/Tests/Tests.RunBinaryAsync_extension=nupkg.DotNet.verified.nupkg index 4ead26d..e2368a7 100644 Binary files a/src/Tests/Tests.RunBinaryAsync_extension=nupkg.DotNet.verified.nupkg and b/src/Tests/Tests.RunBinaryAsync_extension=nupkg.DotNet.verified.nupkg differ diff --git a/src/Tests/Tests.RunBinaryAsync_extension=nupkg.Net.verified.nupkg b/src/Tests/Tests.RunBinaryAsync_extension=nupkg.Net.verified.nupkg index 2720e7e..97f085e 100644 Binary files a/src/Tests/Tests.RunBinaryAsync_extension=nupkg.Net.verified.nupkg and b/src/Tests/Tests.RunBinaryAsync_extension=nupkg.Net.verified.nupkg differ diff --git a/src/Tests/Tests.RunBinaryAsync_extension=xlsx.DotNet.verified.xlsx b/src/Tests/Tests.RunBinaryAsync_extension=xlsx.DotNet.verified.xlsx index 562da0d..9595ec6 100644 Binary files a/src/Tests/Tests.RunBinaryAsync_extension=xlsx.DotNet.verified.xlsx and b/src/Tests/Tests.RunBinaryAsync_extension=xlsx.DotNet.verified.xlsx differ diff --git a/src/Tests/Tests.RunBinaryAsync_extension=xlsx.Net.verified.xlsx b/src/Tests/Tests.RunBinaryAsync_extension=xlsx.Net.verified.xlsx index bfa7808..6e61764 100644 Binary files a/src/Tests/Tests.RunBinaryAsync_extension=xlsx.Net.verified.xlsx and b/src/Tests/Tests.RunBinaryAsync_extension=xlsx.Net.verified.xlsx differ diff --git a/src/Tests/Tests.RunBinary_extension=docx.DotNet.verified.docx b/src/Tests/Tests.RunBinary_extension=docx.DotNet.verified.docx index 65f7c50..f25e418 100644 Binary files a/src/Tests/Tests.RunBinary_extension=docx.DotNet.verified.docx and b/src/Tests/Tests.RunBinary_extension=docx.DotNet.verified.docx differ diff --git a/src/Tests/Tests.RunBinary_extension=docx.Net.verified.docx b/src/Tests/Tests.RunBinary_extension=docx.Net.verified.docx index e7175d0..d76fc61 100644 Binary files a/src/Tests/Tests.RunBinary_extension=docx.Net.verified.docx and b/src/Tests/Tests.RunBinary_extension=docx.Net.verified.docx differ diff --git a/src/Tests/Tests.RunBinary_extension=nupkg.DotNet.verified.nupkg b/src/Tests/Tests.RunBinary_extension=nupkg.DotNet.verified.nupkg index 4ead26d..e2368a7 100644 Binary files a/src/Tests/Tests.RunBinary_extension=nupkg.DotNet.verified.nupkg and b/src/Tests/Tests.RunBinary_extension=nupkg.DotNet.verified.nupkg differ diff --git a/src/Tests/Tests.RunBinary_extension=nupkg.Net.verified.nupkg b/src/Tests/Tests.RunBinary_extension=nupkg.Net.verified.nupkg index 2720e7e..97f085e 100644 Binary files a/src/Tests/Tests.RunBinary_extension=nupkg.Net.verified.nupkg and b/src/Tests/Tests.RunBinary_extension=nupkg.Net.verified.nupkg differ diff --git a/src/Tests/Tests.RunBinary_extension=xlsx.DotNet.verified.xlsx b/src/Tests/Tests.RunBinary_extension=xlsx.DotNet.verified.xlsx index 8b472e5..a5e23e6 100644 Binary files a/src/Tests/Tests.RunBinary_extension=xlsx.DotNet.verified.xlsx and b/src/Tests/Tests.RunBinary_extension=xlsx.DotNet.verified.xlsx differ diff --git a/src/Tests/Tests.RunBinary_extension=xlsx.Net.verified.xlsx b/src/Tests/Tests.RunBinary_extension=xlsx.Net.verified.xlsx index bfa7808..6e61764 100644 Binary files a/src/Tests/Tests.RunBinary_extension=xlsx.Net.verified.xlsx and b/src/Tests/Tests.RunBinary_extension=xlsx.Net.verified.xlsx differ diff --git a/src/Tests/Tests.Run_extension=docx/[Content_Types].verified.xml b/src/Tests/Tests.Run_extension=docx/[Content_Types].verified.xml index 7dad325..2d007b1 100644 --- a/src/Tests/Tests.Run_extension=docx/[Content_Types].verified.xml +++ b/src/Tests/Tests.Run_extension=docx/[Content_Types].verified.xml @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/Tests/Tests.Run_extension=nupkg/[Content_Types].verified.xml b/src/Tests/Tests.Run_extension=nupkg/[Content_Types].verified.xml index 9525a5f..7881007 100644 --- a/src/Tests/Tests.Run_extension=nupkg/[Content_Types].verified.xml +++ b/src/Tests/Tests.Run_extension=nupkg/[Content_Types].verified.xml @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/Tests/Tests.Run_extension=xlsx/[Content_Types].verified.xml b/src/Tests/Tests.Run_extension=xlsx/[Content_Types].verified.xml index ebb83d2..63dc9cd 100644 --- a/src/Tests/Tests.Run_extension=xlsx/[Content_Types].verified.xml +++ b/src/Tests/Tests.Run_extension=xlsx/[Content_Types].verified.xml @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/Tests/Tests.WithWorkbookRels.DotNet.verified.xlsx b/src/Tests/Tests.WithWorkbookRels.DotNet.verified.xlsx index e1cfb1f..a6c9a21 100644 Binary files a/src/Tests/Tests.WithWorkbookRels.DotNet.verified.xlsx and b/src/Tests/Tests.WithWorkbookRels.DotNet.verified.xlsx differ diff --git a/src/Tests/Tests.WithWorkbookRels.Net.verified.xlsx b/src/Tests/Tests.WithWorkbookRels.Net.verified.xlsx index e1bebff..2853331 100644 Binary files a/src/Tests/Tests.WithWorkbookRels.Net.verified.xlsx and b/src/Tests/Tests.WithWorkbookRels.Net.verified.xlsx differ diff --git a/src/Tests/Tests.WithWorkbookRelsZip/[Content_Types].verified.xml b/src/Tests/Tests.WithWorkbookRelsZip/[Content_Types].verified.xml index 1cb0201..54ab818 100644 --- a/src/Tests/Tests.WithWorkbookRelsZip/[Content_Types].verified.xml +++ b/src/Tests/Tests.WithWorkbookRelsZip/[Content_Types].verified.xml @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file