Enlive如何评估其规则/转换?

时间:2016-08-05 12:26:26

标签: clojure enlive

我喜欢Enlive,但是当我观察到以下内容时,我感到有些困惑。

考虑以下Clojure代码(also available on github):

(ns enlivetest.core
  (:require [net.cgrand.enlive-html :refer [deftemplate defsnippet] :as html]))

(deftemplate page "index.html"
  [ctx]
  [:.foobar] (html/content (do (println "GENERATING FOOBAR")
                               "===FOOBAR===")))

这个HTML模板(resources / index.html)在这里:

<!DOCTYPE html>
<html>
    <body>
    </body>
</html>

在调用page模板时,我希望它完全忽略其规则的右侧(转换),因为没有与规则选择器匹配的HTML标记:.foobar

然而,事实证明,规则的右侧确实得到了评​​估:

user=> (require '[enlivetest.core :as c])
nil
user=> (c/page {})
GENERATING FOOBAR
GENERATING FOOBAR
("<!DOCTYPE html>\n" "<" "html" ">" "\n    " "<" "body" ">" "\n    " "</" "body" ">" "\n\n" "</" "html" ">")

(显然,它甚至会被评估两次 - 看似模板中的每个根HTML元素一次。)

但是为什么它会被评估,尽管没有与选择器匹配的元素?这是正确的行为吗?我错过了一些明显的东西吗?

此示例使用Enlive 1.1.6,就像其README建议的那样。

非常感谢澄清。

编辑#1:

事实证明(感谢@leetwinski),我对事物运作方式的假设是错误的:

我假设deftemplate宏只会在规则的选择器与给定HTML中的元素匹配时评估规则的右侧(转换部分)。

但这是正确的:

在调用定义的模板函数(例如page)期间,规则的右侧将始终得到评估,并且期望评估为将依次评估的函数调用时所需的内容(例如&#34; === FOOBAR ===&#34;在此示例中)只有匹配选择器的元素才会调用此函数。

这意味着,例如html/content评估此类函数(而不是直接评估所需内容)。

为了让事情按照我原先的预期运作,我可以这样写:

(deftemplate page "index.html"
  [ctx]
  [:.foobar] #((html/content (do (println "GENERATING FOOBAR")
                                 "===FOOBAR===")) %))

将产生以下输出:

user=> (c/page {})
("<!DOCTYPE html>\n" "<" "html" ">" "\n    " "<" "body" ">" "\n    " "</" "body" ">" "\n\n" "</" "html" ">")

或向HTML模板添加<div class="foobar"></div>时:

user=> (c/page {})
GENERATING FOOBAR
("<!DOCTYPE html>\n" "<" "html" ">" "\n    " "<" "body" ">" "\n\t\t" "<" "div" " " "class" "=\"" "foobar" "\"" ">" "===FOOBAR===" "</" "div" ">" "\n    " "</" "body" ">" "\n\n" "</" "html" ">")

编辑#2:

已经过了几个星期,但我仍然在讨论如何在Enlive中实现这一点。我看到自己一遍又一遍地将规则的转换部分包装成#((html/content ...) %)

是否有人解释为什么Enlive会对变换进行评估(甚至多次),即使它们与当前渲染过程甚至不相关?

我可能会忽视某些事情,因为我真的很惊讶这似乎不会打扰任何人,除了我。

1 个答案:

答案 0 :(得分:2)

原因是enlive的deftemplate宏的性质:

它需要成对的选择器到功能。在你的,这里动态生成函数:

(html/content (do (println "GENERATING FOOBAR") "===FOOBAR==="))

content只创建函数,在匹配的情况下将调用该函数。

user> ((html/content "this" "is" "fine") {:content []})
{:content ("this" "is" "fine")}

content不是宏,所以它应该评估它的参数。 所以,你看到的,不是错误的匹配函数调用,而是调用在匹配的情况下将被调用的一代函数。

你可以通过你的deftemplate形式的宏扩展轻松地看到它:

(def page
 (let*
   [opts__8226__auto__
    (merge (html/ns-options (find-ns 'user)) {})
    source__8227__auto__
    "index.html"]
   (html/register-resource! source__8227__auto__)
   (comp
     html/emit*
     (let*
       [nodes29797
        (map
          html/annotate
          (html/html-resource
            source__8227__auto__
             opts__8226__auto__))]
       (fn*
         ([ctx]
           (doall
             (html/flatmap
               (fn*
                 ([node__8199__auto__]
                    (html/transform
                      (html/as-nodes node__8199__auto__)
                     [:.foobar]
                     (html/content
                       (do
                         (println "GENERATING FOOBAR")
                         "===FOOBAR===")))))
               nodes29797))))))))

所以println中的正确字符串是:

(deftemplate page "index.html"
  [ctx]
  [:.foobar] (html/content (do (println "GENERATING FUNCTION SETTING FOOBAR AS THE NODE CONTENT")
                               "===FOOBAR===")))

您期望的行为可以通过这种方式实现:

user> 
(deftemplate page "index.html"
  [ctx]
  [:.foobar] (fn [node] (assoc node :content
                               (do (println "GENERATING FOOBAR" node)
                                   "===FOOBAR==="))))
#'ttask.core/page

user> (page {})
("<!DOCTYPE html>\n" "<" "html" ">" "\n    " "<" "body" ">" "\n    " "</" "body" ">" "\n\n" "</" "html" ">")

如果你在index.html中向body添加类“foobar”,它会这样做(不要忘记在更改html后重新运行deftemplate):

user> (page {})
GENERATING FOOBAR {:tag :body, :attrs {:class foobar}, :content []}
("<!DOCTYPE html>\n" "<" "html" ">" "\n    " "<" "body" " " "class" "=\"" "foobar" "\"" ">" "=" "=" "=" "F" "O" "O" "B" "A" "R" "=" "=" "=" "</" "body" ">" "\n\n" "</" "html" ">")