Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

htmlwriter - build and render an HTML tree

Import with import "htmlwriter.j" as html;. Assembles an HTML element tree and renders it to a correctly-escaped HTML5 string. Pure Jennifer over strings and lists, so it runs on either binary. It is a writer, not a parser - serialization is a handful of string operations, so it has no dependency on an XML parser. It is the shared output layer an HTML-emitting consumer reuses (a Markdown renderer, a documentation generator, a view layer).

use io;
import "htmlwriter.j" as html;

def kids as list of html.Node init [];
$kids[] = html.text("hi & bye");
def p as html.Node init html.element("p", [], $kids);
io.printf("%s\n", html.render($p));          # <p>hi &amp; bye</p>

Runnable: examples/modules/htmlwriter_demo.j.

The node model

An HTML tree is Node values. A node is one of three kinds, tagged by its kind field, and is built with a constructor rather than a literal:

KindBuilt withRenders as
"element"element(tag, attrs, children)<tag ...>children</tag> (or a void tag)
"text"text(s)s, HTML-escaped
"raw"raw(s)s, verbatim (already-trusted markup only)
export def struct Node {
    kind as string, tag as string,
    attrs as list of Attr, children as list of Node, text as string
};
export def struct Attr { name as string, value as string };

Children are supplied to element as a list of Node you build first (the append sugar does not chain into a struct field, so build the list in a variable, then pass it). Attributes are a list of Attr built with attr.

Surface

CallReturnsNotes
html.element(tag, attrs, children)NodeAn element node. Pass [] for no attributes or no children.
html.text(s)NodeA text node; s is HTML-escaped on render.
html.raw(s)NodeA verbatim node; s is not escaped. Trusted markup only.
html.attr(name, value)AttrOne attribute; value is escaped in attribute context on render.
html.render(node)stringSerialize a node and its subtree to HTML5.
html.renderAll(nodes)stringSerialize a list of Node fragment in order.
html.escape(s)stringHTML-escape a bare string for text context (the helper render uses).

Escaping

Escaping is automatic and context-aware, so a value is escaped exactly once:

  • Text nodes escape &, <, > (with & first, so an existing entity is not double-escaped).
  • Attribute values additionally escape ", since they render inside double quotes.
  • raw nodes are emitted verbatim - the escape hatch for markup you have already produced (an SVG blob, a rendered sub-tree). Only pass trusted content.
def a as list of html.Attr init [];
$a[] = html.attr("title", "a \"b\" <c>");
io.printf("%s\n", html.render(html.element("span", $a, [])));
# <span title="a &quot;b&quot; &lt;c&gt;"></span>

html.escape(s) exposes the text-context escaper on its own, for when you need an escaped string without building a node.

Void elements

The HTML5 void elements - area base br col embed hr img input link meta param source track wbr - render with no closing tag, and any children passed to them are dropped (they cannot have content). The tag is matched case-insensitively.

def a as list of html.Attr init [];
$a[] = html.attr("src", "logo.png");
io.printf("%s\n", html.render(html.element("img", $a, [])));   # <img src="logo.png">

Fragments

render serializes a single node; renderAll serializes a list of sibling nodes with no wrapping element - a document fragment:

def parts as list of html.Node init [];
$parts[] = html.element("h1", [], heading);
$parts[] = html.element("hr", [], []);
io.printf("%s\n", html.renderAll($parts));

Out of scope

This module writes HTML; it does not parse it (parsing arbitrary HTML is a separate, much larger job and would want the xml system library). There is no pretty-printing / indentation pass - output is compact, which round-trips and diffs predictably; wrap it in your own formatter if you need indented source. A <!DOCTYPE html> prologue is not emitted; prepend html.raw("<!DOCTYPE html>") (or a literal string) when you need a full document.

See also

  • strings.md - replace / lower, which the escaping and void-element check build on.
  • lists.md - contains, used for the void-element lookup.
  • modules/index.md - the module catalog and import rules.