Preserve Raw Tags config#518
Conversation
|
|
||
| public void handlePreservedRawTag() { | ||
| hasPreservedRawTags = true; | ||
| if (getParent() != null) { |
There was a problem hiding this comment.
Shouldn't this check all the way up the stack (excluding global) instead of just one level?
There was a problem hiding this comment.
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.
Thanks, I was looking for a while and I missed the recursion.
There was a problem hiding this comment.
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.
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.
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.
You can use assertThatThrownBy() here.
| ); | ||
| if (interpreter.getConfig().isPreserveRawTags()) { | ||
| result.append(renderNodeRaw(tagNode)); | ||
| throw new PreservedRawTagException( |
There was a problem hiding this comment.
Why throw exception here instead of
return result.toString();
?
There was a problem hiding this comment.
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.
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.
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.
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.
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.
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.
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.
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.
<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.
We wouldn't want it to render like:
<div>
Hi Jack!
I personalized this with Jinjava:
Hi Jack!
</div>
| } | ||
| } | ||
|
|
||
| public boolean getHasPreservedRawTags() { |
There was a problem hiding this comment.
I don't see this function is used. Is this needed?
There was a problem hiding this comment.
It's for rendering implementations such as with HubSpot's internal pre-renderer
There was a problem hiding this comment.
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.
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.
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.
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.
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.
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 |
hs-lsong
left a comment
There was a problem hiding this comment.
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
contactis deferred, and there is a raw tag like:{% raw %}Hello {{contact.firstname}}!{% endraw %}, therawtags 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),
preserveRawTagsshould be set to false, and then raw tags will be removed from the rendered output, and leave their children as they were written.