从嵌套地图(和矢量)创建 HTML 表格
Posted
技术标签:
【中文标题】从嵌套地图(和矢量)创建 HTML 表格【英文标题】:Create a HTML table from nested maps (and vectors) 【发布时间】:2011-01-27 12:01:15 【问题描述】:我正在尝试创建一个我以前使用 python 编写过的表(工作时间表),我认为这对我来说是对 Clojure 语言的一个很好的介绍。
我在 Clojure(或 lisp 方面)方面的经验很少,而且我在 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"]]))
我最初使用 for 尝试逐步完成,然后转到 doseq 最后是 domap(这似乎更成功)并转储内容到 html 表中(我最初的 python 程序使用 COM 将它从 sqlite 数据库输出到 excel 电子表格中)。
这是我的尝试(create-table 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,因为我可能需要添加更多的 domap只是为了在适当的列中获得轮班时间,并且代码给人一种“肮脏”的感觉。
如果我没有提供足够的信息,我提前道歉,我通常不会寻求编码方面的帮助,这也是我的第一个 SO 问题 :)。
如果您知道任何更好的方法来做到这一点,甚至是我作为新手应该知道的提示或技巧,他们绝对是受欢迎的。
谢谢。
【问题讨论】:
对于未来,您不应该像这个社区维基一样标记可回答的问题。这破坏了我们的声誉收集游戏。 ;-) 对不起,我没有意识到它抛弃了代表系统。我只是认为这意味着我的问题是可编辑的(无论如何都不需要):(。但是无论如何都感谢您的回答,我从您的帖子中学到了很多东西。:) 感谢您的帮助。我已经让它正确显示数据,我只是还没有决定如何从 SQL 数据库(比如我的 python 版本)创建那个凌乱的哈希映射,或者使用另一种方法,比如 nosql(我没有经验)...我应该在这里问,还是开始另一个问题? 【参考方案1】:我认为您的代码存在一些小问题。我已经尝试在下面的代码中更正它们。用 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))
【讨论】:
hmm,实际上喜欢使用 div,这是包含嵌套循环的好方法(这是我从 for 移到第一个位置的主要原因,因为抱怨有太多参数)。谢谢 哎呀,Brian 的帖子让我意识到我未能将:h1
、:hr
和 :table
包装在 create-table 中的 html
表单中,所以我一直在扔它们离开太...将在几秒钟内修复。至于:div
,我认为它也可以,实际上是为了最清晰的代码,尽管有一些concat
s / list*
s 等,原则上你可以不用它。
我认为 HTML 标准不允许在 div 中包装表格行或单元格。不过我可能是错的。
显然这是真的——validator.w3.org 拒绝 <table><div><tr><td>asdf</td></tr></div></table>
并带有指向错误嵌套标签的错误消息。哦,好吧,更有理由采用您的清洁器 (list*
) 方法。感谢您的提醒!【参考方案2】:
没有办法避免某种嵌套循环。但是您根本不需要domap
,Compojure 足够聪明(有时)可以为您扩展序列。 list
和 map
和 for
就足够了。例如 Michał Marczyk 的回答,或者:
(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)])))]]))
“映射一个 seq 并将所有内容包装在同一个标签中”模式很常见,以至于我有时喜欢为其使用辅助函数。
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
看看内部 seq 如何让 Compojure 吐槽。查看 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>
【讨论】:
哇,谢谢,我真的很喜欢 map-tag 辅助函数的想法,而且你给我做了研究列表*(我也不知道它存在)以上是关于从嵌套地图(和矢量)创建 HTML 表格的主要内容,如果未能解决你的问题,请参考以下文章