Selmer icon indicating copy to clipboard operation
Selmer copied to clipboard

Quoted strings as tag arguments have their quotes escaped and preserved

Open unitof opened this issue 4 years ago • 11 comments

Is it possible to have a quoted string as an argument to a tag, without the surrounding quotes being preserved in the output?

Selmer, as expected, does treat the quoted string as a single argument, but passes the string to the tag function with its quotes, escaped, as part of the string.

Or perhaps there's another notation which allows a string with spaces to be treated as a single argument?

(use 'selmer.parser)

(add-tag! :foo
  (fn [args context-map]
    (str "foo " (first args))))

(render "{% foo \"quux quuz\" %}" {})
=>"foo \"quux quuz\""

Using render-file removes the need to escape the quotes in the template file, but still introduces them in the output:

file.txt:

{% foo "quux quuz" %}
(render-file "file.txt" {})
=>"foo \"quux quuz\""

The {% safe %} tag has no discernible effect:

file.txt:

{% safe %}{% foo "quux quuz" %}{% endsafe %}
(render-file "file.txt" {})
=>"foo \"quux quuz\""

unitof avatar Jul 06 '20 14:07 unitof

I took a look at the built-in cycle tag to see if it handles quotes strings any differently, but it has the same behavior.

In other words, I don't think it's currently possible to write a {% cycle %} tag that cycles between strings that include spaces, without literal "s being included in the output.

(render "{% for i in items %}This is {% cycle \"an apple\" \"a banana\" %}. {% endfor %}" {:items (range 5)})

=>

"This is \"an apple\". This is \"a banana\". This is \"an apple\". This is \"a banana\". This is \"an apple\". "

Removing the literal quotes formats the output as desired, but—of course—treats the strings as four separate arguments:

(render "{% for i in items %}This is {% cycle an apple a banana %}. {% endfor %}" {:items (range 5)})

=>

"This is an. This is apple. This is a. This is banana. This is an. "

My desired output (in this specific example) is:

"This is an apple. This is a banana. This is an apple. This is a banana. This is an apple. "

Is there any tag syntax workaround to achieve that?

Note that this behavior also applies to tag-content within block-style tags, which is actually where it's most relevant to my current work.

I definitely could add a (clojure.string/replace tag-content #"\"" "") to my custom tag function, but that feels a bit icky.

unitof avatar Jul 06 '20 16:07 unitof

Yeah, the best option at the moment is to do replace in the custom tag. I'd be open to adding support for handling quoted strings, but I probably won't be able to get around to it in the near future. If you'd be ok doing a PR for this, I could certainly help with that.

yogthos avatar Jul 06 '20 17:07 yogthos

Alright, I'll see if I have time to learn how to approach this! But for now will do the replace workaround.

Would you prefer that this change the default behavior (quotes always get removed, but that technically would break backward compatibility), or be a new setting on the parser, like (parser strip-quotes-on!)?

Also would need to figure out how it could support quotes if they are desired in output strings. Probably via a double-escaping like \\\".

unitof avatar Jul 06 '20 17:07 unitof

I think it would be best to avoid breaking changes to make upgrading smoother, so escaping would be preferable.

yogthos avatar Jul 06 '20 17:07 yogthos

the render function could preserve the old behavior via and options field, or add the behavior via options.

boxxxie avatar Jul 07 '22 14:07 boxxxie

Yeah, adding flags to hint behavior would be a reasonable approach here.

yogthos avatar Jul 07 '22 14:07 yogthos

Obviously I didn't get to this—sorry! Still eager to try, but I'm very very new to Clojure so would take me a while and I may need help. Feel free to take it on someone else before I do!

unitof avatar Jul 07 '22 14:07 unitof

Definitely no rush, and if you do get around to it then I'm happy to help with a review.

yogthos avatar Jul 07 '22 15:07 yogthos

We rely on this behavior explicitly at work, with a custom tag we've added that takes arguments generated by a (JS) preprocessor. We check in the tag if the argument starts and ends with " and if so, we attempt to read it as EDN to convert it to a plain string (and if that fails, we leave it as-is). We also use that as a hint that the argument string should then be render'd -- and we wouldn't want to call render on all (string) arguments.

Which is a long-winded way of saying "please don't change the default behavior but make this possible via some option to the render functions".

seancorfield avatar Jul 07 '22 15:07 seancorfield

PR: https://github.com/yogthos/Selmer/pull/304 adds resolve-arg which will return the literal without the double quotes. If you don't want to also render the arg if a template, you can just use (parse-literal arg) from selmer.filter-parser. parse-literal will remove the double quotes if any.

didibus avatar Jul 06 '23 06:07 didibus

And new release 1.12.59 is up on Clojars with the feature.

yogthos avatar Jul 06 '23 10:07 yogthos