-
Notifications
You must be signed in to change notification settings - Fork 151
Getting started
(Original version of this tutorial – Adrian Cuthbertson 2009-04-23 – http://clojure.googlegroups.com/web/enlive-tut1.txt).
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 master 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.
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 (:use net.cgrand.enlive-html))
This 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.
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
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.
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"> <li>an item</li> </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
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 :li] (clone-for [item li-itms] (content item))) 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.
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
and sniptest
macros 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"})}]}]}]})
x=> (sniptest "<html><body><span>Hello </span>" [:span] (append "World")) "<html><body><span>Hello World</span></body></html>"
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.
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.