-
Notifications
You must be signed in to change notification settings - Fork 0
feat: add UpdateFieldsOnOpen option for TOC refresh #68
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
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
There was a problem hiding this 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
UpdateFieldsOnOpenboolean property toPlaceholderReplacementOptions(defaults tofalsefor 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(); | ||
| } | ||
|
|
Copilot
AI
Jan 7, 2026
There was a problem hiding this comment.
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.
| if (settingsPart.Settings == null) | |
| { | |
| settingsPart.Settings = new Settings(); | |
| } |
| 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; | ||
| } | ||
| } | ||
| } |
Copilot
AI
Jan 7, 2026
There was a problem hiding this comment.
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(...)'.
| 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); | ||
| } | ||
| } |
Copilot
AI
Jan 7, 2026
There was a problem hiding this comment.
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(...)'.
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
0f9865d to
1b808a9
Compare
There was a problem hiding this 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.
| .Any(fc => fc.Text != null && | ||
| _dynamicFieldTypes.Any(field => | ||
| fc.Text.Contains(field, StringComparison.OrdinalIgnoreCase))); |
Copilot
AI
Jan 7, 2026
There was a problem hiding this comment.
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).
| .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; | |
| }); |
docs/for-developers/quick-start.md
Outdated
|
|
||
| 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. |
Copilot
AI
Jan 7, 2026
There was a problem hiding this comment.
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.
| **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. |
TriasDev.Templify/README.md
Outdated
|
|
||
| **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. |
Copilot
AI
Jan 7, 2026
There was a problem hiding this comment.
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.
| 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. |
- 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
There was a problem hiding this 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.
|
|
||
| - `TOC` - Table of Contents | ||
| - `PAGE` - Current page number | ||
| - `NUMPAGES` - Total page count | ||
| - `PAGEREF` - Page references |
Copilot
AI
Jan 7, 2026
There was a problem hiding this comment.
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.
| - `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 |
| { | ||
| UpdateFieldsOnOpen = UpdateFieldsOnOpenMode.Auto | ||
| }; | ||
| // Only prompts if document contains TOC, PAGE, NUMPAGES, PAGEREF, DATE, TIME, or FILENAME fields |
Copilot
AI
Jan 7, 2026
There was a problem hiding this comment.
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.
| // 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 |
| foreach (string field in _dynamicFieldTypes) | ||
| { | ||
| if (fieldTypeInDoc.Equals(field, StringComparison.OrdinalIgnoreCase)) | ||
| { | ||
| return true; | ||
| } | ||
| } |
Copilot
AI
Jan 7, 2026
There was a problem hiding this comment.
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(...)'.
- Add missing field types (REF, NOTEREF, SECTIONPAGES) to documentation - Refactor GetTableOfContentsPageNumbers to use LINQ
Summary
UpdateFieldsOnOpenoption toPlaceholderReplacementOptionsfor refreshing Table of Contents when documents are opened in Word<w:updateFields>document setting so Word prompts users to update fields on openUsage
Test plan
UpdateFieldsOnOpen = truesets document setting correctlyUpdateFieldsOnOpen = false(default) does not set document settingCloses #67