Skip to content

Preserve StringValue.Encode across string filter transformations - #925

Merged
sebastienros merged 2 commits into
mainfrom
copilot/fix-string-filters-encode-state
Feb 16, 2026
Merged

Preserve StringValue.Encode across string filter transformations#925
sebastienros merged 2 commits into
mainfrom
copilot/fix-string-filters-encode-state

Conversation

Copilot AI commented Feb 16, 2026

Copy link
Copy Markdown
Contributor

String filters were dropping StringValue.Encode when producing transformed strings, causing values marked encode: false to become encoded again (notably in Fluid.ViewEngine with HtmlEncoder). This PR keeps the encode flag when a string filter output is derived from a StringValue input.

  • Root behavior corrected

    • String-transforming filters now preserve the input StringValue.Encode state instead of always returning a default-encoded StringValue.
    • This aligns filter chaining behavior with raw/unencoded content expectations.
  • Implementation

    • Added a small helper in StringFilters to create outputs while carrying forward Encode when input is StringValue.
    • Updated string-transformation return paths in StringFilters to use that helper (e.g., replace, replace_first, replace_last, append, prepend, casing/trim/strip/newline/truncate variants).
  • Coverage

    • Added a focused template-level regression test for replace with HtmlEncoder to verify encode: false is retained through transformation.
var context = new TemplateContext();
context.SetValue("obj", new { html = new StringValue("<div>test</div>", false) });

// before: "&lt;div&gt;content&lt;/div&gt;"
// after:  "<div>content</div>"
{{ obj.html | replace: 'test', 'content' }}
Original prompt

This section details on the original issue you should resolve

<issue_title>String filters doesn't preserve encode state of StringValue</issue_title>
<issue_description>When applying the replace filter to a StringValue with encode: false, it produces a StringValue with the default encode state (true).

For consistency, when a StringValue with encode: false is processed through the replace filter, the resulting StringValue should maintain the same encode: false state. This ensures that the content remains unencoded throughout the template processing pipeline.
I believe the same applies to all string filters. The main place where the problem is seen is Fluid.ViewEngine where the default encoder is HtmlEncoder.

Test case:

using System.Text.Encodings.Web;
using Fluid;
using Fluid.Values;

var source =
    """
    {{ obj.html | replace: 'test', 'content' }}
    """;

if (new FluidParser().TryParse(source, out var template, out var error))
{
    var context = new TemplateContext();
    context.SetValue("obj", new MyClass
    {
        html = new StringValue("<div>test</div>", false)
    });

    Console.WriteLine(template.Render(context, HtmlEncoder.Default));
}
else
{
    Console.WriteLine($"Error: {error}");
}

class MyClass
{
    public StringValue html;
}

Expected output:

<div>content</div>

Actual output:

&lt;div&gt;content&lt;/div&gt;

Explanation:

Versions affected: 2.31.0 and 3.0.0-beta3</issue_description>

<agent_instructions>Take into account that If the result of a filter is a StringValue and a transformation of an input which is also StringValue then it should respect the input "encode" option.</agent_instructions>

Comments on the Issue (you are @copilot in this section)

@sebastienros I am trying to think about the implications...

Right now a mitigation would be to use the raw filter to force the non-encoding whatever the source is like so:

{{ obj.html | replace: 'test', 'content' | raw }}

Assuming this would be an intended behavior, if we were to change it would it mean we also need a tag for force encoding, like | cooked (cute).

Now thinking of how we can get these already encoded strings, there is capture and macros. When we capture some snippet and want to alter it (replace filter) then I believe your suggestion makes sense.</comment_new>
<comment_new>@sebastienros
However let's take a filter like base64_encode, should it really inherit the encoding?</comment_new>
<comment_new>@sebastienros
escape and escape_once are currently pre-encoding values, maybe that's the cooked one I mentioned. Though the currently implementation if forcing html encoding and not the current encoding used by the template, maybe something to fix too.

Maybe not, raw is not decoding, just setting the flag.


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Co-authored-by: sebastienros <1165805+sebastienros@users.noreply.github.com>
@sebastienros
sebastienros marked this pull request as ready for review February 16, 2026 18:16
Copilot AI changed the title [WIP] Fix string filters to preserve encode state of StringValue Preserve StringValue.Encode across string filter transformations Feb 16, 2026
Copilot AI requested a review from sebastienros February 16, 2026 18:16
@sebastienros
sebastienros merged commit c33b938 into main Feb 16, 2026
3 checks passed
@sebastienros
sebastienros deleted the copilot/fix-string-filters-encode-state branch February 16, 2026 18:17
sebastienros added a commit that referenced this pull request Aug 20, 2026
)

* Initial plan

* Preserve StringValue encode flag in string filters

Co-authored-by: sebastienros <1165805+sebastienros@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: sebastienros <1165805+sebastienros@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ee9c8775-0354-41dd-8226-89a69c60241c
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.

String filters doesn't preserve encode state of StringValue

2 participants