Skip to content
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

Mutual lambda interpolation expansion and escaping problem. #100

Open
perchingeagle opened this issue Sep 9, 2016 · 8 comments
Open

Mutual lambda interpolation expansion and escaping problem. #100

perchingeagle opened this issue Sep 9, 2016 · 8 comments
Labels
inconsistency one part of the spec conflicts with another

Comments

@perchingeagle
Copy link

perchingeagle commented Sep 9, 2016

There is a rule for each of the first two codes, but if you trying using them mutually, like in the third example, it creates a problem.

Lambda Escaping (code 1) lambda rule 5

var data = {
  'lambda' : function(){
    return '>';  // it is escaped by default (Lambda Expansion)
  }
};

var template = "<{{lambda}}{{{lambda}}}";

// Result = "<&gt;>"

Lambda Interpolation Expansion (code 2) lambda rule 2

var data = {
  'planet' : 'world',
  'lambda' : function(){
    return {{planet}};  // Interpolation Expansion
  }
};

var template = "Hello, {{lambda}}";

// Result = "Hello, world"

Now simply change the value of data['planet'] from 'world' to '<' and there is the problem:

var data = {
  'planet' : '<',
  'lambda': function(){
    return "{{planet}} "  // the planet tag is escaped twice 
  }
};

// the first escaping of  '<' becomes '&lt;'  (Interpolation Expansion)
// and the second escaping becomes  '&amp;lt;' ( Lambda Escaping)

var template = "Hello, {{lambda}}";

// Result = "Hello &amp;lt;"  // makes no sense

And this is what the rule currently specifies. Is there a clause in the rules or specification that prevents this?

@groue
Copy link

groue commented Sep 9, 2016

GRMustache.swift is able to do it properly:

do {
    let data = [
        "lambda": Box(Lambda { ">" })
    ]
    let template = try! Template(string: "<{{lambda}}{{{lambda}}}")
    // <&gt;>
    let rendering = try! template.render(Box(data))
}

do {
    let data = [
        "planet": Box("world"),
        "lambda": Box(Lambda { "{{planet}}" })
    ]
    let template = try! Template(string: "Hello, {{lambda}}")
    // Hello, world
    let rendering = try! template.render(Box(data))
}

do {
    let data = [
        "planet": Box("<"),
        "lambda": Box(Lambda { "{{planet}}" })
    ]
    let template = try! Template(string: "Hello, {{lambda}}")
    // Hello, &lt;
    let rendering = try! template.render(Box(data))
}

It is able to do so by processing the string returned by the lambda as a text template (a template that does not escape double-mustache tags), and then escaping the full rendering of the text lambda.

I think this avoids total brain-fucked rendering of the majority of lambdas (those with double-mustache tags). But it requires introducing the concept of a text template in the rendering implementation. And it breaks when one wants to use triple-mustache tags in lambdas (note how both {{planet}} and {{{planet}}} have the same rendering below):

do {
    let data = [
        "planet": Box("<"),
        "lambda": Box(Lambda { "{{planet}} {{{planet}}}" })
    ]
    let template = try! Template(string: "Hello, {{lambda}}")
    // Hello, &lt; &lt;
    let rendering = try! template.render(Box(data))
}

Well, what is the conclusion? The lambda spec is broken. A proper lambda shouldn't return a template string, but allow user to perform rendering (and interact with the rendering engine HTML escaping). This is the way chosen by GRMustache and Handlebars. GRMustache implements lambdas on top of this more general solution, but with the caveats implied by the specification.

@groue
Copy link

groue commented Sep 9, 2016

FYI, the GRMustache way of dealing properly with a lambda that renders "{{planet}} {{{planet}}}" is the following.

First with the general rendering solution:

let data = [
    "planet": Box("<"),
    // RenderingInfo contains all necessary input, and the Rendering result
    // is a string tagged with a content type (HTML or text).
    "lambda": Box({ (info: RenderingInfo) -> Rendering in
        try! Template(string: "{{planet}} {{{planet}}}").render(info.context)
    })
]
let template = try! Template(string: "Hello, {{lambda}}")
// Hello, &lt; <
let rendering = try! template.render(Box(data))

Or with a template instead of a lambda (this technique is also called dynamic partial:

let lambdaTemplate = try! Template(string: "{{planet}} {{{planet}}}")
let data = [
    "planet": Box("<"),
    "lambda": Box(lambdaTemplate)
]
let template = try! Template(string: "Hello, {{lambda}}")
// Hello, &lt; <
let rendering = try! template.render(Box(data))

@perchingeagle
Copy link
Author

First of all, thank you for taking some time out of your schedule to read and write a response to my problem / question.
I thought as much that the specification is broken. If those two rules weren't implemented together, there would be no problem, or if interpolations didn't have lambdas as an option, then there wouldn't have been this issue.
While the triple lambda solves this problem, the triple lambda also has it own problem with delimiters
for example:

{{regular-tag}} {{{triple-mustache}}} {{=[ ]=}} [regular-tag] [{triple-mustache}]

@groue
Copy link

groue commented Sep 9, 2016

You're welcome, @perchingeagle. But don't expect the spec to change: it's stuck (it hasn't changed in years despite the work done by many implementors).

The way to avoid HTML escaping with custom delimiters is & tags: {{{foo}}} is equivalent to {{&foo}}, and to {{=[ ]=}} [&foo]. & was introduced specifically for custom delimiters. Quoting https://mustache.github.io/mustache.5.html:

You can also use & to unescape a variable: {{& name}}. This may be useful when changing delimiters (see "Set Delimiter" below).

@perchingeagle
Copy link
Author

I guess the current solution would be the choice of which tag to use for each situation:

{{& tag }} // for delimiters
{{{ tag }}} // for mutual lambda interpolation expansion and escaping

Thanks 👍

@perchingeagle
Copy link
Author

I guess it is safe to say that this issue is now closed
again @groue thank you

@groue
Copy link

groue commented Sep 9, 2016

Why did you close this issue? The lambda spec has an issue - there's no point hiding it under the carpet, is it?

@perchingeagle perchingeagle reopened this Sep 9, 2016
@perchingeagle
Copy link
Author

I have reopened it.

@jgonggrijp jgonggrijp added the inconsistency one part of the spec conflicts with another label Nov 6, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
inconsistency one part of the spec conflicts with another
Projects
None yet
Development

No branches or pull requests

3 participants