-
Notifications
You must be signed in to change notification settings - Fork 0
feat: add text replacement lookup table for HTML entities #65
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
Add configurable text replacement feature to transform HTML entities and custom patterns in template values before Word processing. - Add TextReplacements static class with HtmlEntities preset - Add TextReplacements property to PlaceholderReplacementOptions - Integrate text replacement into PlaceholderVisitor pipeline - Support <br>, <br/>, <br />, , <, >, &, ", ' - Allow custom user-defined replacement mappings ADR: Chose lookup table approach over HTML parser for simplicity, flexibility (custom patterns), predictability, and extensibility. Paired tags like <b>text</b> can use existing markdown support. Closes #64
Add checkbox in GUI to enable HTML entity replacement, allowing users to convert HTML tags (<br>, , etc.) to Word equivalents. - Add EnableHtmlEntityReplacement property to MainWindowViewModel - Add checkbox to MainWindow.axaml with tooltip - Update ITemplifyService interface with enableHtmlEntityReplacement param - Update TemplifyService to use TextReplacements.HtmlEntities when enabled - Add user documentation to placeholders.md
Add — (—) and – (–) to the HtmlEntities preset to support common typographic dash entities used in web content.
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 introduces HTML entity replacement functionality to Templify, enabling automatic conversion of HTML tags and entities (like <br>, , <, etc.) to their Word-compatible equivalents. The implementation uses a simple lookup table approach for flexibility and predictability.
Key Changes:
- Added
TextReplacementsstatic class with built-inHtmlEntitiespreset containing common HTML entities and line break variations - Integrated text replacement into the placeholder processing pipeline (applied after value conversion, before newline/markdown processing)
- Added GUI checkbox to enable HTML entity replacement feature
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
TriasDev.Templify/Replacements/TextReplacements.cs |
Core implementation with Apply() method and HtmlEntities preset dictionary |
TriasDev.Templify/Core/PlaceholderReplacementOptions.cs |
Added TextReplacements property for opt-in configuration |
TriasDev.Templify/Visitors/PlaceholderVisitor.cs |
Integrated text replacement into processing pipeline |
TriasDev.Templify.Tests/Replacements/TextReplacementsTests.cs |
Unit tests for replacement logic (28 tests) |
TriasDev.Templify.Tests/Integration/TextReplacementsIntegrationTests.cs |
Integration tests verifying HTML entity replacement in Word documents |
TriasDev.Templify.Gui/Views/MainWindow.axaml |
Added checkbox UI control for enabling HTML entity replacement |
TriasDev.Templify.Gui/ViewModels/MainWindowViewModel.cs |
Added EnableHtmlEntityReplacement property |
TriasDev.Templify.Gui/Services/TemplifyService.cs |
Updated to pass HTML entity replacement option to processor |
TriasDev.Templify.Gui/Services/ITemplifyService.cs |
Updated interface signatures with new parameter |
docs/for-template-authors/placeholders.md |
Comprehensive documentation on HTML entity replacement feature |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| [" "] = "\u00A0", // Non-breaking space | ||
| ["<"] = "<", | ||
| [">"] = ">", | ||
| ["&"] = "&", | ||
| ["""] = "\"", | ||
| ["'"] = "'", | ||
| ["—"] = "\u2014", // Em dash (—) | ||
| ["–"] = "\u2013", // En dash (–) |
Copilot
AI
Dec 23, 2025
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.
HTML entities are case-sensitive in the HTML specification (e.g., is valid but &NBSP; is not). However, the implementation only supports lowercase entities. Consider documenting this limitation or adding support for common case variations if they appear in real-world data sources.
| [" "] = "\u00A0", // Non-breaking space | |
| ["<"] = "<", | |
| [">"] = ">", | |
| ["&"] = "&", | |
| ["""] = "\"", | |
| ["'"] = "'", | |
| ["—"] = "\u2014", // Em dash (—) | |
| ["–"] = "\u2013", // En dash (–) | |
| [" "] = "\u00A0", // Non-breaking space | |
| ["&NBSP;"] = "\u00A0", | |
| ["<"] = "<", | |
| ["<"] = "<", | |
| [">"] = ">", | |
| [">"] = ">", | |
| ["&"] = "&", | |
| ["&"] = "&", | |
| ["""] = "\"", | |
| ["""] = "\"", | |
| ["'"] = "'", | |
| ["&APOS;"] = "'", | |
| ["—"] = "\u2014", // Em dash (—) | |
| ["&MDASH;"] = "\u2014", | |
| ["–"] = "\u2013", // En dash (–) | |
| ["&NDASH;"] = "\u2013", |
| public static Dictionary<string, string> HtmlEntities { get; } = new Dictionary<string, string> | ||
| { | ||
| // Line break variations | ||
| ["<br>"] = "\n", | ||
| ["<br/>"] = "\n", | ||
| ["<br />"] = "\n", | ||
| ["<BR>"] = "\n", | ||
| ["<BR/>"] = "\n", | ||
| ["<BR />"] = "\n", | ||
|
|
||
| // Common HTML entities | ||
| [" "] = "\u00A0", // Non-breaking space | ||
| ["<"] = "<", | ||
| [">"] = ">", | ||
| ["&"] = "&", | ||
| ["""] = "\"", | ||
| ["'"] = "'", | ||
| ["—"] = "\u2014", // Em dash (—) | ||
| ["–"] = "\u2013", // En dash (–) | ||
| }; |
Copilot
AI
Dec 23, 2025
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 HtmlEntities dictionary is exposed with a getter that returns a mutable Dictionary<string, string>. This allows external code to modify the shared preset, potentially affecting all users of the preset. Consider using IReadOnlyDictionary<string, string> or ReadOnlyDictionary<string, string> as the return type to prevent unintended modifications to the shared preset.
| | `'` | `'` | Single quote | | ||
| | `—` | `—` | Em dash | | ||
| | `–` | `–` | En dash | | ||
|
|
Copilot
AI
Dec 23, 2025
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 table lists the supported HTML entities but does not include the uppercase variants (<BR>, <BR/>, <BR />) that are actually supported in the code. Consider adding these to the documentation table or adding a note that case variations are supported to ensure users are aware of this functionality.
| > Note: HTML tags are matched case-insensitively. Uppercase variants like `<BR>`, `<BR/>`, and `<BR />` are also supported. |
| /// or applying replacements in a specific sequence. | ||
| /// </remarks> | ||
| public static string Apply(string input, Dictionary<string, string>? replacements) | ||
| { |
Copilot
AI
Dec 23, 2025
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 method returns input directly when it's null or empty, but the return type is string (non-nullable). When input is null, this will return null, which contradicts the non-nullable return type. Consider changing the return type to string? or updating the implementation to return an empty string for null input to match the method signature.
| { | |
| { | |
| if (input is null) | |
| { | |
| return string.Empty; | |
| } |
| string result = input; | ||
| foreach (KeyValuePair<string, string> replacement in replacements) | ||
| { | ||
| result = result.Replace(replacement.Key, replacement.Value); | ||
| } |
Copilot
AI
Dec 23, 2025
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 replacement order in a standard Dictionary is not guaranteed to be consistent across different .NET versions. When replacements have overlapping patterns (e.g., "&" could create " " if processed before " "), the order matters. Consider documenting this potential issue more prominently or using an ordered collection like OrderedDictionary or implementing a specific replacement sequence for the HtmlEntities preset to ensure consistent behavior.
| string result = input; | ||
| foreach (KeyValuePair<string, string> replacement in replacements) | ||
| { | ||
| result = result.Replace(replacement.Key, replacement.Value); | ||
| } |
Copilot
AI
Dec 23, 2025
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 implementation performs multiple sequential string.Replace() calls, creating a new string for each replacement. For large strings with many replacements, this could be inefficient. Consider using a StringBuilder or a single-pass approach for better performance, especially when processing large documents with many HTML entities.
- Make HtmlEntities return IReadOnlyDictionary to prevent external modification - Fix nullable return type in Apply() method (string? instead of string) - Update PlaceholderReplacementOptions.TextReplacements to IReadOnlyDictionary - Add documentation note about uppercase <BR> support and case-sensitive entities - Update tests to handle nullable return type
Summary
TextReplacementsstatic class with built-inHtmlEntitiespreset for common HTML entitiesTextReplacementsproperty toPlaceholderReplacementOptionsfor opt-in configurationPlaceholderVisitorpipeline (after value conversion, before newline/markdown processing)<br>,<br/>,<br />) and entities ( ,<,>,&,",')Architecture Decision
Lookup Table vs HTML Parser: Chose lookup table approach for:
string.Replace()operationsTrade-off: Paired tags like
<b>text</b>are not supported - users should use existing markdown support instead.Usage Examples
Test plan
TextReplacements.Apply()method (28 tests)HtmlEntitiespreset coverageCloses #64