Parsecを使ってjinja2風テンプレートをパースする。
http://github.com/mopemope/clarsec2を使って以前のjinja2風のテンプレを解析する。
まずはおおまかなブロックを検出するだけだけど。
input
<html> <body> {% foo %} {{ variable }} {% endfoo %} <div> {#_ Comment Comment #} </div> </body> </html>
コード
(use '[clarsec]) (use '[clarsec.monad]) (require '[clojure.string :as string]) (import '(java.util.regex Pattern)) (def sp-chars #"([\\\\*+\\[\\](){}\\$.?\\^|])") (defn- esc-regex [in] (string/replace in sp-chars "\\\\$1")) (defn- build-reg [args] (string/join "|" (map esc-regex args))) (defn- create-regex [tags] (Pattern/compile (str "^(.*?)(" (build-reg tags) ")") Pattern/DOTALL)) (defmacro make-data [k] `(fn [x#] {~k x#})) (defn text-data-p [regex conv-fn] (make-parser (fn p-text-data [^String strn] (let [found (re-find (re-matcher (create-regex regex) strn)) [pair text token] (or found [nil nil nil])] (if (and token (not= token pair)) (consumed (conv-fn text) (.substring strn (- (.length pair) (.length token)))) (failed)))))) (defmacro surround2 [st ed] `(fn [p#] (between (symb ~st) (symb ~ed) p#))) (defn combine [p last] (let-bind [r p l last] (result (concat [r] l)))) ;; ------------------------------------- (def tag-block ((surround2 "{%" "%}") (<$> (make-data :tag-block) identifier))) (def variant-block ((surround2 "{{" "}}") (<$> (make-data :variant-block) identifier))) (def comment-block ((surround2 "{#" "#}") (<$> (make-data :comment-block) (text-data-p ["#}"] #(str %))))) (def text-data (text-data-p ["{{" "{%" "{#"] (make-data :text-data))) (def all (<$> (make-data :text-data) (make-parser (fn p-any-token [strn] (consumed strn ""))))) (def blocks (many1 (<|> text-data tag-block variant-block comment-block))) (def content (slurp "/tmp/index.html")) (parse$ (combine blocks all) content)
最後の部分はまあ適当なんですけど。
上半分はutilとかキモいパーサーとかです。
正規表現とミックスしたものが使えるので便利ですね。
肝心はココです。
なんとなくどんなものの組み合わせ構成されてるかわかりますね。
(def tag-block ((surround2 "{%" "%}") (<$> (make-data :tag-block) identifier))) (def variant-block ((surround2 "{{" "}}") (<$> (make-data :variant-block) identifier))) (def comment-block ((surround2 "{#" "#}") (<$> (make-data :comment-block) (text-data-p ["#}"] #(str %))))) (def text-data (text-data-p ["{{" "{%" "{#"] (make-data :text-data))) (def all (<$> (make-data :text-data) (make-parser (fn p-any-token [strn] (consumed strn ""))))) (def blocks (many1 (<|> text-data tag-block variant-block comment-block))) (def content (slurp "/tmp/index.html")) (parse$ (combine blocks all) content)
結果:
{:type :consumed, :value (({:text-data "\n
\n "} {:tag-block "foo"} {:variant-block "variable"} {:tag-block "endfoo"} {:text-data "\n "} {:comment-block "_\n Comment\n Comment\n "}) [:text-data "\n \n\n\n"]), :rest ""}
一応パースできてるっぽい。
正規表現でエスケープまみれになったコードよりわかりやすく(?)スッキリ(?)書けます。
速度はもちのろん出ません!!!
(<|>でtry-errorを繰り返しまくるので)