summaryrefslogtreecommitdiff
path: root/compojure-3.2/src/compojure/html/gen.clj
blob: 617190d598750abe637106779294b8c27569b7c5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
;; Copyright (c) James Reeves. All rights reserved.
;; The use and distribution terms for this software are covered by the Eclipse
;; Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) which
;; can be found in the file epl-v10.html at the root of this distribution. By
;; using this software in any fashion, you are agreeing to be bound by the
;; terms of this license. You must not remove this notice, or any other, from
;; this software.

(ns compojure.html.gen
  "A library for generating HTML output from a tree of vectors. The first item
   of the vector is the tag name, the optional second item is a hash of
   attributes, and the rest is the body of the tag."
  (:use compojure.str-utils
        clojure.contrib.def))

(defn optional-attrs
  "Adds an optional attribute map to the supplied function's arguments."
  [func]
  (fn [attrs & body]
    (if (map? attrs)
      (let [[tag func-attrs & body] (apply func body)]
        (apply vector tag (merge func-attrs attrs) body))
      (apply func attrs body))))

(defn escape-html
  "Change special characters into HTML character entities."
  [string]
  (.. (str string)
    (replace "&"  "&")
    (replace "<"  "&lt;")
    (replace ">"  "&gt;")
    (replace "\"" "&quot;")))

(defvar h escape-html
  "Shortcut for escape-html")

(defn- map-to-attrs
  "Turn a map into a string of HTML attributes, sorted by attribute name."
  [attrs]
  (map-str
    (fn [[key val]]
      (if key
        (str " " key "=\"" (h val) "\"")))
    (sort
      (map (fn [[key val]]
             (cond
               (true? val) [(str* key) (str* key)]
               (not val)   [nil nil]
               :else       [(str* key) (str* val)]))
           attrs))))

(defn- create-tag
  "Wrap some content in an HTML tag."
  [tag attrs content]
  (str* "<" tag (map-to-attrs attrs) ">"
          content
        "</" tag ">"))

(defn- create-closed-tag
  "Make a closed XML tag with no content."
  [tag attrs]
  (str* "<" tag (map-to-attrs attrs) " />"))

(defn- expand-seqs
  "Expand out all the sequences in a collection."
  [coll]
  (mapcat
    #(if (or (seq? %) (nil? %))
       %
       (list %))
    coll))

(defn- ensure-attrs
  "Ensure the tag has a map of attributes."
  [[tag & body]]
  (if (map? (first body))
    (list* tag body)
    (list* tag {} body)))

(defvar- css-lexer #"([^\s\.#]+)(?:#([^\s\.#]+))?(?:\.([^\s#]+))?")

(defn- parse-css-tag
  "Pulls the id and class attributes from a tag name formatted in a CSS style.
  e.g. :div#content -> [:div  {:id \"content\"}]
       :span.error  -> [:span {:class \"error\"}]"
  [tag attrs]
  (let [[_ tag id classes] (re-matches css-lexer (str* tag))
        attrs (merge attrs
                (if id {:id id})
                (if classes
                  {:class (.replace classes "." " ")}))]
    [tag attrs]))

(declare html)

(defvar- container-tags
  #{:a :b :body :dd :div :dl :dt :em :fieldset :form :h1 :h2 :h3 :h4 :h5 :h6
    :head :html :i :label :li :ol :pre :script :span :strong :style :textarea
    :ul}
  "A list of tags that need an explicit ending tag when rendered.")

(defn explicit-ending-tag?
  "Returns true if tag needs an explicit ending tag, even if the body of the
  tag is empty."
  [tag]
  (container-tags (keyword (str* tag))))

(defn html-tree
  "Turns a tree of vectors into a string of HTML. Any sequences in the
  tree are expanded out."
  [tree]
  (if (vector? tree)
    (let [[tag attrs & body] (ensure-attrs tree)
          [tag attrs]        (parse-css-tag tag attrs)
          body               (expand-seqs body)]
      (if (or (seq body) (explicit-ending-tag? tag))
        (create-tag tag attrs (apply html body))
        (create-closed-tag tag attrs)))
    (str tree)))

(defn html
  "Format trees of vectors into a string of HTML."
  [& trees]
  (map-str html-tree (expand-seqs trees)))