Skip to content

xmlet/HtmlApiFaster

Repository files navigation

Maven Central Build Coverage Vulnerabilities Bugs

HtmlApiFaster

HtmlApiFaster 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 XsdAsmFaster, XsdParser and ASM. More information of how this library was generated will be added further.

NOTICE: The HTML elements generator XsdAsmFaster is disabled in maven executions section to allow other customizations in source code such as the package org.xmlet.htmlapifaster.async. If you need new features of XsdAsmFaster you have to enable it again.

Installation

First, in order to include it to your Maven project, simply add this dependency:

<dependency>
    <groupId>com.github.xmlet</groupId>
    <artifactId>htmlApiFaster</artifactId>
    <version>1.0.17</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(){
        CustomVisitor customVisitor = new CustomVisitor();

        new Html<Html>(customVisitor)
            .head()
                .comment("This is a comment.")
                .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!")
                                        .__()
                                    .__()
                                .__()
                            .__()
                        .__()
                    .__()
                .__()
            .__()
        .__();

        String result = customVisitor.getResult();
    }
}
The DSL that the HtmlApiFaster 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:

  • visitElement(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.
  • visitAttribute(String attributeName, String attributeValue) - This method is called when an attribute method is called. It received the attribute name and the attribute value.
  • visitParent(Element element) - This method is called when the __() method is invoked, receiving the instance where the method was invoked.
  • visitText(Text text) - This method is called when the text method is invoked.
  • visitComment(Text comment) - This method is called when the comment method is invoked.
Apart from this five methods we have other specific methods for each element class created, e.g. the Html class, as we can see below with the methods visitParentHtml and visitElementHtml. The same strategy is applied to attributes using the manifest attribute as an example with the method visitAttributeManifest. This way a concrete Visitor implementation can redefine these methods to create a specific behaviour to solve a certain proble.
public class ElementVisitor {
    public abstract void visitElement(Element element);
    
    public abstract void visitAttribute(String attributeName, String attributeValue);
    
    public abstract void visitParent(Element element);
    
    public abstract <R> void visitText(Text<? extends Element, R> text);
    
    public abstract <R> void visitComment(Text<? extends Element, R> comment);
    
    public void visitOpenDynamic() { }
    
    public void visitCloseDynamic() { }
    
    public void visitParentHtml(Html element) {
        this.visitParent(element);
    }
    
    public void visitElementHtml(Html element) {
        this.visitElement(element);
    }
    
    public void visitAttributeManifest(String manifest) {
        this.visitAttribute("manifest", manifest);
    }
}

Element binding

The HtmlApiFaster provides the definition of HTML templates as functions. For example, in the next snippet we define a template that returns a table presenting information received as parameter.

public class BinderExample{
    public String bindExample(List<String> names){
        CustomVisitor visitor = new CustomVisitor();
        
        new Html<>(visitor)
            .body()
                .table()
                    .tr()
                        .th()
                            .text("Title")
                        .__()
                    .__()
                    .of(table ->
                        names.forEach(name ->
                            table
                                .tr()
                                    .td()
                                        .text(name)
                                    .__()
                                .__()
                        )
                    )
                .__()
            .__()
        .__();

        return visitor.getResult();
    }
 }

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.

Changelog

1.0.17

  • New Kotlin SuspendConsumer with Element as receiver of the accept() suspending function.
  • New visitSuspending() that deals with a suspend function of SuspendConsumer.
  • Element extension for String unary plus to enable +"some text" in element builders.

1.0.16

  • New raw() method in Element to distinguish from text(). The visitRaw() should keep text as it is, while visitText() should escape HTML.

1.0.15

  • New attributes to globalEventAttributes

1.0.12

  • Replaced async() method by await(), which can deal with any type of asynchronous API, ond not only Observable.
  • New interface AsyncElement<E extends Element> {<M> E await(AwaitConsumer<E,M> asyncAction);}
  • New interface AwaitConsumer<T,M> { void accept(T first, M model, OnCompletion third);
  • Changed implementation for dynamic() that uses a singe visitDynamic() instead of two visits for beginning and end.
  • Upgrade xsdAsmFaster to 1.0.10 to fix problem on duplicated packages in camelcase.

1.0.11

  • New async() method in Element to support asynchronous data models, such as Observable or Publisher.
  • New package org.xmlet.htmlapifaster.async
  • New interfaces Thenable and AsyncElement

1.0.10

  • Removed the async method and corresponding visit methods included in release 1.0.8. Theses methods have been removed from XsdAsmFaster and we intend to implement them at source code level of HtmlApiFaster.

1.0.9

  • Moves dynamic and of method from the specific elements class to a default implementation.
  • Adds a new Element, CustomElement, and a new method .custom on Element. Refer to Issue for details.

1.0.8

  • Added two new methods to ElementVisitor, visitOpenAsync and visitCloseAsync, to allow asynchronous operations.
  • Added an async method to all Elements.

1.0.7

  • First usable version.

About

HtmlApiFaster is an alternative to HtmlApi, which uses XsdAsmFaster instead of XsdAsm in order to increase performance.

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages