ラベル ApachePOI の投稿を表示しています。 すべての投稿を表示
ラベル ApachePOI の投稿を表示しています。 すべての投稿を表示

2013年12月21日土曜日

ABCLでApache POIを使う

ABCLはJVM上で動作するCommon Lisp処理系なので、Javaのライブラリが利用できます。

(require :abcl-contrib)
(require :abcl-asdf)

(asdf:defsystem apache-poi
  :components ((:mvn "org.apache.poi/poi/3.8")
               (:mvn "org.apache.poi/poi-ooxml/3.8")))

(asdf:load-system 'apache-poi)

(defpackage :test-poi
  (:use :cl :jss))

(in-package :test-poi)

(defun create-9x9 (path)
  (let* ((wb (new 'xssfworkbook))
         (sh (#"createSheet" wb)))
    (dotimes (i 9)
      (#"createRow" sh i))
    (dotimes (i 9)
      (dotimes (j 9)
        (#"setCellValue" (#"createCell" (#"getRow" sh j) i)
                         (format nil "~A" (* (1+ i) (1+ j))))))
    (#"write" wb (new 'fileoutputstream path))))

(create-9x9 "test.xlsx")

2012年11月29日木曜日

Clojure+Apache POI+Event API

Apache POIで普段使うのは'User API'というAPIのようですが、 このAPIを利用して大きなファイルを扱おうとすると大量のメモリを必要とし、OutOfMemoryが発生します。

この問題を回避するには、

  • ヒープ領域を増やして実行する
  • Event APIを使う
  • Streaming User APIを使う
  • xlsxファイルを解凍して中身のxmlファイルを処理する

といった解決方法があるようです。

ためしに、Clojure+Apache POIでEvent APIを利用したコードを書いてみます。
ぱっと見た限りではxlsxファイルを解凍して処理する手間の一部を省いてくれているだけのようです。

;; project.clj
(defproject poixml "0.1.0-SNAPSHOT"
  :description "dummy"
  :url "dummy"
  :license {:name "dummy"}
  :dependencies [[org.clojure/clojure "1.4.0"]
                 [org.apache.poi/poi "3.8"]
                 [org.apache.poi/poi-ooxml "3.8"]
                 [org.clojure/data.xml "0.0.6"]]
  :main poixml.core)
;; src/poixml/core.clj
(ns poixml.core
  (:gen-class))

;; @see http://poi.apache.org/spreadsheet/how-to.html

(require 'clojure.data.xml)

(import
 '(org.apache.poi.xssf.eventusermodel XSSFReader)
 '(org.apache.poi.xssf.model SharedStringsTable)
 '(org.apache.poi.xssf.usermodel XSSFSheet
                                 XSSFWorkbook
                                 XSSFRow
                                 XSSFCell
                                 XSSFRichTextString))

(defn call-with-cell-value [path f]
  (let [reader (XSSFReader. (org.apache.poi.openxml4j.opc.Package/open path))
        sst (.getSharedStringsTable reader)]
    (with-open [istream (.next (.getSheetsData reader))]
      (doseq [r (:content (first
                           (filter #(= :sheetData (:tag %))
                                   (:content (clojure.data.xml/parse istream)))))]
        (when (= :row (:tag r))
          (doseq [[col cell] (map vector (iterate inc 1) (:content r))]
                 (let [val (first (filter #(= :v (:tag %)) (:content cell)))]
                   (f (Integer/parseInt (:r (:attrs r))) ; 行番号
                      col ; 列番号
                      (if (= "s" (:t (:attrs cell)))
                          ;; 文字列はSharedStringsTableにある
                          (-> (.getEntryAt sst (bigint (first (:content val))))
                              XSSFRichTextString.
                              .toString)
                          (first (:content val)))))))))))

;; --------------------------------------------------
;; 適当にxlsxファイルを作成
(defn create-3x3 [path]
  (let [wb (XSSFWorkbook.)
        sh (.createSheet wb)]
    (dorun
     (for [y (range 3)]
       (.createRow sh y)))
    (dorun
     (for [x (range 3) y (range 3)]
       (-> (.createCell (.getRow sh y) x)
           (.setCellValue (str (* (inc x) (inc y)))))))
    (with-open [out (java.io.FileOutputStream. path)]
      (.write wb out))))

;; xlsxファイルの各セルを表示
(defn print-cells [path]
  (call-with-cell-value path
    (fn [row col val]
        (printf "[%d,%d] %s\n" row col val)
        (flush))))

;; 引数に作成するファイル名を指定
(defn -main
  [& args]
  (when (= (count args) 1)
    (let [path (first args)]
      (create-3x3 path)
      (print-cells path))))
> lein run test.xlsx 
Compiling poixml.core
[1,1] 1
[1,2] 2
[1,3] 3
[2,1] 2
[2,2] 4
[2,3] 6
[3,1] 3
[3,2] 6
[3,3] 9

2012年9月10日月曜日

Clojure+leiningen+Apache POI

いつか業務でこっそり使うことを夢見てExcelをいじってみます。

1. leiningenでプロジェクト作成
 lein new poitest

2. 作成されたプロジェクトのディレクトリのproject.cljを編集
(defproject poitest "0.1.0-SNAPSHOT"
  :description "Apache POI Test"
  :dependencies [[org.clojure/clojure "1.4.0"]
                 [org.apache.poi/poi "3.8"]
                 [org.apache.poi/poi-ooxml "3.8"]]
  :main poitest.core)

3. 依存解決
 lein deps

4. コードを書く
(ns poitest.core
  (:gen-class))

(import '(org.apache.poi.xssf.usermodel
          XSSFSheet
          XSSFWorkbook
          XSSFRow
          XSSFCell)
        '(org.apache.poi.ss.usermodel
          WorkbookFactory))

(import '(java.io
          FileInputStream
          FileOutputStream))

(defn load-workbook [path]
  (-> path FileInputStream. WorkbookFactory/create))

(defn rows [^XSSFSheet  sheet]
  (let [nrows (.getPhysicalNumberOfRows sheet)]
    (letfn [(f [i]
              (if (<= nrows i)
                nil
                (cons (.getRow sheet i)
                      (lazy-seq (f (inc i))))))]
      (f 0))))

(defn create-9x9 [path]
  (let [wb (XSSFWorkbook.)
        sh (.createSheet wb)]
    (dorun
     (for [y (range 9)]
       (.createRow sh y)))
    (dorun
     (for [x (range 9) y (range 9)]
       (-> (.createCell (.getRow sh y) x)
           (.setCellValue (str (* (inc x) (inc y)))))))
    (.write wb (FileOutputStream. path))))


(defn -main
  "9x9を書き込んだExcelファイルを作成->ファイルを読み込み3列目の要素を表示"
  [& args]
  (create-9x9 "test.xlsx")
  (let [wb (load-workbook "test.xlsx")
        sh (.getSheetAt wb 0)]
    (dorun 
     (for [r (rows sh)]
       (println (-> (.getCell r 2) .getStringCellValue))))))

5. 実効したりコンパイルしたり
 lein run
 lein uberjar
※ 9/22 追記
こちらのほうがシンプルそう
(defn rows [sheet]
  (keep #(.getRow sheet %)
        (range
         (.getFirstRowNum sheet)
         (inc (.getLastRowNum sheet)))))