Enlive 如何评估其规则/转换?
How does Enlive evaluate its rules / transformations?
我喜欢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
)期间进行计算,并预期计算为一个函数当被调用时,它将依次计算出所需的内容(例如本例中的“===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 模板中添加 <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 评估转换(在所有甚至多次),即使它们甚至与当前渲染过程无关?
我可能忽略了一些东西,因为我真的很惊讶这似乎并没有打扰除了我以外的任何人。
原因是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 中的正文中添加 class "foobar" 它会这样做(不要忘记在 运行 deftemplate
之后改变 html):
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,但当我观察到以下内容时,我有些困惑。
考虑以下 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
)期间进行计算,并预期计算为一个函数当被调用时,它将依次计算出所需的内容(例如本例中的“===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 模板中添加 <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 评估转换(在所有甚至多次),即使它们甚至与当前渲染过程无关?
我可能忽略了一些东西,因为我真的很惊讶这似乎并没有打扰除了我以外的任何人。
原因是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 中的正文中添加 class "foobar" 它会这样做(不要忘记在 运行 deftemplate
之后改变 html):
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" ">")