从一种 HTML 表示形式转换为另一种 HTML 表示形式的算法



我有一个以奇怪形式表示的 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"]]]))))

它并不像我希望的那样清楚,但它有效。万岁。:-)

最新更新