-
Notifications
You must be signed in to change notification settings - Fork 170
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
Preserve Raw Tags config #518
Preserve Raw Tags config #518
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have a vague recollection of some other workaround for raw tags, and I'm surprised that @Joeoh's work doesn't already cover it. I could be wrong though.
@@ -290,6 +291,21 @@ public void handleDeferredNode(Node node) { | |||
return ImmutableSet.copyOf(deferredNodes); | |||
} | |||
|
|||
public void handlePreservedRawTag() { | |||
hasPreservedRawTags = true; | |||
if (getParent() != null) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this check all the way up the stack (excluding global) instead of just one level?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not too sure what you mean. I am doing a recursive call, which I believe checks up the stack, excluding global: getParent().handlePreservedRawTag();
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, I was looking for a while
and I missed the recursion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess if (parent.getParent() != null) {
will execute the handlePreservedRawTag()
for Global as well. Can you verify whether we need to have parent.getParent().getParent() != null
to exclude the global one?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Like in #449, we don't want the shared global context to be altered here as it can be shared across threads. My goal with marking a context as hasPreservedTags
is so that we know that a second pass must always be done if raw tags get preserved.
In the instance that we preserve all the raw tags, but nothing gets deferred, a second pass can be done when pre-rendering. If something is deferred, that second pass will happen at serve-time. I want to exclude it for global because I just want it to mark for one run of renderForResult
as mentioned here: #447.
Does that answer what you were wondering?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I misread that question. We only need to check if the parent of what we are calling handlePreservedTag()
on is not null. I think maybe line 300: getParent().handlePreservedRawTag()
is confusing, as it would make more sense for it to be parent.handlePreservedRawTag()
.
The if
statement is checking to make sure that parent
's parent is not null, if so then handle the preserved raw tag for parent
With a context tree shape like G-a-b
, here's some sudo-sequence-code that uses questionable syntax:
this == b
parent == getParent() == a
parent.getParent() == G != null
parent.handlePreservedRawTag():
-this == a
-parent == getParent() == G
-parent.getParent() == null
-return
return
jinjava.getGlobalContextCopy(), | ||
JinjavaConfig.newBuilder().withPreserveRawTags(true).build() | ||
); | ||
Throwable throwable = catchThrowable( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can use assertThatThrownBy()
here.
@@ -33,6 +34,14 @@ public String interpret(TagNode tagNode, JinjavaInterpreter interpreter) { | |||
LengthLimitingStringBuilder result = new LengthLimitingStringBuilder( | |||
interpreter.getConfig().getMaxOutputSize() | |||
); | |||
if (interpreter.getConfig().isPreserveRawTags()) { | |||
result.append(renderNodeRaw(tagNode)); | |||
throw new PreservedRawTagException( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why throw exception here instead of
return result.toString();
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After working with the code base a bit more, I agree that it is simpler to return rather than throw an exception, and call interpreter.getContext().handlePreservedRawTag();
from here instead of in the TagNode
.newBuilder() | ||
.withPreserveRawTags(true) | ||
.build(); | ||
String input = "1{% raw %}2{% endraw %}3"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you also add a test for deferred values? I thought deferred values will not get evaluated normally. Do they get evaluated inside raw
tags?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My testing shows that deferred inside raw does not get evaluated either. https://github.com/HubSpot/jinjava/compare/test-deferred?expand=1
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nothing, including deferred tags, will get evaluated inside raw
tags. However without the preserveRawTags
flag, on the second pass, the deferred values will get evaluated once the real value is put in the context.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In my test above, the deferred values do not get evaluated in the second rendering (there is no preserveRawTags) . Maybe we are not talking about the same case?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After line 83:
interpreter.getContext().put("deferred", "resolved value");
In the second pass, deferred values will no longer be deferred (such as a contact
, which have the actual contact value in the second pass, rather than being a deferred value). If you run the test after putting that line in, I believe that it will get resolved, which is not what is desired.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think what Jack meant by second pass is another renderer pass without having DeferredValue.instance()
in the context. But in your test case, the test setUp has localContext.put("deferred", DeferredValue.instance());
so it gets deferred in the second pass as well. Yo need to remove that from the context to actually resolve them in the second pass.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, what is the use case for those? First round rendering makes it deferred, then second round makes it not deferred, but still don't want to render it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
<div>
Hi {{ contact.firstname }}!
I personalized this with Jinjava:
{% raw %}Hi {{ contact.firstname }}!{% endraw %}
</div>
This should display for someone like:
<div>
Hi Jack!
I personalized this with Jinjava:
Hi {{ contact.firstname }}!
</div>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We wouldn't want it to render like:
<div>
Hi Jack!
I personalized this with Jinjava:
Hi Jack!
</div>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Got it. Thanks.
} | ||
} | ||
|
||
public boolean getHasPreservedRawTags() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see this function is used. Is this needed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's for rendering implementations such as with HubSpot's internal pre-renderer
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I am missing the whole picture. Is this the case, the caller set the config to preserve raw tags, later it wants to confirm it did render with the flag set?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So if the caller sets to preserve raw tags, but there are no raw tags to preserve and no deferred values, the pre-rendered template can be served as-is.
If however, there are any deferred values, then the second render will also get deferred until later.
Finally, if there are no deferred values, but raw tags are preserved, then the second-pass should happen immediately with the flag off to remove the raw tags and the result can be served as-is.
This is because there isn't a way of knowing if there are going to be deferred values down the line so it must always preserve raw tags if the flag is set. 3 examples assuming deferred
is deferred:
Example 1, no deferred values, but a raw tag is preserved:
{% raw %}{{ foo.bar }}{% endraw %}
{{ resolved.variable }}
With preserveRawTags=true
the output of the first render is:
{% raw %}{{ foo.bar }}{% endraw %}
I am resolved.
Since there are no deferred values, it should be rendered again with preserveRawTags=false
. The output of the second render is:
{{ foo.bar }}
I am resolved.
Example 2, there are no deferred values or raw tags preserved:
Hello
{{ resolved.variable }}
With preserveRawTags=true
the output of the first render is:
Hello
I am resolved.
Since there are no deferred values or preserved tags, the output of the first render can be served as-is.
Example 3, there is a deferred value and preserved raw tag:
{% raw %}{{ foo.bar }}{% endraw %}
{{ deferred.variable }}
With preserveRawTags=true
the output of the first render is:
{% raw %}{{ foo.bar }}{% endraw %}
{{ deferred.variable }}
Since there is a deferred variable, it should be rendered again at serve-time with preserveRawTags=false
. The output of the second render is:
{{ foo.bar }}
I was deferred, but am resolved at serve-time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the detailed examples. It helps a lot. But I still don't see where it needs to call getHasPreservedRawTags()
. If the caller set it right before then calling the rendering, it should know it (local variable, etc).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you're mistaking the config option for this. The config option tells the interpreter to preserve the raw tags. getHasPreservedRawTags()
is only true
if a raw tag was encountered and preserved. If there were no raw tags, then getHasPreservedRawTags()
will be false
.
Essentially, the method lets the caller know if any raw tags were preserved. It prevents an unnecessary second pass from happening. If this method didn't exist, it wouldn't break anything, but Example 2 would have an extra rendering call that would yield the same result, which can be avoided by using this method.
There isn't a method to set private boolean hasPreservedRawTags
, it is only set in the context when handlePreservedRawTag()
gets called.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, my bad. I mis-read it. I am OK with this. However, if we do not have this, as you said, in example 2, we would have an extra rendering call -- it should be fast because there is nothing to render.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is true. I think it would be a minimal tradeoff of speed for more simplicity, so I'll just take this out.
This has shown me that it is too confusing to be worth the minimal performance gain
I simplified this further by removing the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM. However, I think preserveRawTags
is more explicit. Just reading preserveForSecondPass
, people may not clear on what are preserved.
Agreed with @hs-lsong on the naming. What if we wanted to render raw tags on the nth pass? |
My reasoning is that we may want to preserve more than just raw tags when pre-rendering so rather than creating config options for each type of tag to preserve, it would be better to have one config option that signifies that a second pass will happen, and raw, etc. tags should be preserved. I could take away the "second" which implies 2 passes total and make it |
Adds an option to the JinjavaConfig to preserve raw tags. This option can be set while performing a pre-render with the possibility that there will be deferred values. If a value like
contact
is deferred, and there is a raw tag like:{% raw %}Hello {{contact.firstname}}!{% endraw %}
, theraw
tags must be preserved during the first rendering pass, otherwise, on the second pass,Hello {{contact.firstname}}!
will get resolved likeHello Jack!
instead of remaining, verbatim.When no values are being deferred, (ie the final render),
preserveRawTags
should be set to false, and then raw tags will be removed from the rendered output, and leave their children as they were written.