Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ All notable changes to this project will be documented in this file.
- CSS-based label filtering enables responsive toggle without any re-rendering

### Fixed
- **NullReferenceException in FindIndexOfNextParaMark when comparing documents with body-level bookmarks** - `FindIndexOfNextParaMark` assumed all elements in the comparison unit array were `ComparisonUnitWord`, but documents with `bookmarkStart`/`bookmarkEnd` as direct children of `w:body` produce other `ComparisonUnit` types. Now handles any `ComparisonUnit` with `Contents` (including `ComparisonUnitGroup`) and adds a null guard for the `LastOrDefault()` call.
- **Paginated rendering: text clipped at page bottom + inconsistent paragraph spacing (Issue #114)**
- Fixed `lineRule` default handling: when `w:lineRule` is absent but `w:line` is present, treat as "auto" per OOXML spec (ISO/IEC 29500). Previously the line value was ignored, causing accumulated line-height mismatches that clipped the last line on pages.
- Fixed `contextualSpacing` handling: now suppresses both `spacingAfter` (margin-bottom) AND `spacingBefore` (margin-top) for consecutive same-style paragraphs. Previously only `spacingAfter` was suppressed, leaving inconsistent inter-paragraph gaps.
Expand Down
40 changes: 40 additions & 0 deletions Docxodus.Tests/WmlComparerBodyLevelBookmarkTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

#nullable enable

using System;
using System.IO;
using Docxodus;
using Xunit;

namespace OxPt
{
public class WmlComparerBodyLevelBookmarkTests
{
[Fact]
public void Compare_WithBodyLevelBookmarks_DoesNotThrowNullReference()
{
DirectoryInfo sourceDir = new DirectoryInfo("../../../../TestFiles/");
var original = new WmlDocument(Path.Combine(sourceDir.FullName, "WC/WC-BodyBookmarks-Before.docx"));
var modified = new WmlDocument(Path.Combine(sourceDir.FullName, "WC/WC-BodyBookmarks-After.docx"));

var settings = new WmlComparerSettings();

// The modified document has bookmarkStart/bookmarkEnd as direct children
// of w:body (siblings of w:p). This caused a NullReferenceException in
// FindIndexOfNextParaMark because it assumed all ComparisonUnits were
// ComparisonUnitWord, but body-level bookmarks produce other types.
//
// This document pair also triggers a separate "Internal error" in
// ProcessFootnoteEndnote (the modified document converted endnotes to
// footnotes). That is a different bug — this test verifies only that the
// NullReferenceException in FindIndexOfNextParaMark is fixed.
var ex = Record.Exception(() => WmlComparer.Compare(original, modified, settings));

Assert.True(
ex is not NullReferenceException,
$"Expected no NullReferenceException but got: {ex}");
}
}
}
9 changes: 6 additions & 3 deletions Docxodus/WmlComparer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7676,9 +7676,12 @@ private static int FindIndexOfNextParaMark(ComparisonUnit[] cul)
{
for (int i = 0; i < cul.Length; i++)
{
var cuw = cul[i] as ComparisonUnitWord;
var lastAtom = cuw.DescendantContentAtoms().LastOrDefault();
if (lastAtom.ContentElement.Name == W.pPr)
var comparisonUnit = cul[i];
if (comparisonUnit == null || comparisonUnit.Contents == null)
continue;

var lastAtom = comparisonUnit.DescendantContentAtoms().LastOrDefault();
if (lastAtom != null && lastAtom.ContentElement.Name == W.pPr)
return i;
}
return cul.Length;
Expand Down
Binary file added TestFiles/WC/WC-BodyBookmarks-After.docx
Binary file not shown.
Binary file added TestFiles/WC/WC-BodyBookmarks-Before.docx
Binary file not shown.