S-JIS[2014-10-05/2017-01-27] 変更履歴

Apache POI Workbook

POI3.10でワークブックを扱う方法について。


概要

ExcelのワークブックはWorkbookインターフェースで扱う。

import org.apache.poi.ss.usermodel.Workbook;

新規ワークブックの作成

新しい(空の)ワークブックを作成するには、Workbookインターフェースの具象クラスを直接インスタンス化する。
Workbook具象クラスはExcelファイルのバージョンに応じて異なるものを使用する。

SpreadsheetVersion 対応するファイル拡張子 Workbook
EXCEL97 xls HSSFWorkbook
EXCEL2007 xlsx XSSFWorkbook
import org.apache.poi.ss.SpreadsheetVersion;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
	public static Workbook createEmptyWorkbook(SpreadsheetVersion version) {
		switch (version) {
		case EXCEL97:
			return new HSSFWorkbook();
		case EXCEL2007:
			return new XSSFWorkbook();
		default:
			throw new IllegalArgumentException(version.toString());
		}
	}

SpreadsheetVersionは列挙型である。


ファイルからのワークブック生成

ファイルを読み込んでワークブックを生成するには、WorkbookFactoryを使うのが便利。

import java.io.File;

import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;
		File file = new File("/tmp/test.xls");
		Workbook workbook = WorkbookFactory.create(file);

WorkbookFactory.createメソッドには、Fileクラスを引数にとるものの他に、InputStreamをとるもの等、いくつかのオーバーロードが存在する。


パスワード付きxlsファイルの読み込み

パスワードが設定されたxlsファイルは、WorkbookFactory.create()を呼ぶ前にパスワードを指定しておけば読み込める。[2015-08-27]

import org.apache.poi.EncryptedDocumentException;
import org.apache.poi.hssf.record.crypto.Biff8EncryptionKey;
	public static Workbook open(File xlsFile, String password) throws InvalidFormatException, IOException {
		Biff8EncryptionKey.setCurrentUserPassword(password);

		try {
			return WorkbookFactory.create(xlsFile);
		} catch (EncryptedDocumentException e) {
			e.printStackTrace();
			return null;
		}
	}

Biff8EncryptionKey.setCurrentUserPassword()は(ThreadLocalを用いてスレッド毎にパスワードを保持するので、)MTセーフである。

パスワードが指定されなかったとき、あるいはパスワードが間違っていたときは、WorkbookFactory.create()でEncryptedDocumentExceptionが発生する。
(EncryptedDocumentExceptionはIllegalStateExceptionのサブクラスなので、throws宣言は不要)


パスワードが設定されていないxlsファイルを開く場合は、事前にsetCurrentUserPassword()が呼ばれていようがいまいが関係なく開ける。


大きなサイズのxlsxファイルの読み込み

大きなサイズのxlsxファイルを読み込むとエラーが発生することがある。[2017-01-27]

Exception in thread "main" org.apache.poi.POIXMLException: java.lang.reflect.InvocationTargetException
	at org.apache.poi.POIXMLFactory.createDocumentPart(POIXMLFactory.java:63)
	at org.apache.poi.POIXMLDocumentPart.read(POIXMLDocumentPart.java:625)
	at org.apache.poi.POIXMLDocument.load(POIXMLDocument.java:186)
	at org.apache.poi.xssf.usermodel.XSSFWorkbook.(XSSFWorkbook.java:260)
	at org.apache.poi.ss.usermodel.WorkbookFactory.create(WorkbookFactory.java:263)
	at org.apache.poi.ss.usermodel.WorkbookFactory.create(WorkbookFactory.java:222)
	at org.apache.poi.ss.usermodel.WorkbookFactory.create(WorkbookFactory.java:201)
	at com.example.poi.PoiExample.main(PoiExample.java:18)
Caused by: java.lang.reflect.InvocationTargetException
	at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
	at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
	at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
	at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
	at org.apache.poi.xssf.usermodel.XSSFFactory.createDocumentPart(XSSFFactory.java:56)
	at org.apache.poi.POIXMLFactory.createDocumentPart(POIXMLFactory.java:60)
	... 7 more
Caused by: java.io.IOException: Zip bomb detected! The file would exceed the max. ratio of compressed file size to the size of the expanded data. This may indicate that the file is used to inflate memory usage and thus could pose a security risk. You can adjust this limit via ZipSecureFile.setMinInflateRatio() if you need to work with files which exceed this limit. Counter: 1644884, cis.counter: 16384, ratio: 0.009960580806914044Limits: MIN_INFLATE_RATIO: 0.01
	at org.apache.poi.openxml4j.util.ZipSecureFile$ThresholdInputStream.advance(ZipSecureFile.java:257)
	at org.apache.poi.openxml4j.util.ZipSecureFile$ThresholdInputStream.read(ZipSecureFile.java:214)
	at com.sun.org.apache.xerces.internal.impl.XMLEntityManager$RewindableInputStream.read(XMLEntityManager.java:2919)
〜
	at javax.xml.parsers.DocumentBuilder.parse(DocumentBuilder.java:121)
	at org.apache.poi.util.DocumentHelper.readDocument(DocumentHelper.java:137)
	at org.apache.poi.POIXMLTypeLoader.parse(POIXMLTypeLoader.java:115)
	at org.openxmlformats.schemas.spreadsheetml.x2006.main.StyleSheetDocument$Factory.parse(Unknown Source)
	at org.apache.poi.xssf.model.StylesTable.readFrom(StylesTable.java:203)
	at org.apache.poi.xssf.model.StylesTable.(StylesTable.java:146)
	... 13 more

xlsxファイルはzip圧縮されているのだが、圧縮率や1エントリーのサイズ(最大4GB)をチェックしているらしい。
上の例では、ratioが0.009960580806914044で最小値の0.01より小さいのでエラーになっている。
この最小値は設定することが出来る。

import org.apache.poi.openxml4j.util.ZipSecureFile;
		File file = new File("/tmp/test.xls");

		ZipSecureFile.setMinInflateRatio(0.001);
		Workbook workbook = WorkbookFactory.create(file);

なお、xlsxファイルを読み込む方法は(WorkbookFactory.create(File)以外に)InputStreamを引数にとるWorkbookFactory.create(InputStream)がある。
こちらの方はratioのチェックは行っていないようだ。


ワークブックの保存

ワークブックをファイル(OutputStream)に保存するには、writeメソッドを使う。

import java.io.FileOutputStream;
import java.io.IOException;

import org.apache.poi.ss.usermodel.Workbook;
	public static void write(Workbook workbook, String path) throws IOException {
		try (FileOutputStream fos = new FileOutputStream(path)) {
			workbook.write(fos);
		}
	}

パスワード付きxlsファイルを読み込んで作ったWorkbookインスタンスでも、保存時にはパスワード無しになる。[2015-08-27]


パスワード付きxlsファイルの保存

POIでは、Excel97用のワークブック(実体がHSSFWorkbook)を(読み込み用の)パスワード付きで保存することは出来ない。[2015-08-27]


上書き保存用パスワード(そのパスワードを入力しないと上書き保存できない)であれば、以下のようにしてセットできる。

import org.apache.poi.hssf.usermodel.HSSFWorkbook;
	public static void write(Workbook workbook, File file, String password) throws IOException {
		HSSFWorkbook wb = (HSSFWorkbook) workbook;
		wb.writeProtectWorkbook(password, "hishidama");

		try (FileOutputStream os = new FileOutputStream(file)) {
			workbook.write(os);
		}
	}

writeProtectWorkbook()でパスワードを指定する。
第2引数はユーザー名。不要な場合は空文字列でよい(nullは駄目)。

上書き保存用パスワードを解除したい場合は、unwriteProtectWorkbook()を呼び出す。

		HSSFWorkbook wb = (HSSFWorkbook) workbook;
		wb.unwriteProtectWorkbook(); // 設定されていたパスワード(およびユーザー名)が解除される

※上書き保存用パスワード付きのxlsファイルを開いてWorkbookインスタンスを作った場合、そのパスワードはWorkbook内に保持されている。writeProtectWorkbook()で新たなパスワードを設定することも、unwriteProtectWorkbook()でパスワードを解除することも(無条件で)出来る。パスワードを変更せずにwriteすると、前のパスワードのまま保存される。

※isWriteProtected()で上書き保存用パスワードがかかっているかどうかチェックできるように見えるが、自分でwriteProtectWorkbook()を呼び出してパスワードを設定したときだけしかtrueにならないようだ。


POI目次へ戻る / Excel操作ライブラリーへ戻る / Java目次へ戻る / 技術メモへ戻る
メールの送信先:ひしだま