Skip to content

Conversation

@vaceslav
Copy link
Contributor

Summary

  • Add TextReplacements static class with built-in HtmlEntities preset for common HTML entities
  • Add TextReplacements property to PlaceholderReplacementOptions for opt-in configuration
  • Integrate text replacement into PlaceholderVisitor pipeline (after value conversion, before newline/markdown processing)
  • Support all common HTML line breaks (<br>, <br/>, <br />) and entities (&nbsp;, &lt;, &gt;, &amp;, &quot;, &apos;)
  • Allow custom user-defined replacement mappings

Architecture Decision

Lookup Table vs HTML Parser: Chose lookup table approach for:

  • Simplicity: No complex HTML parsing, just string.Replace() operations
  • Flexibility: Users can define custom mappings beyond HTML
  • Predictable: No edge cases from malformed HTML
  • Extensible: Easy to add new replacements without code changes

Trade-off: Paired tags like <b>text</b> are not supported - users should use existing markdown support instead.

Usage Examples

// Use built-in HTML entities preset
var options = new PlaceholderReplacementOptions
{
    TextReplacements = TextReplacements.HtmlEntities
};

// Custom replacements
var options = new PlaceholderReplacementOptions
{
    TextReplacements = new Dictionary<string, string>
    {
        ["<br>"] = "\n",
        ["COMPANY_NAME"] = "Acme Corp"
    }
};

// Combine preset + custom
var replacements = new Dictionary<string, string>(TextReplacements.HtmlEntities)
{
    ["CUSTOM"] = "value"
};

Test plan

  • Unit tests for TextReplacements.Apply() method (28 tests)
  • Unit tests for HtmlEntities preset coverage
  • Integration tests for HTML → Word line breaks
  • Integration tests with markdown formatting
  • Integration tests in loops
  • Integration tests preserving document formatting
  • All existing tests pass (926 tests)

Closes #64

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 />, &nbsp;, &lt;, &gt;, &amp;, &quot;, &apos;
- 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>, &nbsp;, 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 &mdash; (—) and &ndash; (–) to the HtmlEntities preset to support
common typographic dash entities used in web content.
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 introduces HTML entity replacement functionality to Templify, enabling automatic conversion of HTML tags and entities (like <br>, &nbsp;, &lt;, etc.) to their Word-compatible equivalents. The implementation uses a simple lookup table approach for flexibility and predictability.

Key Changes:

  • Added TextReplacements static class with built-in HtmlEntities preset 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.

Comment on lines 68 to 75
["&nbsp;"] = "\u00A0", // Non-breaking space
["&lt;"] = "<",
["&gt;"] = ">",
["&amp;"] = "&",
["&quot;"] = "\"",
["&apos;"] = "'",
["&mdash;"] = "\u2014", // Em dash (—)
["&ndash;"] = "\u2013", // En dash (–)
Copy link

Copilot AI Dec 23, 2025

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., &nbsp; 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.

Suggested change
["&nbsp;"] = "\u00A0", // Non-breaking space
["&lt;"] = "<",
["&gt;"] = ">",
["&amp;"] = "&",
["&quot;"] = "\"",
["&apos;"] = "'",
["&mdash;"] = "\u2014", // Em dash (—)
["&ndash;"] = "\u2013", // En dash (–)
["&nbsp;"] = "\u00A0", // Non-breaking space
["&NBSP;"] = "\u00A0",
["&lt;"] = "<",
["&LT;"] = "<",
["&gt;"] = ">",
["&GT;"] = ">",
["&amp;"] = "&",
["&AMP;"] = "&",
["&quot;"] = "\"",
["&QUOT;"] = "\"",
["&apos;"] = "'",
["&APOS;"] = "'",
["&mdash;"] = "\u2014", // Em dash (—)
["&MDASH;"] = "\u2014",
["&ndash;"] = "\u2013", // En dash (–)
["&NDASH;"] = "\u2013",

Copilot uses AI. Check for mistakes.
Comment on lines 57 to 76
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
["&nbsp;"] = "\u00A0", // Non-breaking space
["&lt;"] = "<",
["&gt;"] = ">",
["&amp;"] = "&",
["&quot;"] = "\"",
["&apos;"] = "'",
["&mdash;"] = "\u2014", // Em dash (—)
["&ndash;"] = "\u2013", // En dash (–)
};
Copy link

Copilot AI Dec 23, 2025

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.

Copilot uses AI. Check for mistakes.
| `&apos;` | `'` | Single quote |
| `&mdash;` | `` | Em dash |
| `&ndash;` | `` | En dash |

Copy link

Copilot AI Dec 23, 2025

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.

Suggested change
> Note: HTML tags are matched case-insensitively. Uppercase variants like `<BR>`, `<BR/>`, and `<BR />` are also supported.

Copilot uses AI. Check for mistakes.
/// or applying replacements in a specific sequence.
/// </remarks>
public static string Apply(string input, Dictionary<string, string>? replacements)
{
Copy link

Copilot AI Dec 23, 2025

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.

Suggested change
{
{
if (input is null)
{
return string.Empty;
}

Copilot uses AI. Check for mistakes.
Comment on lines +96 to +100
string result = input;
foreach (KeyValuePair<string, string> replacement in replacements)
{
result = result.Replace(replacement.Key, replacement.Value);
}
Copy link

Copilot AI Dec 23, 2025

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.

Copilot uses AI. Check for mistakes.
Comment on lines +96 to +100
string result = input;
foreach (KeyValuePair<string, string> replacement in replacements)
{
result = result.Replace(replacement.Key, replacement.Value);
}
Copy link

Copilot AI Dec 23, 2025

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.

Copilot uses AI. Check for mistakes.
- 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
@vaceslav vaceslav merged commit 1378e5c into main Dec 23, 2025
14 checks passed
@vaceslav vaceslav deleted the feat/text-replacements branch December 23, 2025 18:42
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.

[Feature]: Translate typical HTML and special characters into formats that can be used in MS Word

2 participants