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.
Premature optimization. It drove me to write a convoluted and limited system. The new Enlive is more regular, more powerful and easier to reason about.
I made it work (old Enlive), I’m making it right (new Enlive) and I’ll make it faster.
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.
A selector is either a vector or a set of selectors (denoting grouping).
Each element in the vector is either a combinator, a simple selector or a compound selector (union or intersection), here is a summary:
Enlive CSS ======================================================= :> > :* * :div div :div#some-id div#some-id :div.class1.class2 div.class1.class2 :*#foo.bar *#foo.bar :#foo.bar #foo.bar (attr? :href) *[href] (attr? :href :title) *[href][title] (attr= :href "foo") *[href=foo] (attr= :href "foo" :title "bar") *[href=foo][title=bar] [:a.action (attr? :href)] a.action[href] #{:div :p} _no equivalent_ (attr-has :foo "bar" "baz") *[foo~=bar][foo~=baz] (attr-starts *[href^=foo][title^=bar] :href "foo" :title "bar") (attr-ends *[href$=foo][title$=bar] :href "foo" :title "bar") (attr-contains *[href*=foo][title*=bar] :href "foo" :title "bar") (attr|= :lang "fr") *[lang|=fr] root *:root (nth-child 3) *:nth-child(3) (nth-child 4 2) *:nth-child(4n+2) (nth-last-child 3) *:nth-last-child(3) (nth-last-child 4 2) *:nth-last-child(4n+2) (nth-of-type 3) *:nth-of-type(3) (nth-of-type 4 2) *:nth-of-type(4n+2) (nth-last-of-type 3) *:nth-last-of-type(3) (nth-last-of-type 4 2) *:nth-last-of-type(4n+2) first-child *:first-child last-child *:last-child first-of-type *:first-of-type last-of-type *:last-of-type only-child *:only-child only-of-type *:only-of-type empty *:empty odd *:nth-child(odd) even *:nth-child(even) (but :a) :not(a) (has [:a]) no equivalent [:p (left :h1)] h1 + p [:p (lefts :h1)] h1 ~ p [:p (right :h1)] no equivalent [:p (rights :h1)] no equivalent
Some examples:
[: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__)))