I am trying to create a FreeMarker macro that can return the interpolation of a concatenation of a string and the input variable:
<#macro findValue var>
<#if (.vars["foo." + var]) ??>
.vars["foo." + var]
<#else>
${.vars["bar." + var]}
</#if>
</#macro>
Unfortunately it doesn't work. Firstly, ${.vars["bar." + var]} gives an undefined error. Secondly, the if condition always returns false even when I can see that the sub variable do exist. It seems like the .vars variable can only look up root variables, but not sub variables like foo.test.
In FreeMarker, foo.bar is the same as foo["bar"], but inside the [] you can have an arbitrary expression that evaluates to a string. So the expression you are looking for is simply foo[var].
BTW, what your macro tries to do is just ${foo[var]!bar[var]}
Related
<#if myVar = "test">
A
<#else>
B
<#/if>
If myVar is null / not defined, neither A nor B will be output. This is fixed by adding ! after the variable:
<#if myVar! = "test">
A
<#else>
B
<#/if>
Is this intended? Because if so, it is extremely confusing and I can't imagine any user would expect it to behave this way. It is suggesting that null = "test" is somehow neither true nor false. It seems clear that null = "test" should always be considered false -- what am I missing?
Note: we have configured Freemarker to replace unknown variables with an empty string:
config.setTemplateExceptionHandler((ex, environment, out) ->
{
if (ex instanceof InvalidReferenceException)
{
try
{
out.write("");
}
catch (IOException e)
{
throw new TemplateException("Error while handling template exception.", e, environment);
}
}
else
{
throw ex;
}
});
So this would (at least I thought it would) end up being:
<#if "" = "test">
A
<#else>
B
<#/if>
In which case, I'd expect the else to be entered for sure -- but it is not.
Given this example:
<#assign myVar = null/>
<#if myVar == "test">
A
<#else>
B
</#if>
This throws the following error:
The following has evaluated to null or missing:
==> null
myVar has been evaluated to null or missing -- this seems like the source of my confusion; Freemarker does not differentiate between missing and existing-but-null values, I guess?
It's irrelevant if it's #if/#else or any other directive call, because when an error occurs (any kind of TemplateException, not just "missing value"), the whole statement is skipped. Not the variable resolution, not even the whole expression, but the whole statement. That's what the Manual and the Javadoc says too, like see: https://freemarker.apache.org/docs/pgui_config_errorhandling.html. Therefore, templateExceptionHandler setting is not for providing defaults for missing values. It's for handling situations that are genuinely errors. Like, someone did a mistake, something is not operational.
As of why not take the #else still. When an error occurs while evaluating the condition, the template engine just can't tell which branch would have been taken if the developers (or whoever) did not make a mistake. Picking either branch of the if/else is a gamble. (It's not even a 50-50 gamble, as the positive branch tends to be the right one considering the real world meaning of the output, yet, if anything, people expect the template engine to bet on the else branch.) So, hoping that you will print some error indicator at least, it rather doesn't pick either branch. But it's really automatic, as the whole #if/#elseif/#else thing is one big statement.
<#assign myVar = "test"/>
<#if myVar?? && myVar == "test">
A
<#else>
B
</#if>
Yes it is intended. When a variable is not defined or null, freemarker will raise an error.
It is explained in the FAQ: Why is FreeMarker so picky about null-s and missing variables, and what to do with it?
This is a design choice from the initial author of the framework. If you are not confortable with this choice, you can use the Default value operator (use myVar! instead of myVar), or use another templating framework.
The custom exception handler you added doesn't replace the expression with an empty String, it replaces the full if/else expression.
I am assigning value to a variable i_type using below assign statement.
<#assign i_type>
<#if x.has("type")>
<#if x.type == "ABC">"ABC"<#else>"${x.type?lower_case}"</#if>
<#else>"pqr"</#if>
</#assign>
Then I want to assign a variable in ftl conversion as:
"final_type" : <#if i_type?has_content && i_type == "pqr">1<#else>0</#if>
But value of final_type is always coming out to be 0 in all cases.
I explicitly printed value of i_type and even though it was "pqr" but condition is always coming out to be false.
what should be changed?
Why the original example doesn't work is that you have quotation marks in <#else>"pqr"</#if>, and on the other similar places. That way the captured value itself will contain the quotation marks, because the nested content of FreeMarker directives is not expressions, instead it's just like top-level template content. So just write <#else>pqr</#if>.
Anyway, a better way to write what you did is this:
<#assign i_type =
x.has("type")?then(
(x.type == "ABC")?then(x.type, x.type?lower_case),
"pqr"
)
>
You also don't need the i_type?has_content condition in the second piece of code, since something is always assigned to i_type. (But even if in reality it isn't, you can write i_type! to default a missing value to "".) So that can be written like this:
"final_type" : ${(i_type == "pqr")?then("1", "0")}
Once I used
"final_type" : <#if i_type?has_content && i_type?eval == "pqr">1<#else>0</#if>
it worked fine.
I need to assign an escaped ${expression} to a variable in Freemarker
From the question here, it's clear that we can escape the $ sign in this way
${r"${expression}"}
This works perfectly outside Freemarker context, but doesnot working inside. I am trying to do
<#assign x = "${r"${expression}"}">
But getting the following error:
Template inclusion failed:
You can't use "${" here as you are already in FreeMarker-expression-mode. Thus, instead of ${myExpression}, just write myExpression. (${...} is only needed where otherwise static text is expected, i.e, outside FreeMarker tags and ${...}-s.)
What is the way to achieve this? Thanks in advance.
I had to spent some time to figure out the following scenarios to escape ${expression} -
In Freemarker assignment:
<#assign var = r"${expression}">
In html attribute:
Some link
In Freemarker concatenation:
<#assign x = "something&"+r"${expression}"/>
Like this:
<#assign x = r"${expression}">
I have an XML document passed as root to a Freemarker template. I want some values from this XML to be assigned to variables as a string and later concatenate/print them out.
<#assign MyVar = root.child1.child2.child3.mynode>
The issue here is that even when a path doesn't exist MyVar gets assigned with a sequence+hash which cannot be printed out or converted to string. This variable although returns false for ?has_content, it needs an extra step for these checks and I have this same issue with many variables and across template files and modules.
The only solution I have been able to find was
<#assign MyVar = root.child1.child2.child3.mynode>
<#assign MyVar = MyVar ?has_content?then(MyVar , "")>
I am looking for something like the Default Value Operator which also checks for nulls like ?has_content.
Does Freemarker provide any simpler one line function to check if a variable has no content and assign it with a default?
In short:
<#assign myVar = root.child1.child2.child3.mynode[0]!''>
Or just <#assign myVar = root.child1.child2.child3.mynode[0]!> if the implicit multi-typed default value doesn't cause problems (like when you just print it with ${}).
Why: XML queries (just like XPath queries) always return a sequence of matching nodes. There are maybe 0 such nodes (or 1, or multiple). An empty sequence is not a "missing value" according the template language. It's an inconvenient mismatch with the XML data-model. But while the sequence always exists, its 1st element ([0]) doesn't, so you can use all the missing value handler operators with it as usual.
I am trying to assign a variable from another variable. My code looks like this
<#macro ctglink c rhs x y z m e b>
<#assign ctg>
<#if ctgroutes["${y}..${x}-${m}"]??>ctgroutes['${y}..${x}-${m}']
<#elseif ctgroutes["${x}..${y}-${m}"]??>ctgroutes['${x}..${y}-${m}']
<#else>{}</#if>
</#assign>
However, this ctg variable is evaluating to just ctgroutes['227..257-TPPMD04X02'] its not actually evaluating the string itself.
I have tried ?eval, and ?interpret and a bunch of other very hacky things to get this to work, no go. Even the {} is a string
Basically, I need the assign function to work like the old PHP eval() function or something. I am trying to access values in a Map whose keys are derived from the state of the data, so I don't see any easy way to query my Map without evaluating keys.
Update:
I forgot to include the elseif in there
Either way, I tried <#assign ctg = ctgroutes["${y}..${x}-${m}"]!ctgroutes["${x}..${y}-${m}"]> but I get the following error:
Caused by: freemarker.core.InvalidReferenceException: The following has evaluated to null or missing:
==> ctgroutes["${y}..${x}-${m}"]!ctgroutes["${x}..${y}-${m}"] [in template "RouteCompare-WptTable.ftlh" at line 5, column 24]
I would like a null result to just return an empty map, however that doesn't seem possible:
Caused by: java.lang.RuntimeException: freemarker.core.InvalidReferenceException: The following has evaluated to null or missing:
==> ctgroutes["${y}..${x}-${m}"]!ctgroutes["${x}..${y}-${m}"] [in template "RouteCompare-WptTable.ftlh" at line 5, column 24]
So basically, my goal is I need to assign a variable that can take 1 of 3 values:
ctgroutes["${y}..${x}-${m}"] // Assuming it is not null
ctgroutes["${x}..${y}-${m}"] // Assuming it is not null
{} // An empty map
What is the best way to do that?
If I understand well what you want to achieve, you can write it like this:
<#assign ctg = ctgroutes["${y}..${x}-${m}"]!ctgroutes["${x}..${y}-${m}"]!{}>
Also note that <#assign target>...</#assign> is for capturing the output printed between the two tags into the target variable (instead of actually printing it). So target will always store a string or markup value. Also things outside FreeMarker tags and ${} are just static text, and won't be parsed. So, the naive but working approach is just using #if/#elseif/#else and have a separate #assign ctg = ... inside each of them, but you can make this much sorter with the ! operator, as it was shown.