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 0000000..8204cdf Binary files /dev/null and b/test/dk/ative/docjure/testdata/missing-workbook.xlsx differ