ColdFusionカフェテリア
SAMURAIZ
| ↑一覧へ戻る |
ColdFusion実験室

第7回: またまた Excel 処理 (第3回・第6回の記事もご覧ください)

作成日: 2019年11月
最終更新日: 2019年11月

■このページ(ColdFusion 実験室)で紹介する内容について

ColdFusion 実験室で公開している情報は、ColdFusionの標準の機能では無いため動作の保証外(未サポート)となります。下記の事項も含めてそれらを予めご認識頂いた上で、参考にされるかどうかをご判断下さい。

@ 意外と知られていないautosize属性

予め、テンプレートとなるxls/xlsxファイルを読み込み、セルに値を追加して別ファイルに書き出すような処理をしていると、設定していた列幅が勝手に変更されてしまったという経験をされた方もいらっしゃると思います。

また、以前のバージョンで、この動作は経験はしていたけど仕様としてあきらめていた方もいらっしゃるのではないでしょうか。実は、ColdFusion 11以降の<cfspreadsheet action="write" ..>タグやSpreadSheetWrite関数に autosize というパラメーターが追加されています。下位互換性のため、デフォルトはtrue(リサイズが有効)になっていますが、これをfalseに変更すれば、列幅を勝手に変更することが無くなります。さらに、下記のメーカーサイトのブログの投稿でも紹介がされている通り、列の展開やリサイズ処理を省くことができるため、処理時間を節約して貴重なリソースの節約にもつながるとのことです。
https://coldfusion.adobe.com/2014/03/performance-improvements-in-spreadsheet/

下位互換性のためにデフォルトがtrueになっていますが、推奨はfalseです!特にxlsxファイルに大量の行を書き出すような処理の場合、処理時間が下記のように異なります。

20個の列に、ランダムに数字と文字列を指定する
  autosize指定しなかった(デフォルトのtrue)の場合 autosizeをfalseにした場合
100行 4544 ms 140 ms
1000行 46596 ms 301 ms
10000行 479387 ms 4852 ms

上記はColdFusion 2016 Update 13で実行した結果です。上記の結果からもautosizeをfalseに設定することをお勧めします(ただしxlsxファイルの処理は多くのメモリを消費しますので、大量の行を書き出す際にはメモリ不足のエラーが発生しないように「最大JVMヒープサイズ」を割り当てることをお勧めします)。

ちなみに、上記のブログ記事にも記載されていますが、SpreadSheetWrite関数を使用すると、autosize="true | false" 以外に、特定の列を”配列”で指定することもできます(測定は試していないです)。上記のブログ記事をご確認ください。

A ヘッダー・フッターに現在の日付(&[日付])やページ(&[ページ番号]や&[総ページ数])はどう指定する?

Microsoft Wordなどのドキュメントファイルでページの「ヘッダー・フッター」を使用する機会が多いと思いますが、Microsoft Excelなどのスプレッドシートファイルでも同様に「ヘッダー・フッター」を指定することができます。ColdFusionのSpreadsheetSetHeader/SpreadsheetSetFooter関数がそれに該当し、ヘッダー・フッターの「左側中央部右側」にそれぞれ文字列を指定することができます。

Microsoft Officeのサイト(ワークシートのヘッダーとフッター

さらに、ヘッダーやフッターには、文字列以外に現在の日付やページ番号といった「ヘッダー/フッター要素」があります。上記のMicrosoft Officeページの「ワークシートに組み込みのヘッダー要素やフッター要素を追加する」の項目を展開すると解説が掲載されています。

すべての要素には対応していませんが、POIのライブラリでもそれら要素を指定することができます。詳しくはColdFusionのマニュアルに記載されていないので、下記のPOIのマニュアルを確認してください。
https://poi.apache.org/apidocs/dev/org/apache/poi/xssf/usermodel/extensions/XSSFHeaderFooter.html

// ヘッダー右部に日付(&D)、フッター中央部に現在のページ(&P)と
// 総ページ(&N)を太字(&B)で表示  
SpreadsheetSetHeader(mySheet,"Left Header","Center Header","&D");
SpreadsheetSetFooter(mySheet,"Left Footer","&B &P / &N","Right Footer");

 

BgetWorkBook()を活用〜スタイルの定義をPOIで行う(改)(一部注意)〜

第3回の実験室で、スタイルの定義をPOIで行うサンプルを紹介しました。この時はPOIのライブラリの該当するものをCreateObjectで読み込んで使用していくというやり方でしたが、第6回で紹介したgetWorkBook()メソッドを使用することで、この処理をより簡単に、なるべくColdFusionのタグや関数を使用するやり方に近づけることができます。

ColdFusion 11でSpreadsheetFormatCellRange関数などが指定範囲で一つのスタイルとなるように改善されて、このプログラムの必要性も下がったこともあり、なかなかgetWorkBookを使用した場合のプログラムを紹介できなかったのですが、せっかくなので、こういったやり方もあるという比較として参考にしてもらえると幸いです。

第3回実験室のプログラム(実験A:最初のスタイルのセットもPOIの処理で行う)

<cfscript>
//対象のExcelファイル(xlsファイル)のファイル・パスを指定
sourceXLSFile = ExpandPath('sample.xls');
//編集したExcelファイルの保存先(ファイル・フォルダ)を指定
outputXLSFile = ExpandPath('poi2_output.xls');
//対象のシート番号を指定
sheetNum = 1;
//スタイルを取得するセル位置(rowとcolumn)を指定
rowNum = 1;
columnNum = 1;
//対象のセル範囲を指定
startRow = 1;
startColumn = 1;
endRow = 1000;
endColumn = 20;

//createobject()でPOIオブジェクトを呼び出す
f = createObject("java", "java.io.File").init(sourceXLSFile);
objTempfile = createObject("java", "java.io.FileInputStream").init(f);
objXLS = createObject("java", "org.apache.poi.hssf.usermodel.HSSFWorkbook");
objXLS.init(objTempfile);

objXLS.setActiveSheet(sheetNum-1);
sheet = objXLS.getSheetAt(sheetNum-1); //sheet名で選択する場合は getSheet()

//スタイルを作成
cellStyle = objXLS.createCellStyle();
cellStyle.setAlignment(cellStyle.ALIGN_CENTER);
cellStyle.setVerticalAlignment(cellStyle.VERTICAL_CENTER);
cellStyle.setBorderTop(cellStyle.BORDER_THIN);
cellStyle.setBorderBottom(cellStyle.BORDER_THIN);
cellStyle.setBorderLeft(cellStyle.BORDER_THIN);
cellStyle.setBorderRight(cellStyle.BORDER_THIN);
  //フォントスタイルを作成
  fontStyle = objXLS.createFont();
  fontStyle.setFontName("MS ゴシック");
  fontStyle.setBoldweight(fontStyle.BOLDWEIGHT_BOLD);
//スタイルにフォントをセット
cellStyle.setFont(fontStyle);

//列ループ→行ループとループを入れ子にして対象セルにスタイルをセット(setCellStyle)
for(idxR = startRow-1; idxR LT endRow; idxR = idxR+1){
	setRow = sheet.getRow(idxR);
	if(not IsDefined("setRow")){
		setRow = sheet.createRow(idxR);
	}
	for(idxC = startColumn-1; idxC LT endColumn; idxC = idxC+1){
		setRowColumn = setRow.getCell(idxC);
		if(not IsDefined("setRowColumn")){
			setRowColumn = setRow.createCell(idxC);
		}
		setRowColumn.setCellStyle(cellStyle);
	}
}

//Excelファイルを書き出し
objFileOutputStream = CreateObject("java", "java.io.FileOutputStream").Init(JavaCast("string", outputXLSFile));
objXLS.Write(objFileOutputStream);

objFileOutputStream.Close();
objTempfile.Close();
</cfscript>

 

getWorkBook()を使用したサンプル

<cfscript>
//対象のExcelファイル(xlsファイル)のファイル・パスを指定
sourceXLSFile = ExpandPath('sample.xls');
//編集したExcelファイルの保存先(ファイル・フォルダ)を指定
outputXLSFile = ExpandPath('poi2_output.xls');
//対象のシート番号を指定
sheetNum = 1;
//スタイルを取得するセル位置(rowとcolumn)を指定
rowNum = 1;
columnNum = 1;
//対象のセル範囲を指定
startRow = 1;
startColumn = 1;
endRow = 1000;
endColumn = 20;

	SpreadsheetObj=SpreadSheetRead(sourceXLSFile);

	//getWorkBook()
	objXLS=SpreadsheetObj.getWorkBook();
	SpreadsheetSetActiveSheetNumber(SpreadsheetObj, sheetNum);

	sheet = objXLS.getSheetAt(sheetNum-1); //sheet名で選択する場合は getSheet()

	//スタイルを作成
	cellStyle = objXLS.createCellStyle();
	cellStyle.setAlignment(cellStyle.ALIGN_CENTER);
	cellStyle.setVerticalAlignment(cellStyle.VERTICAL_CENTER);
	cellStyle.setBorderTop(cellStyle.BORDER_THIN);
	cellStyle.setBorderBottom(cellStyle.BORDER_THIN);
	cellStyle.setBorderLeft(cellStyle.BORDER_THIN);
	cellStyle.setBorderRight(cellStyle.BORDER_THIN);
	  //フォントスタイルを作成
	  fontStyle = objXLS.createFont();
	  fontStyle.setFontName("MS ゴシック");
	  fontStyle.setBoldweight(fontStyle.BOLDWEIGHT_BOLD);
	//スタイルにフォントをセット
	cellStyle.setFont(fontStyle);

	//列ループ→行ループとループを入れ子にして対象セルにスタイルをセット(setCellStyle)
	for(idxR = startRow-1; idxR LT endRow; idxR = idxR+1){
		setRow = sheet.getRow(idxR);
		if(not IsDefined("setRow")){
			setRow = sheet.createRow(idxR);
		}
		for(idxC = startColumn-1; idxC LT endColumn; idxC = idxC+1){
			setRowColumn = setRow.getCell(idxC);
			if(not IsDefined("setRowColumn")){
				setRowColumn = setRow.createCell(idxC);
			}
			setRowColumn.setCellStyle(cellStyle);
		}
	}

	//Excelファイルを書き出し
	SpreadsheetWrite(SpreadsheetObj,outputXLSFile,"",true,false);
    
</cfscript>

どうでしょうか?CreateObject関数を使用しなくても、ColdFusionの関数で読み込んだスプレッドシートオブジェクトからgetWorkBookを使ってWorkbookオブジェクトを取得して操作しました。変更前のコードだと、xlsとxlsxとで読み込むクラスが異なる(HSSF, XSSF)などを考慮する必要がありましたが、読み込みや書き込みはColdFusionの関数に任せることができます。

このgetWorkBookを使ったテクニックをあと2つご紹介する前に、Bのタイトルにもあった、一部注意について説明します。実は、上記のプログラムはColdFusion 2018 では動作しません。ColdFusion 2018 Update 6で実行すると、cellStyle.ALIGN_CENTERVERTICAL_CENTERBORDER_THINの値が存在しないエラーとなります。この部分、下記のページの記載にもありますが、Apache POI 3.17以降に、それらのフィールドは廃止(remove)されてしまいました。https://stackoverflow.com/questions/51426037/cellstyle-setalignmenthorizontalalignment-center-error

それに代わるプロパティも用意されていますが、用途に応じて細分化されているので、前よりも簡単ではありません。そこで、次のCで紹介するプログラムでちょっと複雑になりますが、ColdFusion2018で動作するサンプルで動かします。

 

CgetWorkBook()を活用〜用紙サイズや印刷の向きを指定する(一部制限あり)〜

ColdFusionのSpreadSheet系のタグや関数には、スプレッドシートの用紙サイズや印刷の向きを指定する機能が用意されていません。そこで、それらの指定をPOIで実装されている機能を使って行います。

Bで紹介したコードにさらに手を入れてみますが、あとで記述する事情によって、今回はxlsx(Office2007形式)のファイルを使った例になります。また、こちらのプログラムはColdFusion 2018で動作するように、セルのスタイルのパラメーターの指定方法が変更されています。

<cfscript>
//対象のExcelファイル(xlsファイル)のファイル・パスを指定
sourceXLSFile = ExpandPath('sample.xlsx');
//編集したExcelファイルの保存先(ファイル・フォルダ)を指定
outputXLSFile = ExpandPath('poi3_output.xlsx');
//対象のシート番号を指定
sheetNum = 1;
//スタイルを取得するセル位置(rowとcolumn)を指定
rowNum = 1;
columnNum = 1;
//対象のセル範囲を指定
startRow = 1;
startColumn = 1;
endRow = 1000;
endColumn = 20;

	//Cell Style, Border Style 指定
	oHorizontalAlignment = createObject ( "java" , "org.apache.poi.ss.usermodel.HorizontalAlignment");
	oVerticalAlignment = createObject ( "java" , "org.apache.poi.ss.usermodel.VerticalAlignment");
	oBorderStyle= createObject ( "java" , "org.apache.poi.ss.usermodel.BorderStyle");

	SpreadsheetObj=SpreadSheetRead(sourceXLSFile);

	//getWorkBook()
	objXLS=SpreadsheetObj.getWorkBook();
	SpreadsheetSetActiveSheetNumber(SpreadsheetObj, sheetNum);

	sheet = objXLS.getSheetAt(sheetNum-1);
    objPrint=sheet.getPrintSetup();
    objPrint.setLandscape(true); //印刷の向きを横向きにすr
    objPrint.setPaperSize(objPrint.A3_PAPERSIZE); //A3用紙を設定する

	//スタイルを作成
	cellStyle = objXLS.createCellStyle();
	cellStyle.setAlignment(oHorizontalAlignment.CENTER);
	cellStyle.setVerticalAlignment(oVerticalAlignment.CENTER);
	cellStyle.setBorderTop(oBorderStyle.THIN);
	cellStyle.setBorderBottom(oBorderStyle.THIN);
	cellStyle.setBorderLeft(oBorderStyle.THIN);
	cellStyle.setBorderRight(oBorderStyle.THIN);
	  //フォントスタイルを作成
	  fontStyle = objXLS.createFont();
	  fontStyle.setFontName("MS ゴシック");
	  fontStyle.setBold(true); //setBoldweightメソッド廃止のため、変更0
	//スタイルにフォントをセット
	cellStyle.setFont(fontStyle);

	//列ループ→行ループとループを入れ子にして対象セルにスタイルをセット(setCellStyle)
	for(idxR = startRow-1; idxR LT endRow; idxR = idxR+1){
		setRow = sheet.getRow(idxR);
		if(not IsDefined("setRow")){
			setRow = sheet.createRow(idxR);
		}
		for(idxC = startColumn-1; idxC LT endColumn; idxC = idxC+1){
			setRowColumn = setRow.getCell(idxC);
			if(not IsDefined("setRowColumn")){
				setRowColumn = setRow.createCell(idxC);
			}
			setRowColumn.setCellStyle(cellStyle);
		}
	}

	//Excelファイルを書き出し
	SpreadsheetWrite(SpreadsheetObj,outputXLSFile,"",true,false);
    
</cfscript>

getWorkBookを使ってWorkbookオブジェクトを取得したあと、対象となるシートを選択するところまでは同じですが、その後に「getPrintSetup()」メソッドを呼び出して、印刷設定用の設定を行っています。「setLandscape()」メソッドは、クラスきにするかどうかを true / false で、「setPaperSize()」では用紙の設定を行っています。用紙の設定のパラメーターはPrintSetupのクラスのデータ(フィールド)にあり、フィールド名を指定(A3_PAPERSIZE)してもその値(8)を指定してもどちらでも動作します。


【参考にしたサイト】
https://poi.apache.org/apidocs/dev/org/apache/poi/ss/usermodel/PrintSetup.html
http://negathibudaa.hatenablog.jp/entry/2016/07/19/210656
https://poi.apache.org/apidocs/dev/constant-values.html#org.apache.poi.ss.usermodel.PrintSetup.A3_PAPERSIZE

さて、ここまででCのトピックを終わろうと思っていたら、新たな問題が発覚しました。実は、上記のプログラムは xls では設定が効かないというものでした。思わぬところでつまずいてしまったのですが、どうやら新規にSpreadSheetNew関数を使用してxlsファイルを作成した時には設定が効くのですが、作成済みのxlsファイルをSpreadSheetRead関数などで読み込んだ場合は、設定が効かない模様です(xlsxはどちらの場合でも大丈夫ですが、setLandscape()を指定せずにsetPaperSize()だけを指定すると用紙サイズの変更が効かなかったこともありました)。

xlsxは大丈夫でxls(しかも作成済みのxlsだけ)では効かなかったということから、Apache POIの制限の可能性も考えられますが、いずれにせよ実際に試す際には動作を色々と確認してもらえればと思います。


D一ページの印刷に収める(こちらも一部つまずきあり)

Cの続きのような内容ですが、PrintSetupクラスには、印刷を縦横特定のページ数で収めるための「setFitHeight()」や「setFitWidth()」が設定もあります。 引数に指定するのは数字(ページ数)です。縦幅または横幅をフリーにするには0を指定します。今回は追加したコードの部分のみを以下に記載します

<cfscript>

	sheet = objXLS.getSheetAt(sheetNum-1);

	sheet.setAutobreaks(true); //※1

	objPrint=sheet.getPrintSetup();
objPrint.setLandscape(true); objPrint.setPaperSize(8); objPrint.setFitHeight(1); objPrint.setFitWidth(1); sheet.setFitToPage(true); //※2 </cfscript>

さすがに今回は大丈夫だと思ったら、※1 と ※2 のところで詰まりました。POIのバージョン(ひいてはCFのバージョン)にも依存すると思われますが、xlsのファイルに対しては※1の指定が無いと縦横幅の指定が効かず、xlsxファイルに対しては※1は必要ないものの※2の設定が無いとやっぱり縦横幅の指摘が効かないという結果になりました。なので、私の環境(ColdFusion 2018 Update 6)で、xls も xlsx も有効にするために、※1も※2も両方を指定しておくという結果になりました。今後、ColdFusion 2018 UpdateでさらにPOIのバージョンが置き換わったときは、また違った動きになるかもしれません。

【参考にしたサイト】
https://stackoverrun.com/ja/q/10044154
https://stackoverflow.com/questions/10068074/problems-fitting-sheet-to-a-single-page-in-xlsx-files-using-the-apache-poi-libra
http://garnote.com/2014/11/java-2.html

Eその他、ちょっとした注意点

今回の実験室の記事を作成するにあたり、別のある事象も確認しました。既に用意されている.xlsxファイルを読み込んで、とあるセルに値を埋め込む処理を行っている時に、値を埋め込んだセルの罫線や書式が消えてしまうという件です。

少し調べてみました。.xlsxファイルはファイルの中身が複数のXMLで構成されているので、問題が発生した.xlsxを.zipに拡張子を変えて、解凍ソフトで解凍後、シートの定義がされている.xmlファイルの情報を調べてみました。細かな点は省略しますが、行や列に対して罫線を指定した場合やシート全般でフォントを変更した場合にこの問題が発生するようです。例えば、1〜10行に対して罫線を指定した場合(上)と、範囲(A1〜J10)に罫線を指定した場合(下)のXMLはこのようになります。

<sheetData>
<row r="1" s="1" customFormat="1" x14ac:dyDescent="0.4"/> <row r="2" s="1" customFormat="1" x14ac:dyDescent="0.4"/> <row r="3" s="1" customFormat="1" x14ac:dyDescent="0.4"/> <row r="4" s="1" customFormat="1" x14ac:dyDescent="0.4"/> <row r="5" s="1" customFormat="1" x14ac:dyDescent="0.4"/> <row r="6" s="1" customFormat="1" x14ac:dyDescent="0.4"/> <row r="7" s="1" customFormat="1" x14ac:dyDescent="0.4"/> <row r="8" s="1" customFormat="1" x14ac:dyDescent="0.4"/> <row r="9" s="1" customFormat="1" x14ac:dyDescent="0.4"/> <row r="10" s="1" customFormat="1" x14ac:dyDescent="0.4"/> </sheetData>
<sheetData>
	<row r="1" spans="1:10" x14ac:dyDescent="0.4">
		<c r="A2" s="1"/>
		<c r="B2" s="1"/>
		<c r="C2" s="1"/>
		<c r="D2" s="1"/>
		<c r="E2" s="1"/>
		<c r="F2" s="1"/>
		<c r="G2" s="1"/>
		<c r="H2" s="1"/>
		<c r="I2" s="1"/>
		<c r="J2" s="1"/>
	</row>
	<row r="2" spans="1:10" x14ac:dyDescent="0.4">
		<c r="A3" s="1"/>
		<c r="B3" s="1"/>
		<c r="C3" s="1"/>
		<c r="D3" s="1"/>
		<c r="E3" s="1"/>
		<c r="F3" s="1"/>
		<c r="G3" s="1"/>
		<c r="H3" s="1"/>
		<c r="I3" s="1"/>
		<c r="J3" s="1"/>
	</row>
	<row r="3" spans="1:10" x14ac:dyDescent="0.4">
		<c r="A4" s="1"/>
		<c r="B4" s="1"/>
		<c r="C4" s="1"/>
		...

違いだけに注視すると、行だけに罫線を指定した場合は、スタイルの指定も行の単位でだけ行われて、個々の列(A〜J)のスタイル情報は設定されていません。それに対して、範囲で指定している場合には、各行に対する列(A〜J)に対するスタイル情報も持った状態でファイルが作成されています。

これら.xlsxファイルにデータを追加すると行に対して罫線を指定した.xlsx(上)は、新規に対象セルに対する要素を新たに追加しますが、その際に行全体のスタイルの定義を行わないため、対象のセルの罫線が消えてしまいます。範囲に対して罫線を指定した.xlsx(下)は、既に対象のセルに関する要素が存在しますので、そこに値だけを追加する形となり罫線が維持されます。

<row r="8" s="1" customFormat="1" x14ac:dyDescent="0.4">
	<c r="A8" t="s">
		<v>1</v>
	</c>
	<c r="B8" t="s">
		<v>1</v>
	</c>
	<c r="C8" t="s">
		<v>1</v>
	</c>
</row>
<row r="8" spans="1:13" x14ac:dyDescent="0.4">
	<c r="A8" s="1" t="s">
		<v>1</v>
	</c>
	<c r="B8" s="2" t="s">
		<v>1</v>
	</c>
	<c r="C8" s="2" t="s">
		<v>1</v>
	</c>
		...
セルに値を指定する際の動作はApache POIライブラリ側で行われているので、行全体・列全体の罫線の指定やシート全般のスタイルの指定などは避けた方がよいでしょう。予め、特定の範囲に対するスタイルを指定しておくか、後からSpreadsheetFormatCellRange関数でスタイルを個別に指定する方がよさそうです。

ということで、2年ぶりの実験室の記事の更新となりましたがいかがだったでしょうか。
CFタグや関数では実現できないところをPOIライブラリの直接呼出しで補うのは便利ですが、POIライブラリが新しくなったりxlsやxlsxの違いによって悩まされ、ライブラリ自体を理解しないといけないのは、やっぱり大変だなと思いました(ネットの情報であれこれ試行錯誤していくことになります)。
ですので、やりたいことに限度は出てきますが、CFタグ・関数で呼び出すだけで(内部のライブラリのことを考えずに)さくさくプログラミングが出来ていくColdFusionの開発効率はやっぱりありがたいことだし、利点の一つだと思います。また、新しいネタがあったら、記事を追加していきます。

ColdFusionの各種情報の配信


最新情報
■2019/11/27
『ColdFusion 実験室』
実験7

■2019/11/27
Adobe ColdFusion 2018
インストール セットアップ情報
CFサーバーのインストール

■2017/6/26
『ColdFusion 実験室』
実験6

■2017/3/27
ColdFusion 2016 対応
インストール セットアップ情報
CFサーバーのインストール
CFビルダーのインストール

■2016/9/20
『ColdFusion 2016 リリース
Enterprise Edition 活用資料』
記事一覧

■2016/3/30
『ColdFusion 実験室』
実験4、実験5

■2015/8/10 〜
『ColdFusion クリニック』
記事一覧(随時更新中)

ColdFusionトレーニング


ColdFusionユーザーグループ
ColdFusionユーザーグループ
Copyright 2012 Samuraiz Corporation. All Rights Reserved.