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:
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 usestps-report.html
as its template, and when given somereport-data
finds alltitle
elements that are descendants ofhead
elements (hopefully only one), and replaces their content with a nice string with the date from thereport-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:
- Start with one or more source documents
- Pick the parts you want from the source document(s)
- 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:
Will be translated into the following enlive data:
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
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:
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:
So, enlive/content
returns a new function that when given a node,
assoc
s 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:
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.
-
Funnily enough it is quite similair to XSLT, but don’t let ↩
-
But not quite: actual implementation ↩
Comments
Comments powered by Disqus