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="https://github.com/xmlet/HtmlApiFaster/blob/master/assets/images/favicon.png" />
<link type="text/css" href="https://github.com/xmlet/HtmlApiFaster/blob/master/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="https://github.com/xmlet/HtmlApiFaster/raw/master/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 extends Element, R> text) - This method is called when the text method is invoked.
-
visitComment(Text extends Element, R> 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