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

2015年2月4日水曜日

[Java][Excel][JETT] Excel帳票出力にJETTを試してみる

シフト表作成アプリ作る中で、Excel帳票を出力したくなったので軽くライブラリについて調べて見る。

調べる前に、以前に使ったことあるやつ

POI …以前に実務で帳票ガリガリ書いてたけど、かなりプリミティブで結構辛かった。なので生で使うのは本当に必要な時だけにしたい。
Fisshplate …テンプレートを使ってみて、やっぱりコレぐらいサクサク書けない と困ると実感した。ただ、今回出力したい xlsx 形式に対応していない(対応予定なし?)のでスルー。

Java × Excel だと POI の話題ばかり

Java8 / play framework でやっており、とりあえず最近だとどんなライブラリがあるのかと思い調べてみると、POI の話題ばかり。

どのライブラリも POI ベースでそれをラップするのが基本ぽいのに、もしかして皆 POI で頑張ってるのか…??

そんな中 Excel方眼紙を支える技術 - 新・たけぞう瀕死の日記 がかなり参考になった。

JETT を試して見る

なんとなく先端ぽく、↓の比較も面白かったので今回は帳票出力用ライブラリ JETT を試してみることにした。
本家サイトの jXLS との比較:JETT - Comparison to jXLS

とりあえずテンプレートファイルへの出力を試す。

1.テンプレートファイルを用意する。
2.出力コードを書く。

 public static File makeShift() {
  System.out.println(System.getProperty("user.dir"));
  
  List<employee> employees = Lists.newArrayList();
  
  Map<string object=""> beans = Maps.newHashMap();
  Employee emp = new Employee();
  emp.name = "Butcher";
  emp.skillLevel = "High";
  
  employees.add(emp);
  
  beans.put("employees", employees);
  
  String inPath  = "resources/shift_template.xlsx";
  String outPath = "resources/shift.xlsx";
  
  try (FileOutputStream fileOut = new FileOutputStream(outPath);
    InputStream fileIn       = new BufferedInputStream(new FileInputStream(inPath))) {

   ExcelTransformer transformer = new ExcelTransformer();
   Workbook workbook = transformer.transform(fileIn, beans);
   workbook.write(fileOut);
   fileOut.close();
   
  } catch (IOException e) {
   System.err.println("IOException reading " + inPath + ": " + e.getMessage());
  } catch (InvalidFormatException e) {
   System.err.println("InvalidFormatException reading " + inPath + ": " + e.getMessage());
  }
  
  return new File(outPath);  
 }

余分なコードが多いが、テンプレートへの出力処理は ExcelTransformer#transform で JavaBeans を渡すだけでOKなのでかなり簡潔。

テンプレートファイルに記述する構文もシンプルで良い。
個人的に、forEach タグを利用する時にデータの無い行や列にタグを書かなくて良いのは嬉しい。

番外

その他、調べて評判良かったのが ExCella だったけど、Maven repository に登録されておらずライブラリ管理を考えるのが面倒になってスルーした。
(今回は play なので build.sbt で管理)

2014年5月5日月曜日

[Java] POIで、読み込んだExcelファイルから図形を取得して値をセットする

Excelファイル(※2007以降の.xlsx形式)から目的の図形を取得し、値をセットする。
今回はテキストボックスを取得してセットするサンプル。

既に対象のWorkbookとSheetを取得している状態から。
// 図形描画の為のXSSFDrawingから図形のListを取得
XSSFDrawing drawing = sheet.createDrawingPatriarch();
List<XSSFShape> shapeList = drawing.getShapes();

// Listから目的の図形を探索
for (XSSFShape sh : shapeList) {
  // (1)実際の型を確認
  if (sh instanceof XSSFSimpleShape) {
     XSSFSimpleShape ss = (XSSFSimpleShape) sh;
     // (2)テキストの値をkeyに対象かどうか判断
     if (ss.getText().equals("key")) {
      ss.setText("value you want to set");
     }
  }
}

(1)Listで取得されるオブジェクトはXSSFShapeのサブクラス(※)であり、検査せずにSimpleShapeとして扱おうとするとClassCastExceptionが発生する。
 ※こいつら。↓
  XSSFConnector, XSSFGraphicFrame, XSSFPicture, XSSFShapeGroup, XSSFSimpleShape。

(2)APIDocなんかを読み漁ったけれども、定義した名前から図形を取得する方法がわからず。とりあえずテキストから判断するやり方で記述。


【環境】

 POI 3.10
 Java SE7

【参考】

XSSFSimpleShape (POI API Documentation)


[追記 2014/5/7] 

上記のやり方で値をセットすると、図形にもともと設定されていたフォントや揃方がクリアされてデフォルト値になってしまうことがわかった。

もともと設定されていたフォントなどを維持したままテキストのみを置き換える場合は下記のようにテキストを分解し、テキストの最小単位(TextRun)に対して操作を行わなければいけないらしい。

// 上記の11行目から
if (ss.getText().equals("key")) {
  for (XSSFTextParagraph p : ss.getTextParagraphs()) {
   for (XSSFTextRun r : p.getTextRuns()) {
    r.setText("value you want to set");  // ※1
   }
  }
}
※1 改行を含む文字列をブチ込んでも大丈夫だった。だがParagraphの扱いがよくわかっていないので、複数行を操作する場合はもう少し工夫が必要なのかもしれない。

FontはFontクラスで管理してたり、AlignはTextParagraphで管理していたり。Excelの実装自体がそうなのかもしれないけど、Excel上の設定と各オブジェクトの結びつきがわかりづらく、直感的に操作しづらい。。

2014年5月1日木曜日

[Java] POIで、名前が定義されているセルに値をセットする

Excelファイル(※2007以降の.xlsx形式)から名前が定義されているセルを取得し、値をセットする。

既に対象のWorkbookとSheetを取得している状態から。
XSSFName name = workbook.getName("cell's Name");
CellReference ref = new CellReference(name.getRefersToFormula());
XSSFRow row = sheet.getRow(ref.getRow());
XSSFCell cell = row.getCell(ref.getCol());
cell.setCellValue("value you want to set");

上記コードにて、単一のセルにも結合されたセルにも値をセットできることを確認済み。
結合されたセルは範囲で取得したりする必要があるかと思ったけど、今のところ問題無さそう。

また、存在しないセル名を指定している場合、1行目のworkbook#getNameはNullを返すので注意。

【環境】

 POI 3.10
 Java SE7

【参考】

POIで名前定義されているセルを取得する話 - kelkronsoの日記 -
POIで、名前定義の入ったカラムから値を取得。 - るいもの戯れ言 -

2014年4月30日水曜日

[Java] Apache POIで指定した範囲の中心に画像を貼り付けようとして挫折

下記の条件でExcelファイル(※2007以降の.xlsx形式)に画像を貼り付ける。(だいぶ限定的…)

1.画像を貼り付けるセル範囲が指定されている。
2.画像は指定された範囲内で、アスペクト比を保ったまま最大化する。
3.範囲は任意だが、指定された範囲のセルはすべて同じ幅であり、
 かつ指定された範囲のセルはすべて同じ高さとする。


こんな感じ。(水平・垂直方向が逆のケースもあり)


結論から言うと、完全にアスペクト比を保ったままの最適化は出来なかった。

上記図で言うmarginは画像貼り付け先の開始位置をずらしつつ、オフセットを指定することで構成されるが、オフセットの指定はint型でしか行えないことによる。(必要なオフセットは当然、小数の場合もある)

単純なことだが、それに気付くまでの軌跡は↓。。

// 画像とセル範囲のアスペクト比を比較
double imgRatio = (orgImgWidth / (double)orgImgHeight);
double cellRangeRatio = (cellRangeWidth / (double)cellRangeHeight);

// 高さを最大化し、水平方向位置を計算する
if (imgRatio < cellRangeRatio) {
  // 貼り付けられる画像の幅
  double imgWidth = (orgImgWidth * (cellRangeHeight / (double)orgImgHeight));

  // 余白の幅
  double margin = (cellRangeWidth - imgWidth) / 2 ;
  int offsetCol = (int)(margin /  cellWidth);
  imgStartCol = rcol1 + offsetCol;
  imgEndCol = rcol2 - offsetCol;
  int offset = (int)(XSSFShape.EMU_PER_PIXEL * (margin  %  cellWidth));
  dx1 = offset;
  dx2 = offset;

// 横を最大化し、垂直方向位置を計算する
} else if (imgRatio > cellRangeRatio) {
  // 貼り付けられる画像の高さ
  double imgHeight = (orgImgHeight * (cellRangeWidth / (double)orgImgWidth));

  // 余白の高さ
  double margin = (cellRangeHeight - imgHeight) / 2 ;
  int offsetRow = (int)(margin /  cellWidth);
  imgStartRow = rrow1 + offsetRow;
  imgEndRow = rrow2 - offsetRow;
  int offset =  (int)(XSSFShape.EMU_PER_PIXEL * (margin  %  cellHeight));
  dy1 = offset;
  dy2 = offset;
}

【環境】

 POI 3.10
 Java SE7

2014年4月28日月曜日

[Java] Apache POI XSSFClientAnchor でoffset を指定して画像を貼り付ける

初POI。
EXCEL(.xlsx)ファイルに画像を貼り付ける際の offset の指定に苦戦。

画像の位置指定は XSSFClientAnchor クラスを用いて行う。
設定方法は下記の2通りが可能。

1.create時に位置指定を行う。

XSSFClientAnchor anchor = XSSFDrawing.createAnchor(
                          dx1, dy1, dx2, dy2, col1, row1, col2, row2);

2.create後に位置指定を行う。

XSSFClientAnchor anchor = XSSFCreationHelper.createClientAnchor();
anchor.setDx1(dx1);
anchor.setDy1(dy1);
anchor.setDx2(dx2);
anchor.setDy2(dy2);
anchor.setCol1(col1);
anchor.setRow1(row1);
anchor.setCol2(col2);
anchor.setRow2(row2);

※XSSFDrawing クラスに引数無しのcreateAnchorメソッドは無い模様。

開始位置・終了位置の指定

XSSFAnchor.setCol , setRowでcolumn・rowをそれぞれ指定する。
注意点としては、指定したセルの最右下の座標から開始されること。
anchor.setCol1(1);
anchor.setRow1(1);
anchor.setCol2(4);
anchor.setRow2(4);

と指定すると

と出力される。

また、左辺と上辺は罫線上に描画され、右辺と下辺は罫線に重ならない。


オフセットの指定

XSSFClientAnchor#setDx1, setDy1, setDx2, setDy2 で指定する。
これら setter の引数はjavadocには
the x coordinate within the first cell.
とあり、int型の引数が要求されているが、ピクセルなのかポイントなのかわからない。
適当な数字を入れても挙動の法則がわからなかったので、
The offsets are measured in EMUs (english metric units). There are 12700 emus in a point , see EMU_PER_PIXEL and EMU_PER_POINT constants in the XSSFShape class. 

To position a rectange with given width and height at a point (x,y) 
you need to transform all coordinates to Excel coordinate system, i.e 

x1 --> col1 + dx1 
y1 --> row1 + dy1 
x2 --> col2 + dx2 
y2 --> row2 + dy2 
EMUという単位で指定する、とのこと。
この数字はXSSFShape クラスに定義されており、1ポイント=12700EMU、1ピクセル=9525となっている。

その為、setColやsetRowで指定した開始位置から10ピクセルのoffsetを作りたい時は
setDx1(XSSFShape.EMU_PER_PIXEL * 10);
とすれば良い模様。


指定するdx1, dy1, dx2, dy2 はそれぞれ下記のようにオフセットを構成する。




【環境】

POI 3.10
Java SE7

【参考】

apache poi の HSSFClientAnchor について。 - 人工無脳が作りたかった
POI - User - How to calculate location of the shape in Excel 2007?
POI - Dev - Re: Anchor type for images in Excel