HtmlApi is a fluent Java DSL for the HTML5.2 language. It follows the XML schema
definition, i.e. XSD, for the HTML5.2 language, which means that all the syntax rules are enforced, either being attribute
value restrictions or regarding element organization. This DSL can be used in multiple ways, since all the classes present
in this DSL implement the Visitor pattern, so it is possible to define your own Visitor implementation to manipulate the HTML language
for any purpose, for example, to writing well formed HTML to a text file, a stream, a database, etc.
All the code present in this library was automatically generated based a XSD file representing the rules of HTML5.2. In order to generate this code some additional libraries were needed such as XsdAsm, XsdParser and ASM. More information of how this library was generated will be added further.
All the code present in this library was automatically generated based a XSD file representing the rules of HTML5.2. In order to generate this code some additional libraries were needed such as XsdAsm, XsdParser and ASM. More information of how this library was generated will be added further.
First, in order to include it to your Maven project, simply add this dependency:
<dependency>
<groupId>com.github.xmlet</groupId>
<artifactId>htmlApi</artifactId>
<version>1.0.8</version>
</dependency>
Below it is presented a Java example that shows how the DSL works. It has the following HTML as base.
<html>
<head>
<meta charset="UTF-8"/>
<title>
Title
</title>
<link type="text/css" href="/assets/images/favicon.png" />
<link type="text/css" href="assets/styles/main.css" />
</head>
<body class="clear">
<div id="col-wrap">
<header id="header">
<section class="contain clear">
<div class="logo">
<img id="brand" src="./assets/images/logo.png" />
</div>
<aside>
<em>
Advertisement: <span class="number">HtmlApi is great!</span>
</em>
</aside>
</section>
</header>
</div>
</body>
</html>
public class HtmlApiExample {
public void simpleAPIUsage(){
Html<Html> root = new Html<>();
root.head()
.meta().attrCharset("UTF-8").__()
.title()
.text("Title")
.__()
.link().attrType(EnumTypeContentType.TEXT_CSS).attrHref("/assets/images/favicon.png").__()
.link().attrType(EnumTypeContentType.TEXT_CSS).attrHref("/assets/styles/main.css").__()
.__()
.body().attrClass("clear")
.div()
.header()
.section()
.div()
.img().attrId("brand").attrSrc("./assets/images/logo.png").__()
.aside()
.em()
.text("Advertisement: ")
.span()
.text("HtmlApi is great!");
}
}
The DSL that the HtmlApi provides is pretty straightforward. After creating an Html element we can keep on
creating the HTML element tree by invoking methods of the Html class. Each class that represents an HTML element,
such as Html, Div, P, etc. has its respective methods, acording to the HTML5.2 language specification.
The naming convention of the methods has two variants:
- When adding another element - The method has the name of the element being added, i.e. calling the head() method on the root variable will add a head instance to the html children list.
- When adding another attribute - The method name has the prefix attr before the attribute name.
- The methods which add elements to the element tree return the newly created element.
- The methods which add attributes to the element attributes return the element where the attribute was added.
- To navigate to the parent element we have the __() method.
Having the Java code presented in the previous example how can we generate the respective HTML document? We need to implement
the ElementVisitor abstract class. This class has four different abstract methods:
- sharedVisit(Element element) - This method is called whenever a class generated based on a XSD xsd:element has its accept method called. By receiving the Element we have access to the element children and attributes.
- visit(Text text) - This method is called when the accept method of the special Text element is invoked.
- visit(Comment comment) - This method is called when the accept method of the special Comment element is invoked.
- visit(TextFuction textFunction) - This method is called when the accept method of the special TextFunction element is invoked.
abstract class ElementVisitor<R> {
public abstract <T extends Element> void sharedVisit(Element<T, ?> var1);
public abstract void visit(Text var1);
public abstract void visit(Comment var1);
public abstract <U> void visit(TextFunction<R, U, ?> var1);
public void visit(Html html) {
this.sharedVisit(html);
}
public void visit(Body body) {
this.sharedVisit(body);
}
public void visit(Table table) {
this.sharedVisit(table);
}
}
A concrete implementation of this class is present in the
CustomVisitor
class. In this example the HtmlApi is used to write a well formed and indented HTML document to a StringBuilder
object.
The HtmlApi provides the definition of reusable templates. This allows programmers to postpone the addition of
information to the defined element tree. An example is shown below.
public class BinderExample{
public void bindExample(){
Html<Element> root = new Html<>()
.body()
.table()
.tr()
.th()
.text("Title")
.__()
.__()
.<List<String>>binder((elem, list) ->
list.forEach(tdValue ->
elem.tr().td().text(tdValue)
)
)
.__()
.__()
.__();
}
}
In this example a Table instance is created, and a Title is added in the first row as a title header, i.e. th.
After defining the table header of the table we can see that we invoke a binder method. This method bounds the Table
instance with a function, which defines the behaviour to be performed when this instance receives the information.
This way a template can be defined and reused with different values. A full example of how this works is available at
the method testBinderUsage.
To use this approach we also need to define a concrete implementation of an ElementVisitor which supports element
binding. An example of such implementation is shown in the
CustomVisitor
class.
Even though the code present in this DSL is generated code we implemented some tests to assert code quality,
vulnerabilities and other various metrics. The results are available in the xmlet
Sonarcloud page.
Even though this DSL is created based on aumatically generated classes there are a few nuances. In order to provide
DSL users with source files and java documentation of the DSL, the automatically generated classes are decompiled,
using Fernflower Decompiler used by Intellij,
and then compiled regularly by the maven lifecycle. This process, apart from allowing the DSL users to have the
source and documention files also allows to verify that there are no compiler problems with the code, which is very
helpful when making changes in the way that this DSL is generated.