From c30d0586974a8c114e73a83511f4553dd2bc78a9 Mon Sep 17 00:00:00 2001 From: zackteo Date: Mon, 18 Jan 2021 15:36:34 +0800 Subject: [PATCH 1/7] Add merged cells support with simple tests --- src/zero_one/fxl/read_xlsx.clj | 53 ++++++++++++++++++++++++-------- src/zero_one/fxl/specs.clj | 17 +++++++++- src/zero_one/fxl/write_xlsx.clj | 22 +++++++++++-- test/zero_one/fxl/core_test.clj | 9 ++++-- test/zero_one/fxl/specs_test.clj | 12 ++++++++ 5 files changed, 96 insertions(+), 17 deletions(-) diff --git a/src/zero_one/fxl/read_xlsx.clj b/src/zero_one/fxl/read_xlsx.clj index 17fe6f6..5a8ed3c 100644 --- a/src/zero_one/fxl/read_xlsx.clj +++ b/src/zero_one/fxl/read_xlsx.clj @@ -104,20 +104,49 @@ (mapcat ->seq) (mapcat ->seq)))) -(defn- poi-cell->fxl-cell [workbook poi-cell] - (let [poi-row (.getRow poi-cell)] - {:coord {:row (.getRowNum poi-row) - :col (.getColumnIndex poi-cell) - :sheet (.. poi-row getSheet getSheetName)} - :value (extract-cell-value poi-cell) - :formula (extract-cell-formula poi-cell) - :style (extract-cell-style workbook poi-cell)})) +(defn- poi-cell->fxl-cell [workbook poi-cell index] + (let [testt {:coord {:row (.getRowIndex poi-cell) + :col (.getColumnIndex poi-cell) + :sheet (.. poi-cell getSheet getSheetName)} + :value (extract-cell-value poi-cell) + :formula (extract-cell-formula poi-cell) + :style (extract-cell-style workbook poi-cell)}] + ;; add additional pair of coord for merged cell + (let [{{:keys [row col sheet]} :coord} testt] + (if-let [{:keys [lrow lcol]} (get index [row col sheet])] + (update-in testt [:coord] + assoc :lrow lrow :lcol lcol) + testt)))) + +(defn- extract-merged-cell-index [workbook] + (let [workbook (first workbook) + sheet-number (.getNumberOfSheets workbook) + sheet-names (map #(.getSheetName workbook %) + (range sheet-number)) + merged-cells (->> sheet-names + (map #(.getSheet workbook %)) + (map #(.getMergedRegions %))) + sheet-name->merged-cells (zipmap sheet-names merged-cells)] + (->> sheet-name->merged-cells + (map (fn [[sheet-name merged-cells]] + (->> merged-cells + (map (fn [merged-cell] + [[(.getFirstRow merged-cell) + (.getFirstColumn merged-cell) + sheet-name] + {:lrow (.getLastRow merged-cell) + :lcol (.getLastColumn merged-cell)}])) + (into {})))) + (into {})))) (defn- throwable-read-xlsx! [path] - (let [input-stream (FileInputStream. path) - workbook (XSSFWorkbook. input-stream) - poi-cells (extract-poi-cells workbook) - cells (map #(poi-cell->fxl-cell workbook %) poi-cells)] + (let [input-stream (FileInputStream. path) + workbook (XSSFWorkbook. input-stream) + poi-cells (extract-poi-cells workbook) + merged-cell-index (extract-merged-cell-index [workbook]) + cells (map #(poi-cell->fxl-cell workbook % + merged-cell-index) + poi-cells)] (.close workbook) cells)) diff --git a/src/zero_one/fxl/specs.clj b/src/zero_one/fxl/specs.clj index fd4bdbb..2d5ee71 100644 --- a/src/zero_one/fxl/specs.clj +++ b/src/zero_one/fxl/specs.clj @@ -11,11 +11,26 @@ (def max-cols (int 1e4)) (s/def ::row (s/and nat-int? #(<= % max-rows))) (s/def ::col (s/and nat-int? #(<= % max-cols))) +;; For merged cells +(s/def ::lrow (s/and nat-int? #(<= % max-rows))) +(s/def ::lcol (s/and nat-int? #(<= % max-cols))) (s/def ::sheet string?) -(s/def ::coord + +(s/def ::plain-coord (s/keys :req-un [::row ::col] :opt-un [::sheet])) +(s/def ::merged-coord + (s/and + (s/keys :req-un [::row ::col ::lrow ::lcol] + :opt-un [::sheet]) + #(<= (:row %) (:lrow %)) + #(<= (:col %) (:lcol %)))) + +(s/def ::coord + (s/or ::merged-coord + ::plain-coord)) + ;; Cell Style ;;;; Font Style (s/def ::bold boolean?) diff --git a/src/zero_one/fxl/write_xlsx.clj b/src/zero_one/fxl/write_xlsx.clj index 127d7d5..c798914 100644 --- a/src/zero_one/fxl/write_xlsx.clj +++ b/src/zero_one/fxl/write_xlsx.clj @@ -10,7 +10,8 @@ (:import (java.io FileOutputStream) (org.apache.poi.xssf.usermodel XSSFWorkbook) - (org.apache.poi.ss.usermodel FillPatternType FontUnderline))) + (org.apache.poi.ss.usermodel FillPatternType FontUnderline) + (org.apache.poi.ss.util CellRangeAddress))) ;; Apache POI Navigation (defn- get-or-create-sheet! [cell workbook] @@ -115,6 +116,21 @@ :min-col-sizes (grouped-min-size :col cells) :cell-styles (reduce #(accumulate-style-cache! workbook %1 %2) {} cells)}) +(defn- create-merged-region! [cell sheet] + (let [first-row (-> cell :coord :row) + last-row (-> cell :coord :lrow) + first-col (-> cell :coord :col) + last-col (-> cell :coord :lcol) + cell-range-address (CellRangeAddress. + first-row last-row + first-col last-col)] + (.addMergedRegion sheet cell-range-address))) + +(defn- merged-cell? [cell] + (let [coord (:coord cell)] + (and (contains? coord :lrow) + (contains? coord :lcol)))) + (defn- set-cell-value-and-style! [context workbook cell] (let [sheet (get-or-create-sheet! cell workbook) row (get-or-create-row! cell sheet) @@ -122,7 +138,9 @@ style ((:cell-styles context) (:style cell))] (.setCellValue poi-cell (ensure-settable (:value cell))) (.setCellFormula poi-cell (:formula cell)) - (.setCellStyle poi-cell style))) + (.setCellStyle poi-cell style) + (when (merged-cell? cell) + (create-merged-region! cell sheet)))) (defn- set-row-height! [workbook coord row-size] (let [row-index (:row coord) diff --git a/test/zero_one/fxl/core_test.clj b/test/zero_one/fxl/core_test.clj index d9a2859..8722a7e 100644 --- a/test/zero_one/fxl/core_test.clj +++ b/test/zero_one/fxl/core_test.clj @@ -77,7 +77,9 @@ {:coord {:row 3 :col 0 :sheet "S2"} :value true :style {:row-size 10}} {:coord {:row 3 :col 1 :sheet "S2"} :value false - :style {:col-size 15}}] + :style {:col-size 15}} + {:coord {:row 3 :col 3 :lrow 4 :lcol 4 + :sheet "S2"} :value "merged-cell"}] read-cells (write-then-read-xlsx! write-cells)] (fact "Write and read cells should have the same count" (count read-cells) => (count write-cells)) @@ -118,7 +120,10 @@ (contains? data-formats "@") => true)) (fact "Non-builtin data format should be dropped" (let [data-formats (->> read-cells (map (comp :data-format :style)) set)] - (contains? data-formats "non-builtin") => false))) + (contains? data-formats "non-builtin") => false)) + (fact "Merged cell should be preserved" + (let [coords (->> read-cells (map :coord) set)] + (contains? coords {:row 3 :col 3 :lrow 4 :lcol 4 :sheet "S2"})))) (let [write-cells [{:coord {:row 0 :col 0} :value 12345 :style {}} {:coord {:row 0 :col 0} :value "abc" :style {}}] read-cells (write-then-read-xlsx! write-cells)] diff --git a/test/zero_one/fxl/specs_test.clj b/test/zero_one/fxl/specs_test.clj index 697db35..40ba6e1 100644 --- a/test/zero_one/fxl/specs_test.clj +++ b/test/zero_one/fxl/specs_test.clj @@ -19,11 +19,23 @@ (fs/valid? ::fs/coord {:row 0 :col 1}) => true) (fact "Should allow sheet" (fs/valid? ::fs/coord {:row 0 :col 1 :sheet "ABC"}) => true) + (fact "Should allow merged cell" + (fs/valid? ::fs/coord {:row 0 :col 1 :lrow 1 :lcol 2}) => true) + (fact "Should allow merged cell w sheet" + (fs/valid? ::fs/merged-coord {:row 0 :col 1 :lrow 1 :lcol 2 + :sheet "ABC"}) => true) + (fact "Should not allow negative merged cell area" + (fs/invalid? ::fs/merged-coord {:row 1 :col 1 :lrow 0 :lcol 0 + :sheet "ABC"}) => true) (fact "Should not allow non-string sheet" (fs/invalid? ::fs/coord {:row 0 :col 1 :sheet 123}) => true) (fact "Should not allow negative coords" (fs/invalid? ::fs/coord {:row -1 :col 0}) => true)) + +(#(<= (:col %) (:lcol %)) {:row 1 :col 1 :lrow 0 :lcol 0 + :sheet "ABC"}) + (facts "On fxl data formats" (fact "Should allow example format" (fs/valid? ::fs/data-format "[h]:mm:ss") => true) From 23e20634f9429e896c5f2c7be9aef52c56e61724 Mon Sep 17 00:00:00 2001 From: zackteo Date: Tue, 19 Jan 2021 16:37:07 +0800 Subject: [PATCH 2/7] Refactor merged-cell --- src/zero_one/fxl/read_xlsx.clj | 72 +++++++++++++++++----------------- 1 file changed, 37 insertions(+), 35 deletions(-) diff --git a/src/zero_one/fxl/read_xlsx.clj b/src/zero_one/fxl/read_xlsx.clj index 5a8ed3c..4fde02d 100644 --- a/src/zero_one/fxl/read_xlsx.clj +++ b/src/zero_one/fxl/read_xlsx.clj @@ -104,49 +104,51 @@ (mapcat ->seq) (mapcat ->seq)))) -(defn- poi-cell->fxl-cell [workbook poi-cell index] - (let [testt {:coord {:row (.getRowIndex poi-cell) - :col (.getColumnIndex poi-cell) - :sheet (.. poi-cell getSheet getSheetName)} - :value (extract-cell-value poi-cell) - :formula (extract-cell-formula poi-cell) - :style (extract-cell-style workbook poi-cell)}] - ;; add additional pair of coord for merged cell - (let [{{:keys [row col sheet]} :coord} testt] - (if-let [{:keys [lrow lcol]} (get index [row col sheet])] - (update-in testt [:coord] - assoc :lrow lrow :lcol lcol) - testt)))) +(defn- extract-cell-coord [merged-cell-index poi-cell] + (let [common {:row (.getRowIndex poi-cell) + :col (.getColumnIndex poi-cell) + :sheet (.. poi-cell getSheet getSheetName)}] + (if (contains? merged-cell-index common) + (get merged-cell-index common) + common))) + +(defn- poi-cell->fxl-cell [merged-cell-index workbook poi-cell] + {:coord (extract-cell-coord merged-cell-index poi-cell) + :value (extract-cell-value poi-cell) + :formula (extract-cell-formula poi-cell) + :style (extract-cell-style workbook poi-cell)}) + +(defn- sheet->merged-cell-index [sheet] + (let [merged-cells (.getMergedRegions sheet) + sheet-name (.getSheetName sheet)] + (->> merged-cells + (map #(hash-map {:row (.getFirstRow %) + :col (.getFirstColumn %) + :sheet sheet-name} + {:row (.getFirstRow %) + :col (.getFirstColumn %) + :lrow (.getLastRow %) + :lcol (.getLastColumn %) + :sheet sheet-name})) + (into {})))) (defn- extract-merged-cell-index [workbook] - (let [workbook (first workbook) - sheet-number (.getNumberOfSheets workbook) - sheet-names (map #(.getSheetName workbook %) - (range sheet-number)) - merged-cells (->> sheet-names - (map #(.getSheet workbook %)) - (map #(.getMergedRegions %))) - sheet-name->merged-cells (zipmap sheet-names merged-cells)] - (->> sheet-name->merged-cells - (map (fn [[sheet-name merged-cells]] - (->> merged-cells - (map (fn [merged-cell] - [[(.getFirstRow merged-cell) - (.getFirstColumn merged-cell) - sheet-name] - {:lrow (.getLastRow merged-cell) - :lcol (.getLastColumn merged-cell)}])) - (into {})))) + (let [sheets (->> (range (.getNumberOfSheets workbook)) + (map #(.getSheetName workbook %)) + (map #(.getSheet workbook %)))] + (->> sheets + (map sheet->merged-cell-index) (into {})))) +(defn- extract-fxl-cells [workbook poi-cells] + (let [index (extract-merged-cell-index workbook)] + (map #(poi-cell->fxl-cell index workbook %) poi-cells))) + (defn- throwable-read-xlsx! [path] (let [input-stream (FileInputStream. path) workbook (XSSFWorkbook. input-stream) poi-cells (extract-poi-cells workbook) - merged-cell-index (extract-merged-cell-index [workbook]) - cells (map #(poi-cell->fxl-cell workbook % - merged-cell-index) - poi-cells)] + cells (extract-fxl-cells workbook poi-cells)] (.close workbook) cells)) From d7babc537fbfd6d9ee2c0b9243f16d18d76ca114 Mon Sep 17 00:00:00 2001 From: zackteo Date: Wed, 20 Jan 2021 09:56:45 +0800 Subject: [PATCH 3/7] Refactored tests, remove coverage check for s/keys --- scripts/coverage | 3 ++- src/zero_one/fxl/read_xlsx.clj | 8 ++++---- test/zero_one/fxl/specs_test.clj | 18 ++++++++++-------- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/scripts/coverage b/scripts/coverage index 1851ae2..f21dd50 100755 --- a/scripts/coverage +++ b/scripts/coverage @@ -5,4 +5,5 @@ lein cloverage \ --codecov \ --html \ --runner :midje \ - --ns-exclude-regex "zero-one.fxl.google-sheets" + --ns-exclude-regex "zero-one.fxl.google-sheets" \ + --exclude-call "clojure.spec.alpha/keys" diff --git a/src/zero_one/fxl/read_xlsx.clj b/src/zero_one/fxl/read_xlsx.clj index 4fde02d..1bac393 100644 --- a/src/zero_one/fxl/read_xlsx.clj +++ b/src/zero_one/fxl/read_xlsx.clj @@ -145,10 +145,10 @@ (map #(poi-cell->fxl-cell index workbook %) poi-cells))) (defn- throwable-read-xlsx! [path] - (let [input-stream (FileInputStream. path) - workbook (XSSFWorkbook. input-stream) - poi-cells (extract-poi-cells workbook) - cells (extract-fxl-cells workbook poi-cells)] + (let [input-stream (FileInputStream. path) + workbook (XSSFWorkbook. input-stream) + poi-cells (extract-poi-cells workbook) + cells (extract-fxl-cells workbook poi-cells)] (.close workbook) cells)) diff --git a/test/zero_one/fxl/specs_test.clj b/test/zero_one/fxl/specs_test.clj index 40ba6e1..10984d3 100644 --- a/test/zero_one/fxl/specs_test.clj +++ b/test/zero_one/fxl/specs_test.clj @@ -19,6 +19,12 @@ (fs/valid? ::fs/coord {:row 0 :col 1}) => true) (fact "Should allow sheet" (fs/valid? ::fs/coord {:row 0 :col 1 :sheet "ABC"}) => true) + (fact "Should not allow non-string sheet" + (fs/invalid? ::fs/coord {:row 0 :col 1 :sheet 123}) => true) + (fact "Should not allow negative coords" + (fs/invalid? ::fs/coord {:row -1 :col 0}) => true)) + +(facts "On fxl merged coord" (fact "Should allow merged cell" (fs/valid? ::fs/coord {:row 0 :col 1 :lrow 1 :lcol 2}) => true) (fact "Should allow merged cell w sheet" @@ -27,14 +33,10 @@ (fact "Should not allow negative merged cell area" (fs/invalid? ::fs/merged-coord {:row 1 :col 1 :lrow 0 :lcol 0 :sheet "ABC"}) => true) - (fact "Should not allow non-string sheet" - (fs/invalid? ::fs/coord {:row 0 :col 1 :sheet 123}) => true) - (fact "Should not allow negative coords" - (fs/invalid? ::fs/coord {:row -1 :col 0}) => true)) - - -(#(<= (:col %) (:lcol %)) {:row 1 :col 1 :lrow 0 :lcol 0 - :sheet "ABC"}) + (fact "Should not allow only row and col" + (fs/invalid? ::fs/merged-coord {:row 0 :col 0}) => true) + (fact "Should not allow only lrow and lcol" + (fs/invalid? ::fs/merged-coord {:lrow 0 :lcol 0}) => true)) (facts "On fxl data formats" (fact "Should allow example format" From 1d38a746d071d2a5c51c376521a2fb1c8c4a3988 Mon Sep 17 00:00:00 2001 From: zackteo Date: Wed, 20 Jan 2021 12:54:35 +0800 Subject: [PATCH 4/7] Change merged coord to first/last-row/col --- src/zero_one/fxl/read_xlsx.clj | 16 ++++++++-------- src/zero_one/fxl/specs.clj | 33 ++++++++++++++++++++++++-------- src/zero_one/fxl/write_xlsx.clj | 24 ++++++++++++----------- test/zero_one/fxl/core_test.clj | 6 ++++-- test/zero_one/fxl/specs_test.clj | 13 ++++++++----- 5 files changed, 58 insertions(+), 34 deletions(-) diff --git a/src/zero_one/fxl/read_xlsx.clj b/src/zero_one/fxl/read_xlsx.clj index 1bac393..412c587 100644 --- a/src/zero_one/fxl/read_xlsx.clj +++ b/src/zero_one/fxl/read_xlsx.clj @@ -122,14 +122,14 @@ (let [merged-cells (.getMergedRegions sheet) sheet-name (.getSheetName sheet)] (->> merged-cells - (map #(hash-map {:row (.getFirstRow %) - :col (.getFirstColumn %) - :sheet sheet-name} - {:row (.getFirstRow %) - :col (.getFirstColumn %) - :lrow (.getLastRow %) - :lcol (.getLastColumn %) - :sheet sheet-name})) + (map #(hash-map {:row (.getFirstRow %) + :col (.getFirstColumn %) + :sheet sheet-name} + {:first-row (.getFirstRow %) + :first-col (.getFirstColumn %) + :last-row (.getLastRow %) + :last-col (.getLastColumn %) + :sheet sheet-name})) (into {})))) (defn- extract-merged-cell-index [workbook] diff --git a/src/zero_one/fxl/specs.clj b/src/zero_one/fxl/specs.clj index 2d5ee71..b2c2d30 100644 --- a/src/zero_one/fxl/specs.clj +++ b/src/zero_one/fxl/specs.clj @@ -12,8 +12,10 @@ (s/def ::row (s/and nat-int? #(<= % max-rows))) (s/def ::col (s/and nat-int? #(<= % max-cols))) ;; For merged cells -(s/def ::lrow (s/and nat-int? #(<= % max-rows))) -(s/def ::lcol (s/and nat-int? #(<= % max-cols))) +(s/def ::first-row (s/and nat-int? #(<= % max-rows))) +(s/def ::first-col (s/and nat-int? #(<= % max-cols))) +(s/def ::last-row (s/and nat-int? #(<= % max-rows))) +(s/def ::last-col (s/and nat-int? #(<= % max-cols))) (s/def ::sheet string?) (s/def ::plain-coord @@ -22,14 +24,29 @@ (s/def ::merged-coord (s/and - (s/keys :req-un [::row ::col ::lrow ::lcol] - :opt-un [::sheet]) - #(<= (:row %) (:lrow %)) - #(<= (:col %) (:lcol %)))) + (s/keys :req-un [::first-row ::first-col ::last-row ::last-col] + :opt-un [::sheet]) + #(<= (:first-row %) (:last-row %)) + #(<= (:first-col %) (:last-col %)))) (s/def ::coord - (s/or ::merged-coord - ::plain-coord)) + (s/or :merged ::merged-coord + :plain ::plain-coord)) + +;; (defn- plain-coord? [coord] +;; (and (contains? coord :row) +;; (contains? coord :col))) + +;; (defn- merged-coord? [coord] +;; (and +;; (contains? coord :first-row) +;; (contains? coord :first-col) +;; (contains? coord :last-row) +;; (contains? coord :last-col))) + +;; (s/def ::coord +;; (s/or :merged-coord merged-coord? +;; :plain-coord plain-coord?)) ;; Cell Style ;;;; Font Style diff --git a/src/zero_one/fxl/write_xlsx.clj b/src/zero_one/fxl/write_xlsx.clj index c798914..28a1185 100644 --- a/src/zero_one/fxl/write_xlsx.clj +++ b/src/zero_one/fxl/write_xlsx.clj @@ -20,14 +20,16 @@ (.createSheet workbook sheet-name)))) (defn- get-or-create-row! [cell xl-sheet] - (let [row-index (-> cell :coord :row)] + (let [row-index (or (-> cell :coord :row) + (-> cell :coord :first-row))] (or (.getRow xl-sheet row-index) - (.createRow xl-sheet row-index)))) + (.createRow xl-sheet row-index)))) (defn- get-or-create-cell! [cell xl-row] - (let [col-index (-> cell :coord :col)] + (let [col-index (or (-> cell :coord :col) + (-> cell :coord :first-col))] (or (.getCell xl-row col-index) - (.createCell xl-row col-index)))) + (.createCell xl-row col-index)))) (defn- ensure-settable [value] (if (number? value) @@ -117,10 +119,10 @@ :cell-styles (reduce #(accumulate-style-cache! workbook %1 %2) {} cells)}) (defn- create-merged-region! [cell sheet] - (let [first-row (-> cell :coord :row) - last-row (-> cell :coord :lrow) - first-col (-> cell :coord :col) - last-col (-> cell :coord :lcol) + (let [first-row (-> cell :coord :first-row) + first-col (-> cell :coord :first-col) + last-row (-> cell :coord :last-row) + last-col (-> cell :coord :last-col) cell-range-address (CellRangeAddress. first-row last-row first-col last-col)] @@ -128,8 +130,8 @@ (defn- merged-cell? [cell] (let [coord (:coord cell)] - (and (contains? coord :lrow) - (contains? coord :lcol)))) + (and (contains? coord :last-row) + (contains? coord :last-col)))) (defn- set-cell-value-and-style! [context workbook cell] (let [sheet (get-or-create-sheet! cell workbook) @@ -143,7 +145,7 @@ (create-merged-region! cell sheet)))) (defn- set-row-height! [workbook coord row-size] - (let [row-index (:row coord) + (let [row-index (or (:row coord) (:first-row coord)) sheet (.getSheet workbook (:sheet coord)) row (.getRow sheet row-index)] (.setHeightInPoints row (float row-size)))) diff --git a/test/zero_one/fxl/core_test.clj b/test/zero_one/fxl/core_test.clj index 8722a7e..6499ada 100644 --- a/test/zero_one/fxl/core_test.clj +++ b/test/zero_one/fxl/core_test.clj @@ -78,7 +78,8 @@ :style {:row-size 10}} {:coord {:row 3 :col 1 :sheet "S2"} :value false :style {:col-size 15}} - {:coord {:row 3 :col 3 :lrow 4 :lcol 4 + {:coord {:first-row 3 :first-col 3 + :last-row 4 :last-col 4 :sheet "S2"} :value "merged-cell"}] read-cells (write-then-read-xlsx! write-cells)] (fact "Write and read cells should have the same count" @@ -123,7 +124,8 @@ (contains? data-formats "non-builtin") => false)) (fact "Merged cell should be preserved" (let [coords (->> read-cells (map :coord) set)] - (contains? coords {:row 3 :col 3 :lrow 4 :lcol 4 :sheet "S2"})))) + (contains? coords {:first-row 3 :first-col 3 + :last-row 4 :last-col 4 :sheet "S2"})))) (let [write-cells [{:coord {:row 0 :col 0} :value 12345 :style {}} {:coord {:row 0 :col 0} :value "abc" :style {}}] read-cells (write-then-read-xlsx! write-cells)] diff --git a/test/zero_one/fxl/specs_test.clj b/test/zero_one/fxl/specs_test.clj index 10984d3..a27f2b6 100644 --- a/test/zero_one/fxl/specs_test.clj +++ b/test/zero_one/fxl/specs_test.clj @@ -26,17 +26,20 @@ (facts "On fxl merged coord" (fact "Should allow merged cell" - (fs/valid? ::fs/coord {:row 0 :col 1 :lrow 1 :lcol 2}) => true) + (fs/valid? ::fs/merged-coord {:first-row 0 :first-col 1 + :last-row 1 :last-col 2}) => true) (fact "Should allow merged cell w sheet" - (fs/valid? ::fs/merged-coord {:row 0 :col 1 :lrow 1 :lcol 2 + (fs/valid? ::fs/merged-coord {:first-row 0 :first-col 1 + :last-row 1 :last-col 2 :sheet "ABC"}) => true) (fact "Should not allow negative merged cell area" - (fs/invalid? ::fs/merged-coord {:row 1 :col 1 :lrow 0 :lcol 0 + (fs/invalid? ::fs/merged-coord {:first-row 1 :first-col 1 + :last-row 0 :last-col 0 :sheet "ABC"}) => true) (fact "Should not allow only row and col" - (fs/invalid? ::fs/merged-coord {:row 0 :col 0}) => true) + (fs/invalid? ::fs/merged-coord {:first-row 0 :first-col 0}) => true) (fact "Should not allow only lrow and lcol" - (fs/invalid? ::fs/merged-coord {:lrow 0 :lcol 0}) => true)) + (fs/invalid? ::fs/merged-coord {:last-row 0 :last-col 0}) => true)) (facts "On fxl data formats" (fact "Should allow example format" From 76f688de96795cd633e169ee4f422e6a54775766 Mon Sep 17 00:00:00 2001 From: zackteo Date: Wed, 20 Jan 2021 12:57:18 +0800 Subject: [PATCH 5/7] Alignment --- src/zero_one/fxl/write_xlsx.clj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/zero_one/fxl/write_xlsx.clj b/src/zero_one/fxl/write_xlsx.clj index 28a1185..f4b5036 100644 --- a/src/zero_one/fxl/write_xlsx.clj +++ b/src/zero_one/fxl/write_xlsx.clj @@ -21,15 +21,15 @@ (defn- get-or-create-row! [cell xl-sheet] (let [row-index (or (-> cell :coord :row) - (-> cell :coord :first-row))] + (-> cell :coord :first-row))] (or (.getRow xl-sheet row-index) - (.createRow xl-sheet row-index)))) + (.createRow xl-sheet row-index)))) (defn- get-or-create-cell! [cell xl-row] (let [col-index (or (-> cell :coord :col) - (-> cell :coord :first-col))] + (-> cell :coord :first-col))] (or (.getCell xl-row col-index) - (.createCell xl-row col-index)))) + (.createCell xl-row col-index)))) (defn- ensure-settable [value] (if (number? value) From ebb97171f6f12593fce987f4c5717b4a48b2263f Mon Sep 17 00:00:00 2001 From: zackteo Date: Wed, 20 Jan 2021 12:58:43 +0800 Subject: [PATCH 6/7] Remove unused comments --- src/zero_one/fxl/specs.clj | 25 +++++-------------------- 1 file changed, 5 insertions(+), 20 deletions(-) diff --git a/src/zero_one/fxl/specs.clj b/src/zero_one/fxl/specs.clj index b2c2d30..503386c 100644 --- a/src/zero_one/fxl/specs.clj +++ b/src/zero_one/fxl/specs.clj @@ -24,29 +24,14 @@ (s/def ::merged-coord (s/and - (s/keys :req-un [::first-row ::first-col ::last-row ::last-col] - :opt-un [::sheet]) - #(<= (:first-row %) (:last-row %)) - #(<= (:first-col %) (:last-col %)))) + (s/keys :req-un [::first-row ::first-col ::last-row ::last-col] + :opt-un [::sheet]) + #(<= (:first-row %) (:last-row %)) + #(<= (:first-col %) (:last-col %)))) (s/def ::coord (s/or :merged ::merged-coord - :plain ::plain-coord)) - -;; (defn- plain-coord? [coord] -;; (and (contains? coord :row) -;; (contains? coord :col))) - -;; (defn- merged-coord? [coord] -;; (and -;; (contains? coord :first-row) -;; (contains? coord :first-col) -;; (contains? coord :last-row) -;; (contains? coord :last-col))) - -;; (s/def ::coord -;; (s/or :merged-coord merged-coord? -;; :plain-coord plain-coord?)) + :plain ::plain-coord)) ;; Cell Style ;;;; Font Style From 75eb635139c606754f038b35507eb7a9c2d48473 Mon Sep 17 00:00:00 2001 From: zackteo Date: Sat, 23 Jan 2021 10:16:15 +0800 Subject: [PATCH 7/7] Temp implementation of util functions --- src/zero_one/fxl/core.clj | 38 +++++++++++++++++++++++++----- src/zero_one/fxl/write_xlsx.clj | 2 +- test/zero_one/fxl/helpers_test.clj | 4 ++-- 3 files changed, 35 insertions(+), 9 deletions(-) diff --git a/src/zero_one/fxl/core.clj b/src/zero_one/fxl/core.clj index 9577112..9edeed5 100644 --- a/src/zero_one/fxl/core.clj +++ b/src/zero_one/fxl/core.clj @@ -10,16 +10,23 @@ ;; Utility Functions (defn ->cell [maybe-cell] - (merge defaults/cell maybe-cell)) + (list + (merge defaults/cell maybe-cell))) (defn max-col [cells] (->> cells - (map (comp :col :coord)) + (map + (juxt (comp :col :coord) + (comp :last-col :coord))) + (#(filter some? (flatten %))) (apply max -1))) (defn max-row [cells] (->> cells - (map (comp :row :coord)) + (map + (juxt (comp :row :coord) + (comp :last-row :coord))) + (#(filter some? (flatten %))) (apply max -1))) (defn zip-with-index [coll] @@ -82,11 +89,30 @@ (vector k v))))))) ;; Helper Functions: Relative Coords -(defn- shift-cell [dir shift cell] +(def merged-cell? write-xlsx/merged-cell?) + +(defn- shift-plain-cell [dir shift cell] (let [old-index (get-in cell [:coord dir]) new-index (max 0 (+ old-index shift))] (assoc-in cell [:coord dir] new-index))) +(defn- shift-merged-cell [dir shift cell] + (let [first-key (keyword (str "first-" (symbol dir))) + last-key (keyword (str "last-" (symbol dir))) + old-index1 (get-in cell [:coord first-key]) + old-index2 (get-in cell [:coord last-key]) + new-index1 (max 0 (+ old-index1 shift)) + new-index2 (max 0 (+ old-index2 shift))] + ;;TODO: rename and do i need max? + (-> cell + (assoc-in [:coord first-key] new-index1) + (assoc-in [:coord last-key] new-index2)))) + +(defn- shift-cell [dir shift cell] + (if (merged-cell? cell) + (shift-merged-cell dir shift cell) + (shift-plain-cell dir shift cell))) + (defn shift-right [shift cell] (shift-cell :col shift cell)) @@ -114,7 +140,7 @@ ([shift cells] (concat-right cells - [(->cell {:coord {:row 0 :col (dec shift)} :style {}})]))) + (->cell {:coord {:row 0 :col (dec shift)} :style {}})))) (defn concat-below ([] nil) @@ -131,7 +157,7 @@ ([shift cells] (concat-below cells - [(->cell {:coord {:row (dec shift) :col 0} :style {}})]))) + (->cell {:coord {:row (dec shift) :col 0} :style {}})))) (defn pad-table [cells] (let [coords (->> cells (map :coord) set) diff --git a/src/zero_one/fxl/write_xlsx.clj b/src/zero_one/fxl/write_xlsx.clj index f4b5036..e1285e9 100644 --- a/src/zero_one/fxl/write_xlsx.clj +++ b/src/zero_one/fxl/write_xlsx.clj @@ -128,7 +128,7 @@ first-col last-col)] (.addMergedRegion sheet cell-range-address))) -(defn- merged-cell? [cell] +(defn merged-cell? [cell] (let [coord (:coord cell)] (and (contains? coord :last-row) (contains? coord :last-col)))) diff --git a/test/zero_one/fxl/helpers_test.clj b/test/zero_one/fxl/helpers_test.clj index 301b320..28857c2 100644 --- a/test/zero_one/fxl/helpers_test.clj +++ b/test/zero_one/fxl/helpers_test.clj @@ -34,8 +34,8 @@ (:coord (fxl/shift-up (- 3) cell)) => {:row 8 :col 5}))) (facts "On concat functions" - (let [cells (map fxl/->cell [{:value "abc" :coord {:row 0 :col 0}} - {:value "xyz" :coord {:row 4 :col 4}}])] + (let [cells (flatten (map fxl/->cell [{:value "abc" :coord {:row 0 :col 0}} + {:value "xyz" :coord {:row 4 :col 4}}]))] (fact "Correct fxl/concat-right" (fxl/concat-right) => nil (fxl/concat-right cells) => cells