Skip to content

xmlet/HtmlApi

Repository files navigation

Maven Central Build Coverage Vulnerabilities Bugs

HtmlApi

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.

Installation

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>

Usage

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.
A few notes regarding the usage of the DSL:

  • 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.

The Visitor Pattern

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.
By implementing these methods we can achieve a solution that manipulates the HTML language for any objective. Apart from those four methods we also have methods for each HTML element, such as html, body, table, etc. which provides more control when defining the behaviour in the Visitor implementation. All these methods default behaviour is to invoke the sharedVisit method as shown in the following snippet of the ElementVisitor class.
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.

Element binding

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.

Code Quality

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.

Final remarks

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.