From eadbe13ee1e5d7ab0d1a34c8f5e40181fe80c1fa Mon Sep 17 00:00:00 2001 From: gered Date: Sat, 1 Apr 2017 18:24:34 -0400 Subject: [PATCH] add css stylesheet string generation functionality --- project.clj | 7 ++- src/clj_htmltopdf/css.clj | 53 ++++++++++++++++ test/clj_htmltopdf/test/css.clj | 107 ++++++++++++++++++++++++++++++++ 3 files changed, 166 insertions(+), 1 deletion(-) create mode 100644 src/clj_htmltopdf/css.clj create mode 100644 test/clj_htmltopdf/test/css.clj diff --git a/project.clj b/project.clj index 77fda07..9f4efaf 100644 --- a/project.clj +++ b/project.clj @@ -11,4 +11,9 @@ [hiccup "1.0.5"]] :profiles {:provided - {:dependencies [[org.clojure/clojure "1.8.0"]]}}) + {:dependencies [[org.clojure/clojure "1.8.0"]]} + + :dev + {:dependencies [[pjstadig/humane-test-output "0.8.1"]] + :injections [(require 'pjstadig.humane-test-output) + (pjstadig.humane-test-output/activate!)]}}) diff --git a/src/clj_htmltopdf/css.clj b/src/clj_htmltopdf/css.clj new file mode 100644 index 0000000..f9ac911 --- /dev/null +++ b/src/clj_htmltopdf/css.clj @@ -0,0 +1,53 @@ +(ns clj-htmltopdf.css + (:require + [clojure.string :as string]) + (:import + [java.lang StringBuilder])) + +; everything in this namespace solely exists because garden cannot be used +; to define suitable @page css that we will require. +; and it defines a bunch of its internal extension functionality as private. +; *sigh* + +(defn css-rule-name-str + [names] + (let [names (map #(if (keyword? %) (name %) %) names)] + (string/join ", " names))) + +(defn css-attr-str + [attr-name attr-value] + (let [attr-name (if (keyword? attr-name) (name attr-name) attr-name) + attr-value (str attr-value)] + (if (and (not (string/blank? attr-name)) (not (string/blank? attr-value))) + (str attr-name ": " attr-value ";")))) + +(defn css-rule->str + [^StringBuilder sb rule & [level]] + (if (seq rule) + (let [level (or level 0) + indent (string/join (repeat level " ")) + attr-indent (str indent " ") + names (take-while #(or (keyword? %) (string? %)) rule) + rule (drop (count names) rule) + attrs (first rule) + sub-rules (rest rule)] + (.append sb indent) + (.append sb (css-rule-name-str names)) + (.append sb " {\n") + (doseq [[attr-name attr-value] attrs] + (when-let [attr-str (css-attr-str attr-name attr-value)] + (.append sb attr-indent) + (.append sb attr-str) + (.append sb \newline))) + (doseq [sub-rule sub-rules] + (css-rule->str sb sub-rule (inc level))) + (.append sb indent) + (.append sb "}\n"))) + sb) + +(defn css->str + [rules] + (let [sb (StringBuilder.)] + (doseq [rule rules] + (css-rule->str sb rule)) + (.toString sb))) diff --git a/test/clj_htmltopdf/test/css.clj b/test/clj_htmltopdf/test/css.clj new file mode 100644 index 0000000..5cb3792 --- /dev/null +++ b/test/clj_htmltopdf/test/css.clj @@ -0,0 +1,107 @@ +(ns clj-htmltopdf.test.css + (:use + clojure.test + clj-htmltopdf.css) + (:import + [java.lang StringBuilder])) + +(deftest rule-name-parsing + (is (= "" + (css-rule-name-str nil))) + (is (= "" + (css-rule-name-str []))) + (is (= "p" + (css-rule-name-str ["p"]))) + (is (= "p" + (css-rule-name-str [:p]))) + (is (= "p, li" + (css-rule-name-str ["p" :li]))) + (is (= "h1, h2, h3, h4, h5, h6" + (css-rule-name-str [:h1 :h2 :h3 :h4 :h5 :h6]))) + (is (= "ul>li, ol>li" + (css-rule-name-str [:ul>li :ol>li]))) + (is (= "ul>li, ol>li" + (css-rule-name-str ["ul>li" "ol>li"])))) + +(deftest rule-attribute-parsing + (is (= "color: black;" + (css-attr-str :color "black"))) + (is (= "color: #ff0000;" + (css-attr-str "color" "#ff0000"))) + (is (= nil + (css-attr-str nil "foo"))) + (is (= nil + (css-attr-str :color nil))) + (is (= nil + (css-attr-str "" "bar"))) + (is (= nil + (css-attr-str :color ""))) + (is (= nil + (css-attr-str nil nil)))) + +(deftest rule-parsing + (is (= "" + (str (css-rule->str (StringBuilder.) nil)))) + (is (= "" + (str (css-rule->str (StringBuilder.) [])))) + (is (= "p {\n}\n" + (str (css-rule->str (StringBuilder.) [:p])))) + (is (= "p {\n}\n" + (str (css-rule->str (StringBuilder.) [:p {}])))) + (is (= "p {\n font-weight: bold;\n}\n" + (str (css-rule->str (StringBuilder.) [:p {:font-weight "bold"}])))) + (is (= "p {\n font-weight: bold;\n color: #000;\n}\n" + (str (css-rule->str (StringBuilder.) [:p {:font-weight "bold" :color "#000"}])))) + (is (= "@page {\n size: 8.5in 11in;\n margin: 10%;\n @top-right {\n content: \"Page \" counter(page);\n }\n @top-left {\n content: \"Foobar\";\n border: solid red;\n }\n}\n" + (str (css-rule->str + (StringBuilder.) + ["@page" + {:size "8.5in 11in" :margin "10%"} + ["@top-right" + {:content "\"Page \" counter(page)"}] + ["@top-left" + {:content "\"Foobar\"" + :border "solid red"}]]))))) + +(deftest sheet-parsing + (is (= "" + (css->str nil))) + (is (= "" + (css->str []))) + (is (thrown? + java.lang.IllegalArgumentException + (css->str [:body {:color "red"}]))) + (is (= "body {\n}\n" + (css->str [[:body]]))) + (is (= "body {\n}\n" + (css->str [[:body {}]]))) + (is (= "body {\n}\np {\n font-size: 12pt;\n}\n" + (css->str + [[:body {}] + [:p {:font-size "12pt"}]]))) + (is (thrown? + java.lang.IllegalArgumentException + (css->str + [[:body {}] + :p]))) + (is (= "body {\n background-color: white;\n color: black;\n}\np {\n font-size: 12pt;\n}\n" + (css->str + [[:body + {:background-color "white" + :color "black"}] + [:p + {:font-size "12pt"}]]))) + (is (= "body {\n background-color: white;\n color: black;\n}\n@page {\n size: 8.5in 11in;\n margin: 10%;\n @top-right {\n content: \"Page \" counter(page);\n }\n @top-left {\n content: \"Foobar\";\n border: solid red;\n }\n}\n" + (css->str + [[:body + {:background-color "white" + :color "black"}] + ["@page" + {:size "8.5in 11in" :margin "10%"} + ["@top-right" + {:content "\"Page \" counter(page)"}] + ["@top-left" + {:content "\"Foobar\"" + :border "solid red"}]]])))) + +#_(run-tests)