a selector-based (à la CSS) templating and transformation system for Clojure
Clojure
Switch branches/tags
Nothing to show
Pull request Compare This branch is 2 commits ahead, 142 commits behind cgrand:master.
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
lib
src/net/cgrand
test/net/cgrand/enlive_html
README.textile
build.xml
epl-v10.html
syntax.html

README.textile

Enlive

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.

Where do I get support?

On the group

What’s new in Enlive?

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

Templates and snippets

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

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.

Selectors

Enlive enforces (in select* and transform-loc) that selectors can only match elements.

Syntax

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

Compilation

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__)))

Transformations

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)

Known limitations/problems

  • No namespaces support (hence unsuitable for most XML)