Создать таблицу HTML из вложенных карт (и векторов) - PullRequest
8 голосов
/ 15 марта 2010

Я пытаюсь создать таблицу (график работы), которую я кодировал ранее, используя python, я думаю, что это было бы хорошим введением для языка Clojure для меня.

У меня очень мало опыта работы с Clojure (или в этом отношении шумиха), и я прошел свои обходы в Google и сделал много проб и ошибок, но, похоже, не могу свыкнуться с этим стилем кодирования.

Вот мои примеры данных (будут поступать из базы данных sqlite в будущем):

(def smpl2 (ref {"Salaried" 
             [{"John Doe" ["12:00-20:00" nil nil nil "11:00-19:00"]}
              {"Mary Jane" [nil "12:00-20:00" nil nil nil "11:00-19:00"]}]
             "Shift Manager"
             [{"Peter Simpson" ["12:00-20:00" nil nil nil "11:00-19:00"]}
              {"Joe Jones" [nil "12:00-20:00" nil nil nil "11:00-19:00"]}]
             "Other"
             [{"Super Man" ["07:00-16:00" "07:00-16:00" "07:00-16:00" 
                       "07:00-16:00" "07:00-16:00"]}]}))

Сначала я пытался пройти через это, используя для , затем переходя на дозыq и, наконец, domap (что кажется более успешным) и сбрасывая содержимое в html table (моя оригинальная программа на python преобразовала это из базы данных sqlite в электронную таблицу excel с использованием COM).

Вот моя попытка (таблица создания fn):

(defn html-doc [title & body] 
  (html (doctype "xhtml/transitional") 
    [:html [:head [:title title]] [:body body]])) 

(defn create-table []
  [:h1 "Schedule"]
  [:hr]
  [:table (:style "border: 0; width: 90%")
   [:th "Name"][:th "Mon"][:th "Tue"][:th "Wed"]
   [:th "Thur"][:th "Fri"][:th "Sat"][:th "Sun"]
   [:tr
    (domap [ct @smpl2] 
       [:tr [:td (key ct)]
        (domap [cl (val ct)]
           (domap [c cl]
              [:tr [:td (key c)]]))])
    ]])

(defroutes tstr
  (GET "/" ((html-doc "Sample" create-table)))
  (ANY "*" 404))

Это выводит таблицу с разделами (наемные, менеджер и т. Д.) И именами в разделах, я просто чувствую, что злоупотребляю domap, вкладывая его слишком много раз, поскольку мне, вероятно, потребуется добавить больше domaps просто чтобы получить времена сдвига в соответствующих столбцах, и код становится «грязным».

Я заранее прошу прощения, если я не включаю достаточно информации, я обычно не прошу помощи по кодированию, также это мой первый вопрос SO:).

Если вы знаете какие-либо более подходящие способы сделать это или даже советы или хитрости, которые я должен знать как новичок, они определенно приветствуются.

Спасибо.

Ответы [ 2 ]

4 голосов
/ 15 марта 2010

Нет способа избежать какого-то вложенного цикла. Но вам вообще не нужно domap, Compojure достаточно умен (иногда), чтобы расширить последовательность для вас. list и map и for достаточно. Например, ответ Михаила Марчика или:

(defn map-tag [tag xs]
  (map (fn [x] [tag x]) xs))

(defn create-table []
  (list
   [:h1 "Schedule"]
   [:hr]
   [:table {:style "border: 0; width: 90%"}
    [:tr (map-tag :th ["Name" "Mon" "Tue" "Wed" "Thu" "Fri" "Sat" "Sun"])]
    [:tr (for [[category people] smpl2]
           (list* [:tr [:td category]]
                  (for [person people
                        [name hours] person]
                    [:tr [:td name] (map-tag :td hours)])))]]))

Шаблон "сопоставить последовательность и обернуть все в один и тот же тег" достаточно распространен, и мне иногда нравится использовать для него вспомогательную функцию.

Compojure расширяет один уровень seq для вас. Таким образом, вы можете добавить некоторые вещи в list, чтобы теги появлялись последовательно в вашем HTML-выводе, что я и сделал для отображения h1 и hr. В вашем коде вы просто выбрасываете h1 и hr.

Но учтите, что он расширяет только один уровень. Когда у вас есть список списков или список последовательностей, внешний последовательность будет расширяться, а внутренние - нет.

user> (println (html (list [:div "foo"] (for [x [1 2 3]] [:div x]))))
<div>foo</div>clojure.lang.LazySeq@ea73bbfd

Посмотрите, как внутренняя последовательность делает Compojure barf. Посмотрите на compojure.html.gen/expand-seqs, чтобы увидеть, как это работает, или измените / исправьте его, если хотите.

Это может быть проблемой, если вы вложили for или for в list, так как for возвращает ленивый результат. Но вам просто нужно избегать seq-in-a-seq. Я использую list* выше. Комбинация list и html тоже подойдет. Есть много других способов.

user> (println (html (list* [:div "foo"] (for [x [1 2 3]] [:div x]))))
<div>foo</div><div>1</div><div>2</div><div>3</div>

user> (println (html (list [:div "foo"] (html (for [x [1 2 3]] [:div x])))))
<div>foo</div><div>1</div><div>2</div><div>3</div>
3 голосов
/ 15 марта 2010

Я думаю, у вас есть несколько мелких проблем с вашим кодом. Я попытался исправить их в коде ниже. Тестируя это с Compojure 0.3.2, я осмелюсь сказать, что это работает. (Не стесняйтесь указывать на все, что требует улучшения или, кажется, не работает для вас, конечно.)

(use 'compojure) ; you'd use a ns form normally

;;; I'm not using a ref here; this doesn't change much,
;;; though with a ref / atom / whatever you'd have to take care
;;; to dereference it once per request so as to generate a consistent
;;; (though possibly outdated, of course) view of data;
;;; this doesn't come into play here anyway
(def smpl2 {"Salaried"      [{"John Doe" ["12:00-20:00" nil nil nil "11:00-19:00"]}
                             {"Mary Jane" [nil "12:00-20:00" nil nil nil "11:00-19:00"]}]
            "Shift Manager" [{"Peter Simpson" ["12:00-20:00" nil nil nil "11:00-19:00"]}
                             {"Joe Jones" [nil "12:00-20:00" nil nil nil "11:00-19:00"]}]
            "Other"         [{"Super Man" ["07:00-16:00" "07:00-16:00" "07:00-16:00" 
                                           "07:00-16:00" "07:00-16:00"]}]})

(defn html-doc [title & body] 
  (html (doctype :xhtml-transitional) ; the idiomatic way to insert
                                      ; the xtml/transitional doctype
        [:html
         [:head [:title title]]
         [:body body]]))

(defn create-table []
  (html
   [:h1 "Schedule"]
   [:hr]
   [:table {:style "border: 0; width: 90%;"}
    [:tr
     [:th "Name"][:th "Mon"][:th "Tue"][:th "Wed"]
     [:th "Thur"][:th "Fri"][:th "Sat"][:th "Sun"]]
    (for [category smpl2]
      [:div [:tr [:td (key category)]] ; for returns just one thing per
                                       ; 'iteration', so I'm using a div
                                       ; to package two things together;
                                       ; it could be avoided, so tell me
                                       ; if it's a problem
       (for [people (val category)]
         (for [person people]
           [:tr
            [:td (key person)]
            (for [hours (val person)]
              [:td hours])]))])]))

(defn index-html [request]
  (html-doc "Sample" (create-table)))

(defroutes test-routes
  (GET "/" index-html)
  (ANY "*" 404))

(defserver test-server
  {:port 8080}
  "/*"
  (servlet test-routes))
...