Skip to content

Conversation

@vaceslav
Copy link
Contributor

@vaceslav vaceslav commented Jan 6, 2026

Summary

  • Add UpdateFieldsOnOpen option to PlaceholderReplacementOptions for refreshing Table of Contents when documents are opened in Word
  • When templates with TOC are processed and content is removed via conditionals/loops, TOC page numbers become stale
  • This option sets Word's <w:updateFields> document setting so Word prompts users to update fields on open

Usage

var options = new PlaceholderReplacementOptions
{
    UpdateFieldsOnOpen = true
};

var processor = new DocumentTemplateProcessor(options);
processor.ProcessTemplate(templateStream, outputStream, data);

Test plan

  • Test that UpdateFieldsOnOpen = true sets document setting correctly
  • Test that UpdateFieldsOnOpen = false (default) does not set document setting
  • Test TOC structure preserved after processing
  • Test placeholders after TOC work correctly
  • All 936 tests pass

Closes #67

When templates with Table of Contents are processed and content is
removed via conditionals or loops, TOC page numbers become stale.

Add UpdateFieldsOnOpen option that sets Word's <w:updateFields> document
setting. When enabled, Word prompts users to update fields (including
TOC) when opening the document.

Closes #67
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a new UpdateFieldsOnOpen option to address the issue of stale Table of Contents (TOC) page numbers after template processing. When templates contain TOC fields and content is removed via conditionals or loops, the cached page numbers become outdated. The new option sets Word's document setting to prompt users to update fields when opening the document.

Key Changes

  • Added UpdateFieldsOnOpen boolean property to PlaceholderReplacementOptions (defaults to false for backward compatibility)
  • Implemented ApplyUpdateFieldsOnOpen() method to set the Word document setting
  • Added comprehensive test coverage for TOC behavior and the new option

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
TriasDev.Templify/Core/PlaceholderReplacementOptions.cs Adds new UpdateFieldsOnOpen property with comprehensive documentation explaining the TOC refresh behavior
TriasDev.Templify/Core/DocumentTemplateProcessor.cs Implements ApplyUpdateFieldsOnOpen() method to configure Word document settings and integrates it into the processing pipeline
TriasDev.Templify.Tests/Integration/TableOfContentsTests.cs Adds comprehensive test suite documenting TOC behavior, including tests for stale page numbers, structure preservation, and the new option
TriasDev.Templify.Tests/Helpers/DocumentVerifier.cs Adds helper methods to verify TOC presence, extract entries, and check UpdateFieldsOnOpen setting
TriasDev.Templify.Tests/Helpers/DocumentBuilder.cs Adds methods to build test documents with TOC fields, headings, and page breaks

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

settingsPart = document.MainDocumentPart.AddNewPart<DocumentSettingsPart>();
settingsPart.Settings = new Settings();
}

Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential null reference exception: settingsPart.Settings could be null if the DocumentSettingsPart already exists but has no Settings object initialized. Add a null check after line 267 to handle this case, or initialize Settings if it's null.

Suggested change
if (settingsPart.Settings == null)
{
settingsPart.Settings = new Settings();
}

Copilot uses AI. Check for mistakes.
Comment on lines +472 to +492
foreach (Run run in para.Descendants<Run>())
{
FieldChar? fieldChar = run.GetFirstChild<FieldChar>();
if (fieldChar != null)
{
if (fieldChar.FieldCharType?.Value == FieldCharValues.Begin)
{
inTocField = true;
afterSeparator = false;
}
else if (fieldChar.FieldCharType?.Value == FieldCharValues.Separate)
{
afterSeparator = true;
}
else if (fieldChar.FieldCharType?.Value == FieldCharValues.End)
{
inTocField = false;
afterSeparator = false;
}
}
}
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This foreach loop immediately maps its iteration variable to another variable - consider mapping the sequence explicitly using '.Select(...)'.

Copilot uses AI. Check for mistakes.
Comment on lines 516 to 524
foreach (string entry in entries)
{
// TOC entries typically end with a page number
string[] parts = entry.Split('\t', ' ');
if (parts.Length > 0 && int.TryParse(parts[^1], out int pageNum))
{
pageNumbers.Add(pageNum);
}
}
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This foreach loop immediately maps its iteration variable to another variable - consider mapping the sequence explicitly using '.Select(...)'.

Copilot uses AI. Check for mistakes.
BREAKING CHANGE: UpdateFieldsOnOpen changed from bool to enum.

Add UpdateFieldsOnOpenMode enum with three options:
- Never: default, never set the flag (backward compatible behavior)
- Always: always set the flag (replaces true)
- Auto: only set flag if document contains fields (TOC, PAGE, etc.)

Auto mode is recommended for applications that process various templates,
as it only prompts users to update fields when necessary.

Detected fields: TOC, PAGE, NUMPAGES, PAGEREF, DATE, TIME, FILENAME
Document the new UpdateFieldsOnOpenMode option in README:
- Explain the three modes (Never, Always, Auto)
- Provide usage examples
- Explain why Templify can't update TOC directly
- Add to API Reference section
Add configuration options section and TOC support documentation
to docs/for-developers/quick-start.md including all available
PlaceholderReplacementOptions and detailed UpdateFieldsOnOpenMode
usage examples.
Collection expressions are not available in C# 10.0 (net6.0).
Changed from collection expression to traditional array initializer.
- Move field type array to class-level static readonly field for performance
- Add REF, NOTEREF, SECTIONPAGES to Auto mode field detection
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 289 to 291
.Any(fc => fc.Text != null &&
_dynamicFieldTypes.Any(field =>
fc.Text.Contains(field, StringComparison.OrdinalIgnoreCase)));
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The field detection logic has a potential false positive issue. It checks if the FieldCode text contains any of the dynamic field types, but this could match partial words. For example, a field code containing "TOCENTRY" or "PAGETOTAL" would match "TOC" and "PAGE" respectively. Consider using word boundary matching or checking for the field type at the start of the field code text (after trimming whitespace).

Suggested change
.Any(fc => fc.Text != null &&
_dynamicFieldTypes.Any(field =>
fc.Text.Contains(field, StringComparison.OrdinalIgnoreCase)));
.Any(fc =>
{
if (string.IsNullOrWhiteSpace(fc.Text))
{
return false;
}
// Field codes typically start with the field type, followed by
// spaces, switches (starting with '\'), and parameters.
// Example: "PAGE \\* MERGEFORMAT"
string text = fc.Text.TrimStart();
// Get the first token (up to whitespace or a backslash for switches)
int endIndex = text.IndexOfAny(new[] { ' ', '\t', '\r', '\n', '\\' });
string fieldTypeInDoc = endIndex >= 0 ? text[..endIndex] : text;
foreach (string field in _dynamicFieldTypes)
{
if (fieldTypeInDoc.Equals(field, StringComparison.OrdinalIgnoreCase))
{
return true;
}
}
return false;
});

Copilot uses AI. Check for mistakes.

When templates contain Table of Contents (TOC) or other dynamic fields, and content changes during processing (via conditionals or loops), page numbers become stale.

**Why this happens:** OpenXML SDK cannot calculate page numbers—only Word's layout engine can determine actual pagination.
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment states "OpenXML SDK cannot calculate page numbers" but should be "The OpenXML SDK" for grammatical correctness.

Suggested change
**Why this happens:** OpenXML SDK cannot calculate page numbers—only Word's layout engine can determine actual pagination.
**Why this happens:** The OpenXML SDK cannot calculate page numbers—only Word's layout engine can determine actual pagination.

Copilot uses AI. Check for mistakes.

**Why can't Templify update TOC page numbers directly?**

Page numbers are calculated by Word's layout engine at render time. OpenXML (and Templify) can only store the document structure, not the rendered output. Only Word can calculate actual page numbers based on fonts, margins, page breaks, etc.
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documentation mentions "OpenXML (and Templify)" but should be "The OpenXML SDK (and Templify)" for consistency and grammatical correctness.

Suggested change
Page numbers are calculated by Word's layout engine at render time. OpenXML (and Templify) can only store the document structure, not the rendered output. Only Word can calculate actual page numbers based on fonts, margins, page breaks, etc.
Page numbers are calculated by Word's layout engine at render time. The OpenXML SDK (and Templify) can only store the document structure, not the rendered output. Only Word can calculate actual page numbers based on fonts, margins, page breaks, etc.

Copilot uses AI. Check for mistakes.
- Add null check for settingsPart.Settings in ApplyUpdateFieldsOnOpen
- Fix field detection false positives by parsing field type token
- Fix grammar: "OpenXML SDK" → "The OpenXML SDK" in docs
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +96 to +100

- `TOC` - Table of Contents
- `PAGE` - Current page number
- `NUMPAGES` - Total page count
- `PAGEREF` - Page references
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documentation lists only TOC, PAGE, NUMPAGES, PAGEREF, DATE, TIME, and FILENAME fields as detected in Auto mode, but the implementation also includes REF, NOTEREF, and SECTIONPAGES. The documentation should be updated to include all field types that are detected, or clarify that these are examples of commonly used fields.

Suggested change
- `TOC` - Table of Contents
- `PAGE` - Current page number
- `NUMPAGES` - Total page count
- `PAGEREF` - Page references
The following fields are currently detected when `UpdateFieldsOnOpen` is set to `Auto`:
- `TOC` - Table of Contents
- `PAGE` - Current page number
- `NUMPAGES` - Total page count
- `PAGEREF` - Page references
- `REF` - Cross-references
- `NOTEREF` - Footnote or endnote references
- `SECTIONPAGES` - Page count in the current section

Copilot uses AI. Check for mistakes.
{
UpdateFieldsOnOpen = UpdateFieldsOnOpenMode.Auto
};
// Only prompts if document contains TOC, PAGE, NUMPAGES, PAGEREF, DATE, TIME, or FILENAME fields
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment on line 768 lists TOC, PAGE, NUMPAGES, PAGEREF, DATE, TIME, or FILENAME fields, but the implementation (_dynamicFieldTypes array in DocumentTemplateProcessor.cs) also includes REF, NOTEREF, and SECTIONPAGES. The documentation should be updated to include all detected field types or clarify that these are examples.

Suggested change
// Only prompts if document contains TOC, PAGE, NUMPAGES, PAGEREF, DATE, TIME, or FILENAME fields
// Only prompts if document contains TOC, PAGE, NUMPAGES, PAGEREF, REF, NOTEREF, SECTIONPAGES, DATE, TIME, or FILENAME fields

Copilot uses AI. Check for mistakes.
Comment on lines +305 to +311
foreach (string field in _dynamicFieldTypes)
{
if (fieldTypeInDoc.Equals(field, StringComparison.OrdinalIgnoreCase))
{
return true;
}
}
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This foreach loop implicitly filters its target sequence - consider filtering the sequence explicitly using '.Where(...)'.

Copilot uses AI. Check for mistakes.
- Add missing field types (REF, NOTEREF, SECTIONPAGES) to documentation
- Refactor GetTableOfContentsPageNumbers to use LINQ
@vaceslav vaceslav merged commit 8bd178e into main Jan 7, 2026
14 checks passed
@vaceslav vaceslav deleted the feat/update-fields-on-open branch January 7, 2026 14:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

TOC shows wrong page numbers after template rendering

2 participants