Enlive is a selector-based (à la CSS) templating library for Clojure.
An Enlive template has two parts: a HTML file and a deftemplate
form somewhere in a clj file.
Transformations (the right-hand parts of rules) are now plain old closures. These functions take one arg (the selected node) and return nil, another node or an arbitrarily nested collection of nodes.
Rules are applied top-down: the first rule transforms the whole tree and the resulting tree is passed to the next rules.
Nodes are transformed deep-first, that is: if a selector selects several nodes, descendants are transformed first. Hence, when the transformation is applied to an ancestor, you can “see” the transformed descendants (but you can not see your transformed siblings).
/B /(T B) A if A and B are selected and transformed by T the the resulting tree is (T A ) \C \C
A snippet is a function that returns a seq of nodes, it can be used as a building block for more complex templates.
A template is a function that returns a seq of string — basically it’s a snippet whose output is serialized. Templates return a seq of strings to avoid building the whole string.
Templates and snippets transform a source (specified as a path (to access resources on the classpath), a File, a Reader, an InputStream, an URI, an URL, an element or a seq of nodes).
The at
form is the most important form in Enlive. There are implicit at
forms in snippet
and template
.
(at a-node [:a :selector] a-transformation [:another :selector] another-transformation ...)
The right-hand value of a rule can be nil. It’s the idiomatic way to remove an element.
Transformations are closures which take one arg (the selected node) and return nil, another node or an arbitrarily nested collection of nodes.
Rules are applied top-down: the first rule transforms the whole tree and the resulting tree is passed to the next rules.
Enlive enforces (in select*
and transform-loc
) that selectors can only match elements.
See syntax.html
Some examples:
Enlive CSS ======================================================= [:div] div [:body :script] body script #{[:ul.outline :> :li] [:ol.outline :> li]} ul.outline > li, ol.outline > li [#{:ul.outline :ol.outline} :> :li] ul.outline > li, ol.outline > li [[#{:ul :ol} :.outline] :> :li] ul.outline > li, ol.outline > li
At macroexpansion-time in select
, snippet
and at
macros, selectors are compiled to code: (all expansions are edited for clarity)
net.cgrand.enlive-html=> (compile-selector '[:div]) (chain descendants-or-self (tag= :div)) net.cgrand.enlive-html=> (compile-selector '[:body :script]) (chain descendants-or-self (tag= :body) descendants-or-self (tag= :script)) net.cgrand.enlive-html=> (compile-selector '[#{:ul.outline :ol.outline} :> :li]) (chain descendants-or-self (union (intersection (tag= :ol) (has-class "outline")) (intersection (tag= :ul) (has-class "outline"))) (tag= :li)) net.cgrand.enlive-html=> (compile-selector '[[:div (attr= :title "foobar")]]) (chain descendants-or-self (intersection (tag= :div) (attr= :title "foobar")))
(compile-selector '[:div])
is equivalent to (macroexpand-1 '(selector [:div]))
.
at
, select
, snippet
are macros that expect a selector to be passed. If you want to use a value instead of the selector, you have to use at*
, select*
and snippet*
which are the functions behind the macro sugar.
net.cgrand.enlive-html=> (macroexpand-1 '(at node [:div] (content "It's a div"))) (at* [node] (selector [:div]) (content "It's a div")) net.cgrand.enlive-html=> (macroexpand-1 '(select some-html some-selector)) (select* some-html (selector some-selector))
The relation between snippet
and snippet*
is more complex: snippet*
doesn’t not take a selector, you have to select your nodes before (through select
or select*
).
net.cgrand.enlive-html=> (-> '(snippet a-source [:div#foo] [args] selector1 transformation1) macroexpand-1) (snippet* (select (html-resource a-source) [:div#foo]) [args] selector1 transformation1) net.cgrand.enlive-html=> (-> '(snippet a-source [:div#foo] [args] selector1 transformation1) macroexpand-1 macroexpand-1) (let [nodes__4595__auto__ (select (html-resource a-source) [:div#foo])] (fn [args] (flatmap #(at % selector1 transformation1)) nodes__4595__auto__)))
A transformation is a function that returns either a node or collection of node.
Enlive defines several helper functions:
content (content “xyz” a-node “abc”)
html-content (html-content “please no”)
wrap (wrap :div) or (wrap :div {:class "foo"})
unwrap unwrap
set-attr (set-attr :attr1 “val1” :attr2 “val2”)
remove-attr (remove-attr :attr1 :attr2)
add-class (add-class “foo” “bar”)
remove-class (remove-class “foo” “bar”)
do→ (do→ transformation1 transformation2)
clone-for (clone-for [item items] transformation)
or (clone-for [item items]
selector1 transformation1
selector2 transformation2)
append (append “xyz” a-node “abc”)
prepend (prepend “xyz” a-node “abc”)
after (after “xyz” a-node “abc”)
before (before “xyz” a-node “abc”)
substitute (substitute “xyz” a-node “abc”)
move (move [:.footnote] [:#footnotes] content)
- No namespaces support (hence unsuitable for most XML)