This post is part of a small series of posts on enlive. See Exploring Enlive for the background.

Understanding enlive

The fundamental model implemented by enlive is slightly different from many other templating solutions1, and, while it is not necessarily more complicated than other models, I think that understanding it well is key to a good enlive experience. With this post, I hope to share some realisations that helped me understand enlive better.

I will begin by simply stating the following minimal enlive example:

(ns initech.reports
  (:require [net.cgrand.enlive-html :as enlive]))

(enlive/deftemplate tps-report "tps-report.html"
  [report-data]
  [:head :title] (enlive/content
                   (str "TPS Report for" (:date report-data)))

Depending on your background, some of this may make sense, some of it may be more cryptic. The interesting part of the above snippet is the contents of the deftemplate form, which can be read as:

give me a function called tps-report that uses tps-report.html as its template, and when given some report-data finds all title elements that are descendants of head elements (hopefully only one), and replaces their content with a nice string with the date from the report-data.

Let’s see how all this is actually accomplished.

The Enlive Way

The process of constructing a document using enlive can be divided into the following three steps:

  1. Start with one or more source documents
  2. Pick the parts you want from the source document(s)
  3. Modify and/or put the parts together, forming a new document

Enlive Data

The first step is parsing the source documents into what I’ll call enlive data. This is the fundamental data structure used by enlive in the other two steps. While knowing about this data structure is not strictly required, I think it is very helpful for understanding the last step, and it also gives you more power as a user of the library.

A source document is simply a plain old HTML file, no special funny-looking markup or tags. It only serves as a base (or template, if you will) from which you can pick as much, or as little as you want for your output document.

The data structure for enlive data is a very simple tree, represented using nested maps. Each individual map is referred to as a node.

Example

This HTML snippet:

<div id="message">
  <h1>Warning!</h1>
  <p class="wat">Invalid use of robot</p>
</div>

Will be translated into the following enlive data:

{:tag     :div
 :attrs   {"id" "message"}
 :content ({:tag     :h1
            :attrs   nil
            :content "Warning"}
           {:tag     :p
            :attrs   {"class" "wat"}
            :content "Invalid use of robot"})}

As illustrated, a node map has the keys tag, attrs and content. There are a few other node types, for example used to represent a doctype, but you will rarely need to know about those.

Selection

The second step is picking the parts of the template that you wish to use or affect. So, given some enlive data, we want to select a subset of the nodes for some further processing.

Expressed more succinctly, selection is a function of enlive data to a sequence of nodes.

This function could have been expressed using plain clojure, but since enlive targets HTML, it can leverage a more domain specific language for this purpose: CSS selectors.

CSS selectors where initially conceived for CSS stylesheets, where they are used to determine which elements certain style attributes should apply to. However, they are more general than that, it is really a pattern language for matching DOM elements. For example, jQuery uses CSS selectors to select a subset of DOM elements for some function application.

The enlive implementation of selectors is not strictly equivalent to CSS selectors, but most patterns translate quite easily. In enlive, a selector is represented using clojure data rather than strings. Christophe has a good explanation on the syntax in the readme.

Example

;; match all p elements that are descendants of the div element with id 'message'
[:div#message p]

;; match all p elements that have a 'wat' class
[:p.wat]

;; match the second tr of all tables
[:table [:tr (nth-of-type 2)]]

Transformation

The final step is where the action happens.

Each selector has a corresponding transformation, they are specified in pairs. Since selection is a function that selects (returns) a sequence of nodes, it may not be surprising to learn that transformation is a function that is applied to each of the selected nodes. This will yield a new document where each node has been “replaced” by the result of the transformation function. Thus, transformation is a function from a single node to a single node, or to a sequence of nodes.

Let’s take a look at a specific part of the initial example:

[:head :title] (enlive/content
                 (str "TPS Report for" (:date report-data)))

The body of a deftemplate form specifies pairs of selectors (a vector) and transformations. In this case we have one selector, [:head :title] and its corresponding transformation. So what does the enlive/content function actually do?

Looking at the enlive source code, you would find something almost2 like this:

(defn content
  [value]
  (fn [node] (assoc node :content value)))

So, enlive/content returns a new function that when given a node, assocs a new value for the :content of that node. Functions returning functions can be hard to grasp at first glance, but we could just as easily declare the same transformation inline in our template:

[:head :title] (fn [node]
                 (assoc node
                   :content (str "TPS Report for" (:date report-data)))))

Understanding selectors and transformations at this level made a great difference for me.

Most transformations provided by enlive are really utility functions that help you construct appropriate transformation functions. These functions will take you far without having to write your own transformation, but knowing what really goes on is always powerful.

Moving On

Hopefully, this post has given some insight into the fundamental workings of enlive. The next, and perhaps most difficult step, is to understand how to best use the constructs enlive provides to actually get what you want; a pretty HTML document.

This may require a change of thinking. Some things which you know are trivial in your favourite templating language might seem verbose when you try to implement them using enlive. While enlive is definitely more suited for certain scenarios, and less for others, I’ve often found that this perceived difficulty is more a result of how you’re used to think about templating, than anything else.

You simply need to approach the problem in a different way, much like the way you change your thinking when moving from object-oriented programming to functional programming.

This might be the topic of a future post.

that scare you.

  1. Funnily enough it is quite similair to XSLT, but don’t let

  2. But not quite: actual implementation