Lodash template engine Nashorn

1k Views Asked by At

I'm currently trying to use the lodash template engine in Java using nashorn and I'm facing an issue.

Here's the code :

ScriptEngineManager mgr = new ScriptEngineManager();
ScriptEngine nashorn = mgr.getEngineByName("nashorn");
ScriptContext ctx = nashorn.getContext();
Bindings bindings = ctx.getBindings(ScriptContext.ENGINE_SCOPE);
nashorn.eval(readFileToString("externallibs/lodash.min.js"), bindings);
String tpl = readFileToString("hello.tpl");
bindings.put("tpl", tpl);
nashorn.eval("compiled = _.template(tpl);", bindings);
ScriptObjectMirror compiled = (ScriptObjectMirror)nashorn.get("compiled");
System.out.println("compiled = " + compiled);
Map<String, String> props = new HashMap<String, String(1);
props.put("name", "world");
bindings.put("props", props);
System.out.println(compiled.call(this, bindings.get("props"));

The template is well compiled by Nashorn, if you look at the console you'll see :

compiled = function(obj){obj||(obj={});var __t,__p='';with(obj){__p+='Hello, '+((__t=( name ))==null?'':__t);}return __p}

But when I'm trying to call the compiled template function above with a map as parameter (the props), as you would do in JS : tpl.call(this, {name:'world'})

It fails with the following error :

TypeError: Cannot apply "with" to non script object

And indeed, we can see that the compiled version of the function uses the 'with' keyword.

Does anyone have an idea on how to workaround this issue ? How should I send the parameters to render the compiled template ?

Thanks a lot for any help.

2

There are 2 best solutions below

1
On

You need to pass a JS script object to "with" statement. Arbitrary Java object (which is not a JS object - say a java.util.Vector or object of your class Foo) can not be used as "with" expression. You could create an empty script using code like

Object emptyScriptObj = engine.eval("({})");

and pass emptyScriptObj to your ScriptObjectMirror.call method call for example.

1
On

To use a Java map as scope expression for "with" statement, you could use something like this:

import javax.script.*;
import java.util.*;

public class Main {
  public static void main(String[] args) throws ScriptException {
      Map<String, Object> map = new HashMap<>();
      map.put("foo", 34);
      map.put("bar", "hello");

      ScriptEngineManager m = new ScriptEngineManager();
      ScriptEngine e = m.getEngineByName("nashorn");
      // expose 'map' as a variable
      e.put("map", map);

      // wrap 'map' as a script object
      // __noSuchProperty__ hook is called to look for missing property
      // missing property is searched in the map
      e.eval("obj = { __noSuchProperty__: function(name) map.get(name) }");

      // use that wrapper as scope expression for 'with' statement
      // prints 34 and hello from the map
      e.eval("with (obj) { print(foo); print(bar); }");
   }
}