Some time ago I wrote a post about creating an embedded dsl for Html in Java. Sadly, it was based on an abuse of lambda name reflection that was later removed from Java.
I thought I should do a followup because a lot of people still visit the old article. While it’s no longer possible to use lambda parameter names in this way, we can still get fairly close.
The following approach is slightly less concise. That said, it does have some benefits over the original:
a) You no longer need to have parameter name reflection enabled at compile time.
b) The compiler can check your attribute names are valid, and you can autocomplete them.
What does it look like?
html( head( title("Hello Html World"), meta($ -> $.charset = "utf-8"), link($->{ $.rel=stylesheet; $.type=css; $.href="/my.css"; }), script($->{ $.type= javascript; $.src="/some.js"; }) ), body( div($-> $.cssClass = "article", a($-> $.href="https://benjiweber.com/", span($->$.cssClass="label", "Click Here"), img($->{$.src="/htmldsl2.png"; $.width=px(25); $.height=px(25); }) ), p(span("some text"), div("block")) ) ) ) |
This generates the following html
<html> <head> <title>Hello Html World</title> <meta charset="utf-8" /> <link rel="stylesheet" type="css" href="/my.css" /> <script type="text/javascript" src="/some.js" ></script> </head> <body> <div class="article"> <a href="https://benjiweber.com/"> <span class="label">Click Here</span> <img src="/htmldsl2.png" width="25" height="25" /> </a> <p> <span>some text</span> <div>block</div> </p> </div> </body> </html> |
You get nice autocompletion, and feedback if you specify inappropriate values:
You’ll also get a helping hand from the types to not put tags in inappropriate places:
Generating Code
As it’s Java you can easily mix other code to generate markup dynamically:
assertEquals( """ <html> <head> <meta charset="utf-8" /> </head> <body> <p>Paragraph one</p> <p>Paragraph two</p> <p>Paragraph three</p> </body> </html> """.trim(), html( head( meta($ -> $.charset = "utf-8") ), body( Stream.of("one","two","three") .map(number -> "Paragraph " + number) .map(content -> p(content)) ) ).formatted() ); |
And the code can help you avoid injection attacks by escaping literal values:
assertEquals( """ <html> <head> <meta charset="utf-8" /> </head> <body> <p><script src="attack.js"></script></p> </body> </html> """.trim(), html( head( meta($-> $.charset = "utf-8") ), body( p("<script src=\"attack.js\"></script>") ) ).formatted() ); |
How does it work?
There’s only one “trick” here that’s particularly useful for DSLs. Using the Parameter Objects pattern from my lambda type references post.
The lambdas used for specifying the tag attributes are “aware” of their own types. And capable of instantiating the configuration they specify.
When we call
meta($ -> $.charset="utf-8") |
We make a call to
default Meta meta(Parameters<Meta> params, Tag... children) { … } |
The lambda specifying the attribute config is structurally equivalent to the Parameters<Meta> type. This provides a get() function that instantiates an instance of Meta, and then passes the new instance to the lambda function to apply the config.
public interface Parameters<T> extends NewableConsumer<T> { default T get() { T t = newInstance(); accept(t); return t; } } |
Under the hood the newInstance() method uses reflection to examine the SerializedLambda contents and find the type parameter (in this case “Meta”) before instantiating it.
You can follow the code or see the previous post which explains it in a bit more detail.
Add Mixins
It’s helpful to use interfaces as mixins to avoid having to have one enormous class with all the builder definitions.
public interface HtmlDsl extends Html.Dsl, Head.Dsl, Title.Dsl, Meta.Dsl, Link.Dsl, Script.Dsl, Body.Dsl, Div.Dsl, Span.Dsl, A.Dsl, P.Dsl, Img.Dsl {} |
Each tag definition then contains its own builder methods. We compose them together into a single HtmlDsl interface for convenience. This saves having to import hundreds of different methods. By implementing the Dsl interface a consumer gets access to all the builder methods.
Show me the code
It’s all on github. I’d start from the test examples. Bear in mind that it’s merely a port of the old proof of concept to a slightly different approach. I hope it helps illustrate the technique. It’s in no way attempting to be a complete implementation.
This approach can also be useful as an alternative to the builder pattern for passing a specification or configuration to a method. There’s another example on the type references article.
What else could you use this technique for?