我有一个以奇怪形式表示的 HTML(它比常规嵌套的更容易使用):
[{:text "5d" :em true :strong true}
{:text "xx" :em true}
{:text "damn" :em true :strong true}
{:text "c6"}
{:text "qwe" :em true}
{:text "asd"}
{:text "qqq" :em true :strong true}]
我需要将其转换为类似打嗝的:
[[:em
[:strong "5d"]
"xx"
[:strong "damn"]]
"c6"
[:em "qwe"]
"asd"
[:strong [:em "qqq"]]]
我想出的最好的实现是:
(defn wrap-tags [states nodes]
(if (seq states)
(reduce
(fn [nodes state]
[(into [state] nodes)])
nodes states)
nodes))
(defn p->tags
([data]
(p->tags data #{} [] []))
([[node & rest] state waiting result]
(let [new-state (set (keys (dissoc node :text)))
closed (clojure.set/difference state new-state)
waiting (conj (wrap-tags closed waiting) (:text node))
result (if-not (seq new-state)
(into result waiting)
result)
waiting (if-not (seq new-state) [] waiting)]
(if (seq rest)
(p->tags rest new-state waiting result)
(if (seq waiting)
(into result (wrap-tags new-state waiting))
result)))))
不过它无法正常工作,当 :strong 出现时它无法处理这种情况(它不知道它应该包装多少"等待"节点,并包装所有这些节点 - 但我不知道如何跟踪这一点)。对我来说,它看起来也有点丑陋,但这不那么烦人。:)它现在为我的情况返回的是:
[[:em
[:strong
[:strong "5d"]
"xx"
"damn"]]
"c6"
[:em "qwe"]
"asd"
[:em [:strong "qqq"]]]
我很想听听如何改进我的代码的任何想法。
如果我正确理解了数据的布局,看起来您想通过元素是否包含:em
来对序列进行分区,如果它们包含,则将它们包装在单个[:em...]
节点的一侧。 Clojure的partition-by
可以用来做到这一点:
(def elements [{:text "5d" :em true :strong true}
{:text "xx" :em true}
{:text "damn" :em true :strong true}
{:text "c6"}
{:text "qwe" :em true}
{:text "asd"}
{:text "qqq" :em true :strong true}])
(vec (partition-by #(:em %1) elements))
;; =>
[({:text "5d", :strong true, :em true}
{:text "xx", :em true}
{:text "damn", :strong true, :em true})
({:text "c6"})
({:text "qwe", :em true})
({:text "asd"})
({:text "qqq", :strong true, :em true})]
然后,您可以使用reduce
处理它以创建类似打嗝的结构:
(defn group->tag [acc group]
(cond
(nil? group)
acc
(:em (first group))
(conj
acc
(vec
(concat [:em]
(mapv
(fn [elt]
(if (contains? elt :strong)
[:strong (:text elt)]
(:text elt)))
group))))
:otherwise
(vec (concat acc (mapv :text group)))))
(defn elements->hiccup [elements]
(reduce
group->tag
[]
(partition-by #(:em %1) elements)))
以上看起来它产生了您要求的内容:
(elements->hiccup elements)
;; =>
[[:em
[:strong "5d"]
"xx"
[:strong "damn"]]
"c6"
[:em "qwe"]
"asd"
[:em [:strong "qqq"]]]
好吧,看来我赢了这场比赛:
(defn node->tags [node]
(set (keys (dissoc node :text))))
(defn tag-reach [data tag]
(reduce (fn [cnt node]
(if (tag node)
(inc cnt)
(reduced cnt)))
0 data))
(defn furthest-tag [data exclude]
(let [exclude (into #{:text} exclude)
tags (filterv #(not (exclude %)) (node->tags (first data)))]
(if (seq tags)
(reduce (fn [[tag cnt :as current] rival]
(let [rival-cnt (tag-reach data rival)]
(if (> rival-cnt cnt)
[rival rival-cnt]
current)))
[nil 0] tags)
[nil 1])))
(defn nodes->tree
([nodes]
(nodes->tree nodes []))
([nodes wrapping-tags]
(loop [nodes nodes
result []]
(let [[tag cnt] (furthest-tag nodes wrapping-tags)
[to-process to-recur] (split-at cnt nodes)
processed (if tag
(nodes->tree to-process (conj wrapping-tags tag))
(mapv :text to-process))
result (into result (if tag
[(into [tag] processed)]
processed))]
(if (seq to-recur)
(recur to-recur result)
result)))))
(deftest test-gen-tree
(let [data [{:text "5d" :em true :strong true}
{:text "xx" :em true}
{:text "qqq" :em true :strong true}
{:text "c6"}
{:text "qwe" :em true}
{:text "asd"}
{:text "qqq" :em true :strong true}]]
(is (= (nodes->tree data)
[[:em
[:strong "5d"]
"xx"
[:strong "qqq"]]
"c6"
[:em "qwe"]
"asd"
[:strong [:em "qqq"]]]))))
它并不像我希望的那样清楚,但它有效。万岁。:-)