Skip to content
cgrand edited this page Sep 13, 2010 · 26 revisions

(Original version of this tutorial – Adrian Cuthbertson 2009-04-23 – http://clojure.googlegroups.com/web/enlive-tut1.txt).

Introduction

Enlive is a library for applying clojure based transformations to html documents.
The basic procedure is that you define a template with rules, each having a CSS like selector
which selects certain nodes from a parsed html document, and a directive for applying a
transformation to the selected node/s in order to create a new output html document.

The Enlive project is hosted at http://github.com/cgrand/enlive.
The project is under active development, so check the latest correct branch to work with at the site.

To install…

git clone git://github.com/cgrand/enlive.git

The src directory and lib/tagsoup-1.2.jar are needed on your classpath to use Enlive.

This tutorial conducts a repl session to introduce Enlive’s main features.

Html Source

Lets start with a simple example. Say we have a file t1.html containing…

<html>
  <body>
    <h1></h1>
  </body>
</html>

Exploring what we can do from the repl – start your repl making sure the classpath includes
clojure.jar, clojure-contrib.jar, enlive/src, envlive/lib/tagsoup-1.2.jar. Then set your namespace
as follows…

user=> (ns x (:refer-clojure :exclude [empty complement]) (:use net.cgrand.enlive-html))

This excludes the clojure/core versions of empty and complement (enlive has it’s own versions),
loads enlive-html and sets the ns to x. So, x now contains clojure/core and enlive.

x=> (html-resource “t1.html”) ({:tag :html, :attrs nil, :content [{:tag :body, :attrs nil, :content [{:tag :h1, :attrs nil, :content nil}]}]})

From this we see that the html source is parsed from the file into a hash map of nodes representing the structure
of the document. Note that html-resource allows you to load source from files, classpath resources,
input streams, readers, URL’s etc. Check the source for details.

Here’s another example…

x=> (html-resource (java.io.StringReader. "<html><body><h1></h1></body></html>"))
({:tag :html, :attrs nil, :content [{:tag :body, :attrs nil, :content [{:tag :h1, :attrs nil, :content nil}]}]})

In the deftemplate macro shown below, the html source is determined from the source parameter type, e.g a String
implies a file name/path, a Reader implies a Reader source, etc.

Defining Enlive Clojure Templates

Next step is to define a template that will be used to transform the html source…

x=> (deftemplate t1 “t1.html” [] [:html] (content “wargh”))

This says define a template called t1 which transforms source file “t1.html”. We won’t pass any parameters
at this stage (hence []). This is followed by the rules for transformation. These comprise one or more
selector/directive pairs. In this case, select a node with the tag :html (from the parsed tree described above)
and replace it’s content with “wargh”.

x=> t1 #<core$comp__3776$fn__3778 clojure.core$comp__3776$fn__3778@e73e0>

So, t1 has been defined (compiled) as a function

Performing the Transformation

To perform the transformation, call the defined template function (passing parameters if any)…

x=> (apply str (t1))
"<html>wargh</html>"

Why did we use (apply str… ?

x=> (t1) (“<” “html” “>” “wargh” “</” “html” “>”)

Just calling the function performs the transformation, but returns a list of tokens which need to be
concatenated into the final output string.

Maybe we actually wanted to set the heading rather than simply replace the :html’s full contents…

x=> (deftemplate t1 "t1.html" [hdr] [:h1] (content hdr))
x=> (apply str (t1 "First Enlive App"))
"<html><body><h1>First Enlive App</h1></body></html>"

That’s more likely what we might do, but the two examples illustrate the basic ideas.

A More Elaborate Example

First we need a more elaborate html file (t2.html)…

<html>
  <body>
    <h1>Sample Header</h1>
    <div id="d1">
      <p class="d"></p>
    </div>
    <div id="d2">
      <ul class="l">
      </ul>
    </div>
  </body>
</html>

Our goal is to create a Clojure program which will transform this html by…

  • Replacing the sample header
  • Adding a paragraph to div#d1
  • Adding some items to the ul in div#d2

First lets create a function to generate the li items for the ul…

x=> (defn to-li “Given a sequence of items, generate a set of li html list items” [itms] (map (fn [x] {:tag :li :content x}) itms))

Test that…

x=> (to-li [“one” “two”]) ({:tag :li, :content "one"} {:tag :li, :content "two"})

Now the deftemplate…

x=> (deftemplate t2 "t2.html" [hdr para-txt li-itms]
      [:h1] (content hdr)
      [:div#:d1 :p] (content para-txt)
      [:div :ul.l] (content (to-li li-itms)))

x=> (apply str (t2 "Second Enlive App" "Some paragraph stuff" ["one" "two" "three"]))
"<html><body><h1>Second Enlive App</h1><div id=\"d1\"><p class=\"d\">Some paragraph stuff</p></div><div id=\"d2\">
<ul class=\"l\"><li>one</li><li>two</li><li>three</li></ul></div></body></html>"

Note: The html file must exist before the deftemplate is executed as the compilation (see above)
includes parsing the file.

That should get you started with Enlive.

At a Lower Level

The selectors are similar to CSS selectors. Refer to the Enlive project site for a table
showing Enlive’s selectors and the equivalent CSS selector.

It’s quite useful while developing with Enlive to try things out at a lower level. The at function can be used
for this…

(def t2-nd (first (html-resource “t2.html”)))

That just loads the html and t2-nd now contains the parsed node…

x=> t2-nd {:tag :html, :attrs nil, :content [{:tag :body, :attrs nil, :content [{:tag :h1, :attrs nil, :content [“Sample Header”]} {:tag :div, :attrs {:id "d1"}, :content [{:tag :p, :attrs { :class "d"}, :content nil}]} {:tag :div, :attrs {:id "d2"}, :content [{:tag :ul, :attrs {:class "l"}, :content nil}]}]}]}

Now you can use at to experiment with selectors and transformation directives…

x=> (at t2-nd [:div :ul.l] (content (to-li [“one” “two”]))) ({:tag :html, :attrs nil, :content [{:tag :body, :attrs nil, :content [{:tag :h1, :attrs nil, :content [“Sample Header”]} {:tag :div, :attrs {:id "d1"}, :content [{:tag :p, :attrs {:class "d"}, :content []}]} {:tag :div, :attrs {:id "d2"}, :content [{:tag :ul, :attrs {:class "l"}, :content ({:tag :li, :attrs nil, :content "one"} {:tag :li, :attrs nil, :content "two"})}]}]}]})

Transformation Directives

So, every rule has a selector on the left and a transformation directive on the right. The transformation
directive is a function that takes one argument (the selected node) and returns a node or sequence of nodes.

Here’s an example…

x=> (at t2-nd [:div :ul.l] (fn [nd] (prn nd) {:tag :huh :attrs {:wah "foo"} :content nil})) ({:tag :ul, :attrs {:class "l"}, :content []} {:tag :html, :attrs nil, :content [{:tag :body, :attrs nil, :content [{:tag :h1, :attrs nil, :content [“Sample Header”]} {:tag :div, :attrs {:id "d1"}, :content [{:tag :p, :attrs {:class "d"}, :content []}]} {:tag :div, :attrs {:id "d2"}, :content [{:tag :huh, :attrs {:wah "foo"}, :content nil}]}]}]})

There we’re just printing out the selected node, but you could use it in applying the transformation. We’re
returning the replacement node as a :huh tag with a :wah attribute with value “foo”.

All that (content x) does is define a function that assocs x into the :content of the passed node.
Here’s it’s definition…

(defn content “Replaces the content of the node. Values can be nodes or nested collection of nodes.” [& values] #(assoc % :content (flatten values)))

There are a number of other transformation helper functions in addition to content – wrap, set-attr, remove-attr,
etc. Check the source for more info.

This should give you an idea of how powerful Enlive really is and how you could develop sophisticated libraries
of selection/transformation functions.

We’ll cover snippets and other more advanced topics in other tutorials.

Summary

Enlive is a powerful html transformation library. It is NOT a templating system in the traditional sense of the
word (where code is embedded in html files, e.g JSP, etc).

It is easy to use at a high-level – straight html file transformations, but also has the lower-level features which
will allow you to develop very advanced web/content generation applications.

This is functional web development at its best.

Clone this wiki locally