Cannot list a hash on key-value pairs in Freemarker - freemarker

What's wrong with the following template?
package ${packageName}
public interface ${entityName} {
<#list methods as methodName, map >
public void ${methodName}(${map}) ;
</#list>
}
which gives on version 2.3.23:
freemarker.core.ParseException: Syntax error in template "javaclass.ftl" in line 5, column 29:
Encountered ",", but was expecting:
">"
at freemarker.core.FMParser.generateParseException(FMParser.java:5251)
at freemarker.core.FMParser.jj_consume_token(FMParser.java:5122)
at freemarker.core.FMParser.List(FMParser.java:1431)
at freemarker.core.FMParser.FreemarkerDirective(FMParser.java:2827)
at freemarker.core.FMParser.MixedContent(FMParser.java:3081)
at freemarker.core.FMParser.OptionalBlock(FMParser.java:3253)
at freemarker.core.FMParser.Root(FMParser.java:3432)
at freemarker.template.Template.<init>(Template.java:208)
at freemarker.cache.TemplateCache.loadTemplate(TemplateCache.java:495)
The documentation gives the following example for a hash structure
Listing hashes is very similar, but you need to provide two variable
names after the as; one for the hash key, and another for the
associated value. Assuming products is { "apple": 5, "banana": 10,
"kiwi": 15 }:
<#list products as name, price>
<p>${name}: ${price}
</#list>
<p>apple: 5
<p>banan: 10
<p>kiwi: 15
Note that my example is before submitting content.

That is expected since listing key-value was added in 2.3.25.
http://freemarker.org/docs/ref_directive_list.html#ref.directive.list
... and to list the key-value pairs of a hash (since 2.3.25):
<#list hash as key, value>
Part repeated for each key-value pair
</#list>
So, upgrade if you can or rewrite your list.
See also:
Freemarker iterating over hashmap keys

Related

Assigning a variable from another variable

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.

How to do a projection on Freemarker sequences to extract a property?

Let's say I have some object container in a freemarker variable and container.content gives me a sequence of objects (I will call them "things") with names and a String getName() accessor. I would like to produce a comma-separated list of the names from the container.content sequence.
If I already had a sequence of names instead of a sequence of things with names, I could simply do names?join(", "). Is there something concise to extract the .name-s from container.content and join them afterwards? More generally, I am looking for a functional programming map (collect, projection) operation, but did not find one in the docs.
What I have tried for now:
Currently, I have <#list container.content as x>${x.name}<#if x?has_next>, </#if></#list> to reproduce that map-then-join operation, but I find that rather verbose and it looks like a smell to me to have basically reimplemented join.
Previously I had container.content?join(", ") and I got "Thing[name=A, otherStuff=...], Thing[name=B, otherStuff=...]" instead of "A, B", of course. I do not wish to modify that Thing#toString method to only return the name instead. I would like to keep that detailed representation for debugging purposes.
You can create function which will extract key values from input sequence:
<#function map seq key>
<#assign result = []>
<#list seq as item>
<#assign result = result + [item[key]]>
</#list>
<#return result>
</#function>
And then use it like that:
${map(container.content, "name")?join(", ")}

Display multiple hash keys notation

Let's say i have a ruby hash in the style of savon soap xml response to hash
hash1= { node1­: {node­2:{node3:1­,node4:2}}­}
now to display this hash
hash1[:nod­e1][:node2­][:node3]
works and outputs => 1
hash1[:nod­e1][:node2­][:node4]
works and output => 2
hash1[:nod­e1][:node2­][:node3][:node4]
gives TypeError
although i have seen that type of code on savon scripts. What doesnt it work in my situation ?
hash1[:nod­e1][:node2­][:node3][:node4] is calling the method [] on
hash1[:nod­e1][:node2­][:node3].
Its the equivalent of trying 1[:node4]. The method on an integer takes a Fixnum and cannot implicitly convert a symbol (or a string etc) into an integer.
These multiply-nested hashes are difficult to read, aren't they? Let's spread your hash out a bit:
hash1= {
node1­: {
node­2: { node3:1­, node4:2 }
}­
}
So: The value of node1 is itself a hash. The only entry in that hash, node2, also has a hash for a value. This hash has two entries: node3 and node4, both of which have integers as values.
So hash1[:node1][:node2] returns {node3:1, node4:2}. And hash1[:node1][:node2][:node3] returns 1.
But hash1[:node1][:node2][:node3][:node4] doesn't make any sense, because 1 isn't a hash, and therefore doesn't have a key :node4. That key belongs to the :node2 hash.
It would make sense if you had hash1= { node1­: {node­2: {node3: {node4:2} }}­}. But you don't.
Like I said: these nested hashes are a pain to read...

checking if a map contains a value in Freemarker Template

I'm trying to check if a map contains a value to conditionally execute some freemarker code. This is what I've got so far:
<#if productLayout.layoutWidgetConfiguration[pos.id]??>
<#assign configId>${productLayout.layoutWidgetConfiguration[pos.id]}</#assign>
<#else>
<#assign configId></#assign>
</#if>
But I get this error, which basically fails the if condition.
Error executing FreeMarker template freemarker.core.UnexpectedTypeException: For "...[...]" left-hand operand: Expected a sequence or string (or something that's implicitly convertible to string), but this evaluated to an extended_hash (wrapper: f.t.SimpleHash):
==> productLayout.layoutWidgetConfiguration [in template "admin/pages/catalog/products/partials/productLayoutEditorRefreshZone.ftl" at line 7, column 22]
The failing instruction (print stack trace for 9 more):
==> #if productLayout.layoutWidgetConfigu... [in template "admin/pages/catalog/products/partials/productLayoutEditorRefreshZone.ftl" at line 7, column 17]
at freemarker.core.DynamicKeyName.dealWithNumericalKey(DynamicKeyName.java:141) ~[DynamicKeyName.class:2.3.20]
How can I check if a value exists in a map in a freemarker template?
Update Here:
It seems the hash doesn't like a Long key value if I change it to this, the if check works, but the value doesn't get retrieved even when it exists - so I guess the question now is how to retrieve a value from a hash with a java.lang.Long key?
<#assign configId = "">
<#if productLayout.layoutWidgetConfiguration[pos.id?string]?has_content>
Hello
<#assign configId = productLayout.layoutWidgetConfiguration[pos.id?string]>
</#if>
<h1>${pos.id}</h1>
[] only supports string hash (Map, etc.) keys and numerical sequence (List, array, etc.) indexes. For now the solution is not using [] for Map-s with non-String keys. You can use the Java API of the object instead, like myMap?api.get(nonStringKey), etc. Note that ?api has to be allowed in the configuration; see http://freemarker.org/docs/ref_builtins_expert.html#ref_buitin_api_and_has_api for more.

Iterating over a map of Object: List in Freemarker

I'm trying to iterate in a freemarker template over a HashMap<SeapSubscription, List<PiNotice>>.
The map doesn't contain any nulls (in keys or values).
The code in Freemarker is:
<#list subscriptionsWithPiNotices?keys as s>
${s.title}
<#list subscriptionsWithPiNotices[s] as piNotice>
Autoritate contractanta: ${piNotice.contractingAuthorityName}
.
.
</#list>
</#list>
If I remove the iteration from the second list (<#list subscriptionsWithPiNotices[s] as piNotice>) it all works (that is iterating over the map keys, but when I add the second part, trying to iterate over the map-s value, i get a Null / missing exception
FreeMarker template error: The following has evaluated to null or missing:
==> subscriptionsWithPiNotices[s] [in template "seap-subscription-newsletter.ftl" at line 21, column 16]
Tip: If the failing expression is known to be legally null/missing,
either specify a default value with myOptionalVar!myDefault, or use
<#if myOptionalVar??>when-present<#else>when-missing. (These
only cover the last step of the expression; to cover the whole
expression, use parenthessis: (myOptionVar.foo)!myDefault,
(myOptionVar.foo)??
The failing instruction (FTL stack trace):
==> #list subscriptionsWithPiNotices[s] a... [in template "seap-subscription-newsletter.ftl" at line 21, column 9]
#list subscriptionsWithPiNotices?keys... [in template "seap-subscription-newsletter.ftl" at line 18, column 5]
I repeat, I dumped that HashMap, and it only has one key with one ArrayList having one item inside. So there's no reason to report a null, is it ?
Finally i managed to solve the issue, by configuring a
BeansWrapper bw = BeansWrapper.getDefaultInstance();
bw.setSimpleMapWrapper(true);
cfg.setObjectWrapper(bw);
and then using the
map(key) syntax instead of map[key] or map.key. It seems to work with any type of key class.
So this is the old issue that the [] operator only supports string keys. Since 2.3.22 you can use someMap?api.get(someNonStringKey) to work that around. It needs some configuring to enable, but nothing that breaks an existing application. See this answer or this FAQ entry.

Resources