Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

a selector-based (à la CSS) templating and transformation system for Clojure

NotificationsYou must be signed in to change notification settings

cgrand/enlive

Repository files navigation

Enlive is a selector-based (à la CSS) templating library for Clojure.

David Nolen wrote anice tutorial.

Anothertutorial is by Brian Marick.

There's a quickstart section inClojure Cookbook.

Where do I get support?

On theEnlive Google Group

Artifact

All artifacts are published toclojars. Latest version is1.1.6:

[enlive "1.1.6"]

What's new in Enlive?

(most recent first)

1.1.6:

  • ADD: exception message when html-resource not found.
  • FIX: auto-reload on windows (also works with chestnut).

Auto-reloading (1.1.2)

(net.cgrand.reload/auto-reload *ns*)

Each time a resource or file used by a template/snippet is updated thenamespace is reloaded (as per(require ... :reload)).

Misc

  • Perf improvements
  • Fixes to${vars} substitutions

Pluggable parsers! (1.1.1)

The*parser* dynamic var controls the parser to be used byhtml-resource atruntime. (or you can pass{:parser XXX} as an additional arg).

For templates and snippets whose sources are not read dynamically, youcan opt for another parser either locally:

(deftemplateugh {:parser xml-parser}  (java.io.StringReader."<a><div>hello</div></a>")  [])

or globallyfor the declaring ns:

(set-ns-parser! xml-parser)

A parser is a function from InputStream to nodes.xml-parser,net.cgrand.tagsoup/parser andnet.cgrand.jsoup/parser are the threebuiltin ones.

${vars} substitutions (1.1.1)

The following selector + function is going to replace any${var} intextand attributes by the value found in the map (or any function).

[:#container any-node] (replace-vars {:name"world":class"hello"})

hiccup-style helper (1.1.0)

(content (html [:h3#hello"Hello worls"]))

older stuff

By default selector-transformation pairs are run sequentially. When you knowthat several transformations are independent, you can now specify (as anoptimization) to process them in lockstep. Note that this doesn't work withfragments 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 ora collection of nodes (instead of freely nested collections of nodes).

Dynamic selectors: selectors aren't compiled anymore. It means that you don'tneed to wrap them in(selector ...) forms anymore nor to eval them in the mostdynamic cases.

Fragment selectors allow to select adjacent nodes. They are denoted by a map oftwo node selectors (eg{[:h1] [:p]}), bounds are inclusive and they selectthe 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 nodeor a collection of nodes.

Rules are applied top-down: the first rule transforms the whole tree and theresulting 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 toan ancestor, you can "see" the transformed descendants (but you can not seeyour 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

Concepts

snippet is a unit of your page. It may be logical or visual entry,such as header, footer, page element. Snippet is usually a part of atemplate, and may serve as a container for other snippets. Forexample, for navigation on the web page. For that, let’s first define anhtml template for the navigation. Snippets are created by usingnet.cgrand.enlive-html/defsnippet function and, same as templates,they require a corresponding HTML template file to be availble in aclasspath.

So,snippet function returns a seq of nodes, it can be used as abuilding block for more complex templates.

templates combine snippets together, they serve like a basement forthe snippets. In order to create a template, you can usenet.cgrand.enlive-html/deftemplate function.deftemplate is used assomething what you would call layout in some other templatingsystems. In essence, it’s either a self-contained page (rarely true inbigger applications), or a container for snippets.

That said, atemplate is a returns a seq of string -- basically it's asnippet whose output is serialized. Templates return a seq of strings to avoidbuilding the whole string.

Templates and snippets transform a source (specified as a path (to accessresources on the classpath), a File, a Reader, an InputStream, a URI, a URL,an element or a seq of nodes).

Next concept isselectors, which are used within snippets andtemplates to identify the block of HTML code the transformation would beapplied to. They’re very similar to CSS selectors, but also allow moresophisticated, predicate-based selections, for example, you can select atag based on some part of content, or an attribute. Transformations arefunctions that triggered on the elements found by selectors. Theyreceive content obtained selector, and modify it in some way.

Quickstart tutorial

Template

If you want to go see the compiled version of the following steps all inone place, you can check outan example Ring application.

First thing you need to start, is to define your first template:

(require '[net.cgrand.enlive-html:as html])(html/deftemplatemain-template"templates/application.html"  [])

Now, you can start writing selectors and transformations for the givenselectors. Let's add a title to the template. Given that your templatealready has<head> and<title> tags, let's insert a title.

Content oftemplates/application.html:

<!DOCTYPE html><htmllang="en"><head><title>This is a title placeholder</title></head><body></body></html>
(html/deftemplatemain-template"templates/application.html"  []  [:head:title] (html/content"Enlive starter kit"))

Here,[:head :title] is a selector, pretty much like a cssselector. If you're coming from jQuery, you can write same selector as$("head title").html/content is a transformation. It puts thegiven content into the element specified by your selector.

Snippet

Let's add several snippets. For example, navigation and somecontent. For that, let's first define a template for the navigation.Content oftemplates/header.html

<!DOCTYPE html><htmllang="en"><body><header><h1>Header placeholder</h1><ulid="navigation"><li><ahref="#">Placeholder for navigation</a></li></ul></header></body></html>
(html/defsnippetmain-template"templates/header.html"  [:header]  [heading navigation-elements]  [:h1] (html/content heading)  [:ul [:li html/first-of-type]] (html/clone-for [[caption url] navigation-elements]                                                 [:li:a] (html/content caption)                                                 [:li:a] (html/set-attr:href url)))

Selectors

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 calledsteps.

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 grouppredicates into a vector:inside steps, vectors mean "and". This may seemconfusing but the rule is simple: the outer-most vector hierarchicallychains steps, all other vectors denote intersection (and) between steps.

So[:p (attr? :lang)] is going to match any elements with alang attributeinside a:p element. On the other hand,[[:p (attr? :lang)]] is going to matchanyp with alang attribute.

Similarly, sets group predicates in an union. Henceinside steps, sets mean"or". So[#{:div.class1 :div.class2}] match everydiv which has eitherclass1 orclass2. This can alternatively be writtenas[[: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 severalselectors in a set.#{[:td :em] [:th :em]} is going to match anyem insideseither ath or atd. This is equivalent to[#{:td :th} :em].

Selector Syntax

Seesyntax.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)

Theat form

Theat form is the most important form in Enlive. There are implicitatforms insnippet andtemplate.

(at a-node  [:a:selector] a-transformation  [:another:selector] another-transformation;; ...  )

The right-hand value of a rule can benil. It's the idiomatic way to remove anelement.

Transformations are closures which take one arg (the selected node) and returnnil, another node or an arbitrarily nested collection of nodes.

Rules are applied top-down: the first rule transforms the whole tree and theresulting tree is passed to the next rules.

Transformations

A transformation is a function that returns either a node or collection of node.

Enlive defines several helper functions:

;; Replaces the content of the element. Values can be nodes or collection of nodes.(content"xyz" a-node"abc");; Replaces the content of the element. Values are strings containing html code.(html-content"<blink>please no</blink>");; Wraps selected node into the given tag(wrap:div);; or(wrap:div {:class"foo"});; Opposite to wrap, returns the content of the selected nodeunwrap;; Sets given key value pairs as attributes for selected node(set-attr:attr1"val1":attr2"val2");; Removes attribute(s) from selected node(remove-attr:attr1:attr2);; Adds class(es) to the selected node(add-class"foo""bar");; Removes class(es) from the selected node(remove-class"foo""bar");; Chains (composes) several transformations. Applies functions from left to right.(do-> transformation1 transformation2);; Clones the selected node, applying transformations to it.(clone-for [item items] transformation);; or(clone-for [item items]  selector1 transformation1  selector2 transformation2);; Appends the values to the content of the selected element.(append"xyz" a-node"abc");; Prepends the values to the content of the selected element.(prepend"xyz" a-node"abc");; Inserts the values after the current selection (node or fragment).(after"xyz" a-node"abc");; Inserts the values before the current selection (node or fragment).(before"xyz" a-node"abc");; Replaces the current selection (node or fragment).(substitute"xyz" a-node"abc");; Takes all nodes (under the current element) matched by src-selector, removes;; them and combines them with the elements matched by dest-selector.(move [:.footnote] [:#footnotes] content)

Known limitations/problems

  • No namespaces support (hence unsuitable for most XML)

About

a selector-based (à la CSS) templating and transformation system for Clojure

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors20


[8]ページ先頭

©2009-2025 Movatter.jp