Enlive is a selector-based (à la CSS) templating library for Clojure.
[enlive "1.1.4"]
(ns your-namespace (:use [net.cgrand.enlive-html]))
An Enlive template has two parts: a HTML file and a deftemplate
form somewhere in a clj file.
David Nolen wrote a nice tutorial
Another tutorial by Brian Marick.
(most recent first)
(net.cgrand.reload/auto-reload *ns*)
Each time a resource or file used by a template/snippet is updated
the namespace is reloaded (as per (require ... :reload)
).
- Perf improvements
- Fixes to
${vars}
substitutions
The *parser*
dynamic var controls the parser to be used by html-resource
at
runtime. (or you can pass {:parser XXX}
as an additional arg).
For templates and snippets whose sources are not read dynamically, you can opt for another parser either locally:
(deftemplate ugh {:parser xml-parser} (java.io.StringReader. "<a><div>hello</div></a>") [])
or globally for the declaring ns:
(set-ns-parser! xml-parser)
A parser is a function from InputStream to nodes. xml-parser, net.cgrand.tagsoup/parser
and net.cgrand.jsoup/parser
are the three builtin ones.
The following selector + function is going to replace any ${var}
in text and attributes by the value found in the map (or any function).
[:#container any-node] (replace-vars {:name "world" :class "hello"})
(content (html [:h3#hello "Hello worls"]))
By default selector-transformation pairs are run sequentially. When you know
that several transformations are independent, you can now specify (as an
optimization) to process them in lockstep. Note that this doesn’t work with
fragments selectors.
Example:
[:a :selector] a-transformation
[:another :selector] another-transformation
[:a :dependent :selector] yet-another-transformation
If the first two transformations are independent you can rewrite this code as:
:lockstep
{[:a :selector] a-transformation
[:another :selector] another-transformation}
[:a :dependent :selector] yet-another-transformation
Transformations are now slightly restricted in their return values: a node or
a collection of nodes (instead of freely nested collections of nodes).
Dynamic selectors: selectors aren’t compiled anymore. It means that you don’t
need to wrap them in (selector …) forms anymore nor to eval thme in the most
dynamic cases.
Fragment selectors allow to select adjacent nodes. They are denoted by a map of
two node selectors (eg {[:h1] [:p]}
), bounds are inclusive and they select
the smallest matching fragments.
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 a 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
Enlive selectors can match either nodes or fragments (several adjacent nodes).
At the core, every selector is a vector. The items of this vector are called
steps.
A step is a predicate, for example :h1
, :p.some-class
or even
(attr? :lang)
.
To select elements which match several predicates, you need to group
predicates into a vector: inside steps, vectors mean “and”. This may seem
confusing but the rule is simple: the outer-most vector hierarchically
chains steps, all other vectors denote intersection (and) between steps.
So [:p (attr? :lang)]
is going to match any elements with a lang
attribute
inside a p
element. On the other hand, [[:p (attr? :lang)]]
is going to match
any p
with a lang
attribute.
Similarly, sets group predicates in an union. Hence inside steps, sets mean
“or”. So [#{:div.class1 :div.class2}]
match every div
which has either
class1
or class2
. This can alternatively be written
as [[:div #{:.class1 .class2}]]
. Indeed you can have nested “ors” and “ands”
which means nested sets and vectors.
At the top level you can have a big “or” between selectors by wrapping several
selectors in a set. #{[:td :em] [:th :em]}
is going to match any em
insides
either a th
or a td
. This is equivalent to [#{:td :th} :em]
.
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 [:div :> :*] div > * [:div :> text-node] (text children of a div) [:div :> any-node] (all children (including text nodes and comments) of a div) {[:dt] [:dl]} (fragments starting by DT and ending at the *next* DD)
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, a URI, a 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.
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)