【问题标题】:How does Enlive evaluate its rules / transformations?Enlive 如何评估其规则/转换?
【发布时间】:2016-08-25 21:37:40
【问题描述】:

我喜欢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 模板时,我希望它完全忽略其规则的右侧(转换),因为没有与规则选择器 :.foobar 匹配的 HTML 标记。

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

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)期间被评估,并且预计会评估为一个函数,该函数将依次评估为调用时所需的内容(例如,本例中的“===FOOBAR===”)。只有匹配选择器的元素才会调用此函数。

这意味着例如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 模板添加 &lt;div class="foobar"&gt;&lt;/div&gt; 时:

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 会评估转换(全部甚至多次),即使它们甚至与当前渲染过程无关?

我可能忽略了一些事情,因为我真的很惊讶这似乎并没有打扰到除了我之外的任何人。

【问题讨论】:

    标签: clojure enlive


    【解决方案1】:

    原因是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 中将类“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" ">")
    

    【讨论】:

    • 感谢您很好地阐述了这个解释!我现在明白它为什么会这样了。
    • 但是,我不认为它的工作方式是最直观的方式,因为它可能会误导人们认为规则的转换部分仅在同一规则的选择器时才被评估火柴。我可能会提交一个拉取请求,这至少会在 Enlive 的文档/自述文件中更明确地说明这一事实。
    猜你喜欢
    • 2019-09-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多