From 0264e46176b292530df17ca0d27ec2b80a32d585 Mon Sep 17 00:00:00 2001 From: Tom Booth Date: Fri, 18 Nov 2016 17:23:37 +0000 Subject: [PATCH] Prototype to test idea --- src/dk/ative/docjure/spreadsheet.clj | 30 ++++++--- src/dk/ative/docjure/spreadsheet/v2.clj | 63 ++++++++++++++++++ test/dk/ative/docjure/spreadsheet/v2_test.clj | 44 ++++++++++++ test/dk/ative/docjure/spreadsheet_test.clj | 2 +- .../docjure/testdata/missing-workbook.xlsx | Bin 0 -> 22485 bytes 5 files changed, 128 insertions(+), 11 deletions(-) create mode 100644 src/dk/ative/docjure/spreadsheet/v2.clj create mode 100644 test/dk/ative/docjure/spreadsheet/v2_test.clj create mode 100644 test/dk/ative/docjure/testdata/missing-workbook.xlsx diff --git a/src/dk/ative/docjure/spreadsheet.clj b/src/dk/ative/docjure/spreadsheet.clj index 6235ebb..0b7072e 100644 --- a/src/dk/ative/docjure/spreadsheet.clj +++ b/src/dk/ative/docjure/spreadsheet.clj @@ -31,24 +31,34 @@ (defmethod read-cell-value Cell/CELL_TYPE_ERROR [^CellValue cv _] (keyword (.name (FormulaError/forInt (.getErrorValue cv))))) -(defmulti read-cell #(when % (.getCellType ^Cell %))) -(defmethod read-cell Cell/CELL_TYPE_BLANK [_] nil) -(defmethod read-cell nil [_] nil) -(defmethod read-cell Cell/CELL_TYPE_STRING [^Cell cell] (.getStringCellValue cell)) -(defmethod read-cell Cell/CELL_TYPE_FORMULA [^Cell cell] - (let [evaluator (.. cell getSheet getWorkbook - getCreationHelper createFormulaEvaluator) +(defmulti configure-evaluator (fn [_ k _] k)) +(defmethod configure-evaluator :ignore-missing-workbooks? [evaluator _ value] + (.setIgnoreMissingWorkbooks evaluator value) + evaluator) +(defmethod configure-evaluator :default [evaluator _ _] + ;; probably should log or error so people know they've got a typo or something + evaluator) + +(defmulti read-cell (fn [cell _] (when cell (.getCellType ^Cell cell)))) +(defmethod read-cell Cell/CELL_TYPE_BLANK [_ _] nil) +(defmethod read-cell nil [_ _] nil) +(defmethod read-cell Cell/CELL_TYPE_STRING [^Cell cell _] (.getStringCellValue cell)) +(defmethod read-cell Cell/CELL_TYPE_FORMULA [^Cell cell options] + (let [evaluator (reduce (fn [evaluator [k v]] (configure-evaluator evaluator k v)) + (.. cell getSheet getWorkbook + getCreationHelper createFormulaEvaluator) + (:evaluator options)) cv (.evaluate evaluator cell)] (if (and (= Cell/CELL_TYPE_NUMERIC (.getCellType cv)) (DateUtil/isCellDateFormatted cell)) (.getDateCellValue cell) (read-cell-value cv false)))) -(defmethod read-cell Cell/CELL_TYPE_BOOLEAN [^Cell cell] (.getBooleanCellValue cell)) -(defmethod read-cell Cell/CELL_TYPE_NUMERIC [^Cell cell] +(defmethod read-cell Cell/CELL_TYPE_BOOLEAN [^Cell cell _] (.getBooleanCellValue cell)) +(defmethod read-cell Cell/CELL_TYPE_NUMERIC [^Cell cell _] (if (DateUtil/isCellDateFormatted cell) (.getDateCellValue cell) (.getNumericCellValue cell))) -(defmethod read-cell Cell/CELL_TYPE_ERROR [^Cell cell] +(defmethod read-cell Cell/CELL_TYPE_ERROR [^Cell cell _] (keyword (.name (FormulaError/forInt (.getErrorCellValue cell))))) diff --git a/src/dk/ative/docjure/spreadsheet/v2.clj b/src/dk/ative/docjure/spreadsheet/v2.clj new file mode 100644 index 0000000..933e866 --- /dev/null +++ b/src/dk/ative/docjure/spreadsheet/v2.clj @@ -0,0 +1,63 @@ +(ns dk.ative.docjure.spreadsheet.v2 + (:require [dk.ative.docjure.spreadsheet :as spreadsheet]) + (:import (org.apache.poi.ss.usermodel Workbook Sheet Cell Row))) + +(defprotocol Context + (select-sheet [context predicate]) + (select-cell [context ref]) + (read-cell [context]) + + (sheet-seq [context]) + (row-seq [context]) + (cell-seq [context]) + + (add-style! [context style]) + + (set-cell-style! [context]) + (set-row-styles! [context])) + +(defrecord POIContext [^Workbook workbook ^Sheet sheet ^Row row ^Cell cell styles options] + Context + (select-sheet [context predicate] + (assert (instance? Workbook workbook) "We need a workbook to be able to select a sheet") + (assoc context :sheet (spreadsheet/select-sheet predicate workbook))) + (select-cell [context ref] + (assert (instance? Sheet sheet) "We require a sheet to be able to select a cell, please use select-sheet") + (assoc context :cell (spreadsheet/select-cell ref sheet))) + (read-cell [_] + (assert (instance? Cell cell) "Please select a cell using select-cell") + (spreadsheet/read-cell cell options)) + + (sheet-seq [context] + (assert (instance? Workbook workbook) "We need a workbook to be able to select a sheet") + (map #(assoc context :sheet %) (spreadsheet/sheet-seq workbook))) + (row-seq [context] + (assert (instance? Sheet sheet) "") + (map #(assoc context :row %) (spreadsheet/row-seq sheet))) + (cell-seq [context] + (assert (or (instance? Sheet sheet) + (instance? Row row)) "") + (map #(assoc context :cell %) (spreadsheet/cell-seq (or row sheet)))) + + (add-style! [context style] + (assert (instance? Workbook workbook) "We need a workbook to be able to select a sheet") + (update context :styles #(conj (or % []) (spreadsheet/create-cell-style! workbook style)))) + + (set-cell-style! [context] + (assert (instance? Cell cell) "Please select a cell using select-cell") + (spreadsheet/set-cell-style! cell (first styles))) + (set-row-styles! [context] + (assert (instance? Row row) "") + (spreadsheet/set-row-styles! row styles))) + +(defn load-workbook [input & [options]] + (map->POIContext {:workbook (spreadsheet/load-workbook input) + :options (or options {})})) + +(defn create-workbook [sheet-name data & [options]] + (map->POIContext {:workbook (spreadsheet/create-workbook sheet-name data) + :options (or options {})})) + +(defmacro with-styles [context styles & body] + `(let [~context (reduce add-style! ~context ~styles)] + ~@body)) diff --git a/test/dk/ative/docjure/spreadsheet/v2_test.clj b/test/dk/ative/docjure/spreadsheet/v2_test.clj new file mode 100644 index 0000000..26ad3e6 --- /dev/null +++ b/test/dk/ative/docjure/spreadsheet/v2_test.clj @@ -0,0 +1,44 @@ +(ns dk.ative.docjure.spreadsheet.v2-test + (:require [clojure.test :refer :all] + [dk.ative.docjure.spreadsheet.v2 :refer :all]) + (:import (org.apache.poi.ss.usermodel IndexedColors))) + +(def config {:simple "test/dk/ative/docjure/testdata/simple.xlsx" + :missing-workbook "test/dk/ative/docjure/testdata/missing-workbook.xlsx"}) + +(deftest load-and-read-simple + (let [workbook (load-workbook (:simple config)) + sheet (first (sheet-seq workbook))] + (is (= 1.0 (read-cell (select-cell sheet "A2")))))) + +(deftest missing-workbooks-causes-explosions + (let [workbook (load-workbook (:missing-workbook config)) + sheet (first (sheet-seq workbook))] + (is (thrown? java.lang.RuntimeException + (read-cell (select-cell sheet "A1")))))) + +(deftest ignore-missing-workbooks-uses-cached-value + (let [workbook (load-workbook (:missing-workbook config) + {:evaluator {:ignore-missing-workbooks? true}}) + sheet (first (sheet-seq workbook))] + (is (= 6.0 (read-cell (select-cell sheet "A1")))))) + +(deftest formatting-cells + (let [workbook (load-workbook (:simple config)) + sheet (first (sheet-seq workbook))] + (with-styles sheet [{:background :yellow}] + (let [cell (select-cell sheet "A1")] + (set-cell-style! cell) + (is (= (.getIndex IndexedColors/YELLOW) (.. (:cell cell) getCellStyle getFillForegroundColor))))))) + +(deftest formatting-rows + (let [workbook (create-workbook "Dummy" [["foo" "bar"] ["data b" "data b"]]) + [header-row data-row] (row-seq (select-sheet workbook "Dummy")) + [a1 b1] (cell-seq header-row) + [a2 b2] (cell-seq data-row)] + (with-styles header-row [{:background :yellow} {:background :red}] + (set-row-styles! header-row)) + (is (= (.getIndex IndexedColors/YELLOW) (.. (:cell a1) getCellStyle getFillForegroundColor))) + (is (= (.getIndex IndexedColors/RED) (.. (:cell b1) getCellStyle getFillForegroundColor))) + (is (not= (.getIndex IndexedColors/YELLOW) (.. (:cell a2) getCellStyle getFillForegroundColor))) + (is (not= (.getIndex IndexedColors/RED) (.. (:cell b2) getCellStyle getFillForegroundColor))))) diff --git a/test/dk/ative/docjure/spreadsheet_test.clj b/test/dk/ative/docjure/spreadsheet_test.clj index bfd5873..d2b1c58 100644 --- a/test/dk/ative/docjure/spreadsheet_test.clj +++ b/test/dk/ative/docjure/spreadsheet_test.clj @@ -499,7 +499,7 @@ (let [wb (create-xls-workbook "Dummy" [["foo"]]) cs (create-cell-style! wb {:border-left :thin :border-right :medium - :border-top :thick + :border-top :thick :border-bottom :thin :left-border-color :red :right-border-color :blue diff --git a/test/dk/ative/docjure/testdata/missing-workbook.xlsx b/test/dk/ative/docjure/testdata/missing-workbook.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..8204cdf032c9e3f3d4edb6ef0e865de446b10a2d GIT binary patch literal 22485 zcmeHvcUTn3)_22zfMm&Wl&pYA5F{fAf{1{UqoCv{APfo)Ip-)D1Oz21QG!GP$w&^8 zvw#di7+{?F8h7{ZU3d4rd-wYN@jmZuAL*Hzs?%MkPxzfvT}MqBf|M<4U5qu{9nG8#FT(6>&pyM!=E?-H!T$d}{tt7YFiz!G z%Ox`Z1I3T$cBt9ybP0$*JLG=Y@S>=4px)&-yyt6Q#8yr!Amx)bv5rLGMg5Jew)vgXjlOZ!_5qhec=%(nS_HjBlDznpE zw_v+tWO*~7xM465K)EZ5jtA=O6sVGfpw1dQnb|t?T|D{wUtRq_tf2oi^}?8@ z8}BZW1#J0jkvYvguMG*Zt)U^`=eP~ME1Xn0E}Rr~zv6gbfz&#SvPtrm`A*-#N}sF9 zg!fyMpn%lH^=FGjX)JaI+=n(_%WGD6I2`FuIdL7*vCe3YYERNIo?TLYEhtr5IR9+X z@CN#7P`+3>8R5>VQ(o5Hb4vo{&Ay+qHA=@NIS#^(`+S7~{iZPnv5$J&w{4!iA2R5MOre>AlH0gE*dCUy4un~F-;>Q# z=VSFgP`%+xG=8@*l}bxS-zLga80NfR6>U<@-r>+#H;2OdT9-PQ2YOYJ>&4Jh1Qo?V~WZ#~L*Gep~+a zzGIJh#lL37y%#N~#FmWT!}6=E^fT+Q92S0Z9D-wu=ks{`f%$k6Y4FtNh%_^4+V?Qd z%=b=rSK~^;Y;Zi7rnl0))#Hj|M#L#Eooy)2$W$fB z7^*iA^uULfcRsIUKG1f=uj$^q*_^MJiql}r7q_eFJCLo!aqvGBqQd9Co_V0*O#%TE zDcA!PqJI{j91RtR$xB3UNyp%qT-@%9SMGXWkxLfR@iO76sgUFrps$Hzn3}k_2BUzkd(W z*)c(*r#A9?ObWg<$$1T!=QtK8-?nzg3K{n<&U}}cCLAIv)RR8Z33S{M$}$=do;e$K*WPoE#` zsUHfkV^8or9m`BtV$pxYF}rBG8cX&=#G>w76PcD*-0o771O!!S8_WeS`)*#=W!q*e z`z%01vJ;kL`H8j8>1j<(zW=*X%eVlntlc~<13ICo16i?4?9Q)-a^;ekanZ&~dK+e0 zud${tdzAQNZ9LBI_6TSQQym{M;@5^%MAK!7P_Jj-D!}Ru=C&j`2>)Vfi7pbBVbZ$3 z3z0wE@?Pev6Lk)&Cre9n+vB@$QKugAOw15>>Z@Dsa+!IK11{rcSz-8E+UILY>#W6! zQK#Opk-klfXt!jZ92JkQ#b$^thp%}+uWk?qK1dsUz4=hEh)#*@dDb4wy;_E+)sk;Z z(}MYW>iypM^;9=iE==9THR|Rd)%TMkx@9-Bba|t3mKq|Pc9`y)EsRvXnyU1~tZvy- zkY)I}d9kt)!!=iBj}FBy=z_HFC`WUBXc&rCWkgAWF89eBJ+>#Q0rxO0Vb`D385p+F z#NK~H8v&OR5k^E;2S(2miP4l-+krPy$TnG*8Nv6qXx*d&4#p(L#)pb^TiJL zN5*{VH)c!phCXk3ue$Is?p+sGNbG9xOR=}%FHBRLgXNCH84ehSt!ggSjTP2a+<$D4 z%6a(U)FW~$<5m>SAIfxhS+0r#DAOA_06_ecOgp={+nPC_7{r{oDVxblWQ{X;$56j% zCQ+kOjwaW0N)Ug0%9ti2Gkw=n$XMloUWOB}fPh7Yd7h`Od)7L~GN7f-#a^pl_>8{A1KoijdJCkJxX;4RC$ z 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 WnldgZ$tQKXhyhzr8SZGDeEWY<{x~uK literal 0 HcmV?d00001