TDom

For some time now I've been using a technique to succinctly create and manipulate DOM-like trees from Java. It is useful to generate HTML or XML without using JSPs or pulling in large libraries.

The code is in a single java file running around 600 lines. You can directly add it to your source, or grab a jar file from the github repository. At any rate, I hope you find the code small enough to be adapted for your own needs.

Usage

Constructing HTML declaratively works with a set of static methods, and is indented directly by your editor. The resulting indentation reflects the structure of the HTML. The method names are inconspicuous, and the HTML is visually prominent as you scan the code.

import org.tdom.TDom;
import static org.tdom.TDom.*;

// ....
// Create straight-up html 
TNode html =
    n("html",
      n("head",
        n("title", t("A title"))),
      n("body",
        n("div", a("class", "content"),
          t("Hello, world."))));

n() creates nodes, a() adds attributes, and t() creates text. These methods can be nested arbitrarily, and mirrors the structure of the underlying HTML.

However, you'll probably construct large structures in separate methods rather than directly inline as above.

TNode html =
    n("html",
      makeHead(headerData),
      makeBody(bodyData));

This permits a more modular way to construct the tree, please see the section on Typical Patterns as well. But I'll stick to the declarative style in this example.

Once you have a partially constructed tree, you can continue inserting nodes. I've borrowed something like jQuery's selector mechanism for this.

// <body>
// <div class="content">
// ...

// Insert a title before the content div
html.before(".content", n("h1", t("The Title")));

// Becomes:
// <body>
// <h1>The Title</h1>
// <div class="content">
// ...

The selector syntax is limited. You can use tagged classes and ids (eg: "div.content", "span#id", "body", "#otherid" etc.)

You can also use simple attribute-based selectors. "div[itemprop='affiliation']" selects all divs that contain an attribute itemprop with the value affiliation. "a[name]" selects all a elements that have a name attribute, regardless of its value, and "[itemprop]" matches any element that has an itemprop attribute. "div.content" is equivalent to "div[class=~'content']". The ~ character indicates that content can be one of the (whitespace separated) values in the class attribute. Note that the single quote (') is used in the attribute selectors to avoid backslashitis in java.

Finally, descendant selectors can be used, (eg: "div.content a[href]" selects all a tags that have an href attribute, but only if they occur within a div.content block.)

Selectors simplify inserting new nodes relative to other nodes, or performing a bulk change on a partially created tree. Operations on selectors are always performed on all the selected nodes. Also note that methods can be chained.


// html.before(".content", ...) internally runs
// html.select(".content").before(...)

html.select(".content").dump(printWriter);
// <div class="content">Hello, world.</div>

// Append a content block to the end of the body.
html.append("body",
            n("div", a("class", "content"),
              t("Goodbye, World")));

html.select(".content").dump(printWriter);
// This now selects two nodes.
// <div class="content">Hello, world.</div>
// <div class="content">Goodbye, World</div>

If you like the chaining style, you can perform several operations in one shot.


// Add a spacing ruler at the end of all content blocks, a
// css file to the header, and render the result.
html
  .after(".content", n("hr", a("class", "space")))

  .append("head",
            n("link", a("rel", "stylesheet"),
                      a("href", "css/style.css")))
  .dump(printWriter);
The generated HTML looks like this, and you can look at the full source for this example.

Typical Patterns

Visual design is often done through CSS files, leaving the Java code to generate relatively straightforward div structures.

Java design patterns also normally have a layer that generates a data object composed of many smaller data objects, and a rendering layer that generates HTML from these objects. For instance, to render a picture album, there may be a AlbumInfo object that contains a list of PictureInfo objects, as well as some meta-data about the album itself.

One way to render such objects is by creating different methods (or classes, if you prefer) that create a sub-tree for each interesting sub-object. The parent method then composes sub-trees generated by the child methods into a larger tree, and so on.

So there could be a makePictureTile(PictureInfo) method that creates the tree for one tile. A makeAlbum(AlbumInfo) method would call makePictureTile() for each PictureInfo object and compose it into a larger tree for the album, and so on. The outer code just passes the container object to an appropriate top-level method, and renders the resultant tree after any desired tweaks.

Note that the library provides no support to format text (say dates.) I've found it simplest to use dedicated, locale-specific libraries for this purpose (eg: java.text.MessageFormat, or some other translation library.)

Lastly, as this is a data-structure rather than markup, you can render the structure into different textual formats. This makes it possible to have a servlet generate machine-readable formats (say XML or JSON) as well as HTML, depending on the URL or other parameters in the request.

Here is an example that demonstrates how you can generate both HTML and a vCard file from the same data structure.


// create content for a user profile page marked up with hCard tags.
// [a common Google indexing tweak.]

TNode html = ....;

html.dump(printWriter);
//
// <html>
// ...
// <h1>This is the info for a user</h1>
// <div class="vcard">
//   <img class="photo" src="http://example.com/bob.jpg" />
//   <strong class="fn">Bob Smith</strong> is the
//    <span class="title">Senior editor</span> at 
//    <span class="org">ACME Reviews</span>
// </div>
// <h3>This is a footer</h3>
// ...

// Select vcard tags and dump them out.
html.select(".vcard").visit(new VCardRenderer(printWriter));

// BEGIN:VCARD
// VERSION:4.0
// PHOTO:http://example.com/bob.jpg
// FN:Bob Smith
// TITLE:Senior editor
// ORG:ACME Reviews
// END:VCARD

by KB Sriram