sebastienros/fluid

Using a dot ('.') in a custom IdentifierTag parameter breaks rendering silently

Closed this issue · 10 comments

Hello,

It seems the parser tries to interpret any '.' character as if we were trying to access a field of an object, even where it doesn't make any sense to do so (just an assumption, since I couldn't find any actual errors; my program finishes correctly even though the rendering silently fails).

For example, I have a simple custom IdentifierTag called "resource" that I use to grab some resource from a remote storage (emulated by a local file here) and inject the contents into the template. I defined the IdentifierTag like so:

parser.RegisterIdentifierTag(
    "resource",
    async (s, writer, _, _) =>
    {
        if (s != "logo.png")
            return Completion.Normal;

        var logoBytes = File.ReadAllBytes(@"Data/logo.png");
        var base64 = Convert.ToBase64String(logoBytes);

        await writer.WriteAsync($"<img src=\"data:image/png;base64, {base64}\" alt=\"barcode\" />");

        return Completion.Normal;
    }
)

And i use it like so:

{% resource logo.png %}

This just stops the rendering wherever the call to my tag is located in the file. For instance, this:

{% if IsOk %}
  {% resource logo.png %}
{% endif %}

throws an error stating that {% endif %} is missing, because the renderer stopped right before it and did not finish reading the template.

I did some tests to try and work around the problem, like trying to put my "logo.png" into a variable:

{% assign logoName = "logo.png" %}
{% resource logoName %}

but I end up with "logoName" as my identifier, which implies that variables aren't subtituted here. Which means that it is completely unecessary to treat '.' in the identifier name as a member access operator, since the variable won't get substituted in the end anyway.

I also tried to 'escape' the '.' as best I could, either with '\' which lead to the same error as before (the parser stops right here), or with single or double quotes around logo.png, but that results in an error stating "an identifier was expected after resource tag".

Am I doing something horribly wrong here ? Did I miss something ? Or is it an actual bug ?

For anyone encountering the same kind of problem, I managed to work around it by using Functions instead of IdentifierTags, but it's a lot more boilerplate than I would like for a simple lookup in a table. Also, I scratched my head for way too long on this issue, not understanding why my {% endif %} was "missing" when it was absolutely not x)

@Naliwe Identifier in Parlot startes with underscore or letter and contains letters or digits. You probably need to create a custom tag using Parlot Text instead

@hishamco Fluid uses a custom class for identifiers.

@Naliwe Identifier can't contain a dot (.) in liquid, by design. When you writing parser.RegisterIdentifierTag it means it expects an identifier, so if you want "something" that can contain a dot, then don't ask for an identifier.

What you seem to want is an Expression. Then it will accept either an identifier (without a dot), strings, numbers, ...

Example with the layout tag in the ViewEngine project:

RegisterExpressionTag("layout", static async (pathExpression, writer, encoder, context) =>
{
var layoutPath = (await pathExpression.EvaluateAsync(context)).ToStringValue();
// If '' is assigned, remove any Layout, for instance to override one defined in a _viewstart
if (string.IsNullOrEmpty(layoutPath))
{
context.AmbientValues[Constants.LayoutIndex] = null;
return Completion.Normal;
}
context.AmbientValues[Constants.LayoutIndex] = layoutPath;
return Completion.Normal;
});

@hishamco Fluid uses a custom class for identifiers.

I thought it uses the same Parlot identifier rules

Honestly it wasn't my best accomplishment to introduce something like this in Parlot. It only works for JavaScript ;)

@sebastienros Alright thanks! Functions seem to do the trick for my usecase, I don't really want to allow a whole expression here, it would confuse my consumers and probably fail more often than it succeeds if I let them put expressions in there :p

Is there anywhere I could have seen what the definition of Identifier is ? I read both this doc and the Liquid documentation and couldn't find much ~ Also, is it normal that it just stops rendering without throwing anything or at least telling me ? It's quite confusing x)

You can create your own rule (a Parser), there is an overload for that. I assume you'd just say "any alphanumeric characters, underscores and dashes".

Also, is it normal that it just stops rendering without throwing anything or at least telling me ? It's quite confusing x)

No, because syntactically it's correct. And expression can be a member accessor. And in the case of logo.png it just mean the png member of the logo variable.

And in the case of logo.png it just mean the png member of the logo variable.

Yes I understand that part, that was exactly my assumption: it's a member access operator; my confusion comes from the fact that it stops rendering everything that's after it without any errors (as in if I put it at the first line of a 100 lines long file, exactly 0 lines are written to the output). I would have expected some kind of 'not found' or at least a 'Nil' written in the resulting HTML. Here it just stops and the program finishes normally (exit status is 0).

This just stops the rendering wherever the call to my tag is located in the file.

That's expected, it just couldn't parse the template because it didn't expect anything after the first valid identifier. When you parse a template there is an overload with an error output, use this to render issue with a template to the user. (or log it).

Yes, I use it :P
image

That is why I got confused, there are no errors thrown here