001/* 002 * Copyright (c) 2009 The openGion Project. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 013 * either express or implied. See the License for the specific language 014 * governing permissions and limitations under the License. 015 */ 016package org.opengion.fukurou.model; 017 018import java.io.File; // 6.2.0.0 (2015/02/27) 019import java.io.IOException; 020import java.io.OutputStream; 021import java.io.FileOutputStream; 022import java.io.BufferedOutputStream; 023import java.util.Locale; 024import java.util.Map; // 6.0.2.3 (2014/10/10) 画像関連 025import java.util.HashMap; // 6.0.2.3 (2014/10/10) 画像関連 026 027import org.apache.poi.common.usermodel.HyperlinkType; // 6.5.0.0 (2016/09/30) poi-3.15 028import org.apache.poi.ss.util.WorkbookUtil; 029import org.apache.poi.ss.usermodel.Workbook; 030import org.apache.poi.ss.usermodel.Sheet; 031import org.apache.poi.ss.usermodel.Row; 032import org.apache.poi.ss.usermodel.Cell; 033import org.apache.poi.ss.usermodel.CellType; // 6.5.0.0 (2016/09/30) poi-3.15 034import org.apache.poi.ss.usermodel.CellStyle; 035import org.apache.poi.ss.usermodel.VerticalAlignment; // 6.5.0.0 (2016/09/30) poi-3.15 036import org.apache.poi.ss.usermodel.BorderStyle; // 6.5.0.0 (2016/09/30) poi-3.15 037import org.apache.poi.ss.usermodel.Font; 038import org.apache.poi.ss.usermodel.IndexedColors; 039import org.apache.poi.ss.usermodel.RichTextString; 040import org.apache.poi.ss.usermodel.Hyperlink; 041import org.apache.poi.ss.usermodel.CreationHelper; 042import org.apache.poi.ss.usermodel.Drawing; // 6.0.2.3 (2014/10/10) 画像関連 043import org.apache.poi.ss.usermodel.ClientAnchor; // 6.0.2.3 (2014/10/10) 画像関連 044import org.apache.poi.ss.usermodel.Picture; // 6.0.2.3 (2014/10/10) 画像関連 045 046import org.apache.poi.hssf.usermodel.HSSFWorkbook; // .xls 047 048import org.apache.poi.POIXMLDocumentPart; // 6.2.4.2 (2015/05/29) テキスト変換処理 049import org.apache.poi.xssf.usermodel.XSSFDrawing; // 6.2.4.2 (2015/05/29) テキスト変換処理 050import org.apache.poi.xssf.usermodel.XSSFShape; // 6.2.4.2 (2015/05/29) テキスト変換処理 051import org.apache.poi.xssf.usermodel.XSSFSimpleShape; // 6.2.4.2 (2015/05/29) テキスト変換処理 052import org.apache.poi.xssf.usermodel.XSSFTextParagraph; // 6.2.4.2 (2015/05/29) テキスト変換処理 053import org.apache.poi.xssf.usermodel.XSSFTextRun; // 6.2.4.2 (2015/05/29) テキスト変換処理 054import org.apache.poi.xssf.streaming.SXSSFWorkbook; // .xlsx 6.3.7.0 (2015/09/04) 制限あり 高速、低メモリ消費 055 056import org.opengion.fukurou.system.OgRuntimeException ; // 6.4.2.0 (2016/01/29) 057import org.opengion.fukurou.system.Closer; 058import org.opengion.fukurou.util.ImageUtil; // 6.0.2.3 (2014/10/10) 画像関連 059 060import static org.opengion.fukurou.system.HybsConst.CR; // 6.1.0.0 (2014/12/26) refactoring 061import static org.opengion.fukurou.system.HybsConst.BUFFER_MIDDLE; // 6.1.0.0 (2014/12/26) refactoring 062 063/** 064 * POI による、EXCELバイナリファイルに対する、データモデルクラスです。 065 * 066 * 共通的な EXCEL処理 を集約しています。 067 * staticメソッドによる簡易的なアクセスの他に、順次処理も可能なように 068 * 現在アクセス中の、Workbook、Sheet、Row、Cell オブジェクトを内部で管理しています。 069 * 070 * 入力形式は、openXML形式にも対応しています。 071 * ファイルの内容に応じて、.xlsと.xlsxのどちらで読み取るかは、内部的に 072 * 自動判定されます。 073 * 074 * @og.rev 6.0.2.0 (2014/09/19) 新規作成 075 * @og.group その他 076 * 077 * @version 6.0 078 * @author Kazuhiko Hasegawa 079 * @since JDK7.0, 080 */ 081public class ExcelModel { 082 /** このプログラムのVERSION文字列を設定します。 {@value} */ 083 private static final String VERSION = "6.8.2.4 (2017/11/20)" ; 084 085 private static final String DEF_SHEET_NAME = "Sheet" ; 086 087 // 6.0.2.3 (2014/10/10) ImageUtil の Suffix と、Workbook.PICTURE_TYPE_*** の関連付けをしておきます。 088 // Suffix 候補は、[bmp, gif, jpeg, jpg, png, wbmp] だが、対応する PICTURE_TYPE は一致しない。 089 /** staticイニシャライザ後、読み取り専用にするので、ConcurrentHashMap を使用しません。 */ 090 private static final Map<String,Integer> PICTURE_TYPE ; 091 static { 092 PICTURE_TYPE = new HashMap<>() ; 093 PICTURE_TYPE.put( "png" , Integer.valueOf( Workbook.PICTURE_TYPE_PNG ) ); 094 PICTURE_TYPE.put( "jpeg" , Integer.valueOf( Workbook.PICTURE_TYPE_JPEG ) ); 095 PICTURE_TYPE.put( "jpg" , Integer.valueOf( Workbook.PICTURE_TYPE_JPEG ) ); 096 } 097 098 private final String inFilename ; // エラー発生時のキーとなる、EXCELファイル名 099 private final String sufix ; // 6.1.0.0 (2014/12/26) オープンしたファイル形式を記憶(ピリオドを含む) 100 101 private final Workbook wkbook ; // 現在処理中の Workbook 102 private Sheet sheet ; // 現在処理中の Sheet 103 private Row rowObj ; // 現在処理中の Row 104 105 private int refSheetIdx = -1; // 雛形シートのインデックス 106 107 private final CreationHelper createHelper ; // poi.xssf対応 108 109 private CellStyle style ; // 共通のセルスタイル 110 private CellStyle hLinkStyle ; // Hyperlink用のセルスタイル(青文字+下線) 111 112 private int maxColCount = 5 ; // 標準セル幅の5倍を最大幅とする。 113 private int dataStartRow = -1; // データ行の開始位置。未設定時は、-1 114 private boolean isAutoCellSize ; // カラム幅の自動調整を行うかどうか(true:行う/false:行わない) 115 116 private String addTitleSheet ; // Sheet一覧を先頭Sheetに作成する場合のSheet名 117 118 private String[] recalcSheetNames ; // 6.5.0.0 (2016/09/30) セルの計算式の再計算をさせるシート名の配列。 119 120 /** 121 * EXCELファイルのWookbookのデータ処理モデルを作成します。 122 * 123 * ここでは、既存のファイルを読み込んで、データ処理モデルを作成しますので、 124 * ファイルがオープンできなければエラーになります。 125 * 126 * @og.rev 6.0.2.0 (2014/09/19) 新規作成 127 * @og.rev 6.2.0.0 (2015/02/27) ファイル引数を、String → File に変更 128 * 129 * @param file EXCELファイル 130 * @see #ExcelModel( File , boolean ) 131 */ 132 public ExcelModel( final File file ) { 133 this( file,true ); 134 } 135 136 /** 137 * EXCELファイルのWookbookのデータ処理モデルを作成します。 138 * 139 * isOpen条件によって、ファイルオープン(true)か、新規作成(false)が分かれます。 140 * ファイルオープンの場合は、EXCELの読み込み以外に、追記するとか、雛形参照する 141 * 場合にも、使用します。 142 * ファイルオープンの場合は、当然、ファイルがオープンできなければエラーになります。 143 * 144 * isOpen=新規作成(false) の場合は、ファイル名の拡張子で、XSSFWorkbook か HSSFWorkbook を 145 * 判定します。.xlsx の場合⇒XSSFWorkbook オブジェクトを使用します。 146 * 147 * @og.rev 6.0.2.0 (2014/09/19) 新規作成 148 * @og.rev 6.0.2.3 (2014/10/10) POIUtil#createWorkbook( String ) を使用するように変更 149 * @og.rev 6.1.0.0 (2014/12/26) 入力ファイルの拡張子判定の対応 150 * @og.rev 6.2.0.0 (2015/02/27) ファイル引数を、String → File に変更 151 * @og.rev 6.2.2.0 (2015/03/27) マクロ付Excel(.xlsm)対応 152 * @og.rev 6.3.7.0 (2015/09/04),5.9.0.0 (2015/09/04) 標準を、SXSSFWorkbook に切り替えてみる。 153 * 154 * @param file EXCELファイル 155 * @param isOpen true:ファイルオープン/false:新規作成 156 * @see #ExcelModel( File ) 157 */ 158 public ExcelModel( final File file , final boolean isOpen ) { 159 inFilename = file.getName(); 160 161 final int idx = inFilename.lastIndexOf( '.' ); // 拡張子の位置 162 if( idx >= 0 ) { 163 sufix = inFilename.substring( idx ).toLowerCase( Locale.JAPAN ); // ピリオドを含む 164 } 165 else { 166 final String errMsg = "ファイルの拡張子が見当たりません。(.xls か .xlsx/.xlsm を指定下さい)" + CR 167 + " filename=[" + file + "]" + CR ; 168 throw new IllegalArgumentException( errMsg ); 169 } 170 171 if( isOpen ) { 172 wkbook = POIUtil.createWorkbook( file ); 173 } 174 else { 175 // 新規の場合、ファイル名に.xlsxで終了した場合⇒.xlsx形式ファイル作成、その他⇒.xls形式ファイル作成 176 if( ".xlsx".equals( sufix ) || ".xlsm".equals( sufix ) ) { // 6.2.2.0 (2015/03/27) 177 // 6.3.7.0 (2015/09/04),5.9.0.0 (2015/09/04) 標準を、SXSSFWorkbook に切り替えてみる。 178 // wkbook = new XSSFWorkbook(); 179 wkbook = new SXSSFWorkbook(); // 機能制限有:シートや行の削除や、AutoCellSize の指定ができないなど。 180 } 181 else if( ".xls".equals( sufix ) ) { 182 wkbook = new HSSFWorkbook(); 183 } 184 else { 185 final String errMsg = "ファイルの拡張子が不正です。(.xls か .xlsx/.xlsm のみ可能)" + CR 186 + " filename=[" + file + "]" + CR ; 187 throw new IllegalArgumentException( errMsg ); 188 } 189 } 190 191 createHelper = wkbook.getCreationHelper(); // poi.xssf対応 192 } 193 194 /** 195 * 内部 Workbook に、フォント名、フォントサイズを設定します。 196 * fontName(フォント名)は、"MS Pゴシック" など名称になります。 197 * fontPoint は、フォントの大きさを指定します。 198 * 内部的には、setFontHeightInPoints(short)メソッドで設定します。 199 * 200 * @og.rev 6.0.2.0 (2014/09/19) 新規作成 201 * 202 * @param fontName フォント名 ("MS Pゴシック" など。nullの場合セットしません) 203 * @param fontPoint フォントの大きさ (0やマイナスの場合はセットしません) 204 */ 205 public void setFont( final String fontName , final short fontPoint ) { 206 // System.out.println( "FontName=" + fontName + " , Point=" + fontPoint ); 207 208 if( style == null ) { style = wkbook.createCellStyle(); } 209 210 final Font font = wkbook.createFont(); 211 // final Font font = wkbook.getFontAt( style.getFontIndex() ); // A,B などのヘッダーもフォントが 212 if( fontName != null ) { 213 font.setFontName( fontName ); // "MS Pゴシック" など 214 } 215 if( fontPoint > 0 ) { 216 font.setFontHeightInPoints( fontPoint ); 217 } 218 219 style.setFont( font ); 220 } 221 222 /** 223 * データ設定する セルに、罫線を追加します。 224 * 225 * ここで設定するのは、罫線の種類と、罫線の色ですが、内部的に固定にしています。 226 * Border=CellStyle.BORDER_THIN 227 * BorderColor=IndexedColors.BLACK 228 * 229 * @og.rev 6.0.2.0 (2014/09/19) 新規作成 230 * @og.rev 6.5.0.0 (2016/09/30) poi-3.15 対応(Cell.CELL_TYPE_XXXX → CellType.XXXX) 231 */ 232 public void setCellStyle() { 233 if( style == null ) { style = wkbook.createCellStyle(); } 234 235 // style.setBorderBottom( CellStyle.BORDER_THIN ); // 6.5.0.0 (2016/09/30) poi-3.12 236 // style.setBorderLeft( CellStyle.BORDER_THIN ); // 6.5.0.0 (2016/09/30) poi-3.12 237 // style.setBorderRight( CellStyle.BORDER_THIN ); // 6.5.0.0 (2016/09/30) poi-3.12 238 // style.setBorderTop( CellStyle.BORDER_THIN ); // 6.5.0.0 (2016/09/30) poi-3.12 239 240 style.setBorderBottom( BorderStyle.THIN ); // 6.4.6.0 (2016/05/27) poi-3.15 241 style.setBorderLeft( BorderStyle.THIN ); // 6.5.0.0 (2016/09/30) poi-3.15 242 style.setBorderRight( BorderStyle.THIN ); // 6.5.0.0 (2016/09/30) poi-3.15 243 style.setBorderTop( BorderStyle.THIN ); // 6.5.0.0 (2016/09/30) poi-3.15 244 245 style.setBottomBorderColor( IndexedColors.BLACK.getIndex() ); 246 style.setLeftBorderColor( IndexedColors.BLACK.getIndex() ); 247 style.setRightBorderColor( IndexedColors.BLACK.getIndex() ); 248 style.setTopBorderColor( IndexedColors.BLACK.getIndex() ); 249 250 // style.setVerticalAlignment( CellStyle.VERTICAL_TOP ); // isAutoCellSize=true 文字は上寄せする。 // 6.5.0.0 (2016/09/30) poi-3.12 251 style.setVerticalAlignment( VerticalAlignment.TOP ); // isAutoCellSize=true 文字は上寄せする。 // 6.5.0.0 (2016/09/30) poi-3.15 252 // style.setWrapText( true ); // isAutoCellSize=true 折り返して表示する。 253 } 254 255 /** 256 * 全てのSheetに対して、autoSizeColumn設定を行うかどうか指定します(初期値:false)。 257 * 258 * autoSize設定で、カラム幅が大きすぎる場合、現状では、 259 * 初期カラム幅の5倍を限度にしています。 260 * 261 * なお、autoSizeColumn設定は負荷の大きな処理なので、saveFile(String)の 262 * 中で実行されます。(セーブしなければ実行されません。) 263 * よって、指定は、いつ行っても構いません。 264 * 265 * @og.rev 6.0.2.0 (2014/09/19) 新規作成 266 * 267 * @param flag autoSizeColumn設定を行うかどうか [true:自動カラム幅設定を行う/false:行わない] 268 * @see #useAutoCellSize( boolean,int ) 269 */ 270 public void useAutoCellSize( final boolean flag ) { 271 isAutoCellSize = flag; 272 } 273 274 /** 275 * 全てのSheetに対して、autoSizeColumn設定を行うかどうか指定します(初期値:false)。 276 * 277 * autoSize設定で、カラム幅が大きすぎる場合、現状では、 278 * 初期カラム幅のcount倍を限度に設定します。 279 * ただし、count がマイナスの場合は、無制限になります。 280 * 281 * なお、autoSizeColumn設定は負荷の大きな処理なので、saveFile(String)の 282 * 中で実行されます。(セーブしなければ実行されません。) 283 * よって、指定は、いつ行っても構いません。 284 * 285 * @og.rev 6.0.2.0 (2014/09/19) 新規作成 286 * 287 * @param flag autoSizeColumn設定を行うかどうか [true:自動カラム幅設定を行う/false:行わない] 288 * @param count 最大幅を標準セル幅の何倍にするかを指定。マイナスの場合は、無制限 289 * @see #useAutoCellSize( boolean ) 290 */ 291 public void useAutoCellSize( final boolean flag, final int count ) { 292 isAutoCellSize = flag; 293 maxColCount = count ; 294 } 295 296 /** 297 * EXCELで、出力処理の最後にセルの計算式の再計算をさせるシート名の配列を指定します。 298 * 299 * null の場合は、再計算しません。 300 * なお、再計算は、saveFile(String)の中で実行されます。(セーブしなければ実行されません。) 301 * 302 * @og.rev 6.5.0.0 (2016/09/30) セルの計算式の再計算をさせる recalcSheetNames 属性の追加。 303 * 304 * @param sheets 対象シート名の配列 305 */ 306 public void setRecalcSheetName( final String[] sheets ){ 307 recalcSheetNames = sheets; 308 } 309 310 /** 311 * データ行の書き込み開始位置の行番号を設定します。 312 * 313 * これは、autoSize設定で、自動調整するカラムを、ヘッダーではなく、 314 * データ部で計算する場合に使用します。 315 * 316 * @og.rev 6.0.2.0 (2014/09/19) 新規作成 317 * 318 * @param st データ行の開始位置。未設定時は、-1 319 * @see #useAutoCellSize( boolean ) 320 */ 321 public void setDataStartRow( final int st ) { 322 dataStartRow = st; 323 } 324 325 /** 326 * Sheet一覧を先頭Sheetに作成する場合のSheet名を指定します。 327 * 328 * これは、Workbook に含まれる Sheet 一覧を作成する場合に、利用可能です。 329 * 330 * この処理は、#saveFile( File ) 処理時に、実行されます。 331 * 332 * @og.rev 6.0.2.0 (2014/09/19) 新規作成 333 * 334 * @param shtName Sheet一覧のSheet名 335 * @see #makeAddTitleSheet() 336 */ 337 public void setAddTitleSheet( final String shtName ) { 338 addTitleSheet = shtName; 339 } 340 341 /** 342 * 内部 Workbookの Sheet数を返します。 343 * 344 * @og.rev 6.0.2.0 (2014/09/19) 新規作成 345 * 346 * @return シート数 347 */ 348 public int getNumberOfSheets() { 349 return wkbook.getNumberOfSheets(); 350 } 351 352 /** 353 * 内部 Workbookより、雛形Sheetをセットします。 354 * 355 * これは、雛形シートを使用する場合に、使います。このメソッドが呼ばれると、 356 * 雛形シートを使用すると判定されます。 357 * 雛形シート名が、内部 Workbook に存在しない場合は、エラーになります。 358 * ただし、null をセットした場合は、最初のシートを雛形シートとして使用すると 359 * 判定します。 360 * 361 * @og.rev 6.0.2.0 (2014/09/19) 新規作成 362 * 363 * @param refSheetName 参照シート名(nullの場合、参照シート使用する場合は、先頭のシート) 364 */ 365 public void setRefSheetName( final String refSheetName ) { 366 // 参照シート名の指定がない場合は、最初のシート 367 refSheetIdx = ( refSheetName == null ) ? 0 : wkbook.getSheetIndex( refSheetName ); 368 369 if( refSheetIdx < 0 ) { // 参照シート名が存在しなかった。 370 final String errMsg = "指定の参照シート名は存在しませんでした。" + CR 371 + " inFilename=[" + inFilename + "] , refSheetName=[" + refSheetName + "]" + CR ; 372 throw new IllegalArgumentException( errMsg ); 373 } 374 } 375 376 /** 377 * 内部 Workbookより、新しいSheetを作ります。 378 * 379 * 先に雛形シートを指定している場合は、その雛形シートから作成します。 380 * 指定していない場合は、新しいシートを作成します。 381 * 雛形シートを参照する場合は、雛形シートそのものを返します。 382 * また、雛形シートの枚数を超える場合は、前の雛形シートをコピーします。 383 * 雛形シートが存在しない場合は、新しいシートを作成します。 384 * 385 * シート名は、重複チェックを行い、同じ名前のシートの場合は、(1),(2)が付けられます。 386 * shtName が null の場合は、"Sheet" が割り振られます。 387 * 388 * この処理を行うと、内部の Sheet にも、ここで作成された Sheet が設定されます。 389 * 390 * @og.rev 6.0.2.0 (2014/09/19) 新規作成 391 * @og.rev 6.2.2.3 (2015/04/10) 雛形シートにそのままデータを書き込んでいく。 392 * @og.rev 6.5.0.0 (2016/09/30) 雛形シート名をそのまま使用する場合は、isOverwrite に、true を指定します。 393 * 394 * @param shtName シート名 (重複する場合は、(2)、(3)のような文字列を追加 、nullの場合は、"Sheet") 395 * @param isOverwrite 雛形シート名をそのまま使用する場合は、true を指定します。 396 */ 397 public void createSheet( final String shtName , final boolean isOverwrite ) { 398 // 参照シートを使う場合(整合性の問題で、両方ともチェックしておきます) 399 400 // 6.2.2.3 (2015/04/10) 雛形シートにそのままデータを書き込んでいく。 401 final int shtNo ; 402 if( refSheetIdx < 0 ) { // 雛形シートを使用しない。 403 sheet = wkbook.createSheet(); 404 shtNo = wkbook.getNumberOfSheets() - 1; 405 } 406 else if( refSheetIdx >= wkbook.getNumberOfSheets() ) { // シート数が雛形より超えている。 407 sheet = wkbook.cloneSheet( refSheetIdx-1 ); // 最後の雛形シートをコピーします。 408 shtNo = wkbook.getNumberOfSheets() - 1; 409 refSheetIdx++ ; 410 } 411 else { 412 sheet = wkbook.getSheetAt( refSheetIdx ); // 雛形シートをそのまま使用 413 shtNo = refSheetIdx; 414 refSheetIdx++ ; 415 } 416 417 // 6.5.0.0 (2016/09/30) 雛形シート名をそのまま使用する場合。 418 if( !isOverwrite ) { 419 setSheetName( shtNo , shtName ); 420 } 421 } 422 423 /** 424 * 内部 Workbook の指定のシート番号の Sheet の名前を設定します。 425 * 426 * 指定のシート名が、既存のシートになければ、そのまま設定します。 427 * すでに、同じ名前のシートが存在する場合は、そのシート名の後に 428 * (1)、(2)、(3)のような文字列を追加します。 429 * shtName が null の場合は、"Sheet" が割り振られます。 430 * 431 * @og.rev 6.0.2.0 (2014/09/19) 新規作成 432 * @og.rev 6.2.5.1 (2015/06/12) シート名重複が自分自身の場合は、(1)等の追加は行わない。 433 * 434 * @param shtNo シート番号 435 * @param shtName シート名 (重複する場合は、(1)、(2)のような文字列を追加 、nullの場合は、"Sheet") 436 */ 437 public void setSheetName( final int shtNo, final String shtName ) { 438 String tempName = ( shtName == null ) ? DEF_SHEET_NAME : WorkbookUtil.createSafeSheetName( shtName ) ; 439 int cnt = 1; 440 441 // 6.2.5.1 (2015/06/12) シート名重複が自分自身の場合は、(1)等の追加は行わない。 442 // ※ EXCELのシート名は、大文字、小文字だけでなく、全角半角の区別もしない。 443 final String nowName = wkbook.getSheetName( shtNo ); 444 if( tempName != null && !tempName.equals( nowName ) ) { // 全く同一の場合は、何もしない。 445 if( shtNo == wkbook.getSheetIndex( tempName ) ) { // シート名判定が、自身の場合 446 wkbook.setSheetName( shtNo,tempName ); 447 } 448 else { 449 while( wkbook.getSheetIndex( tempName ) >= 0 ) { // シート名が存在している場合 450 tempName = WorkbookUtil.createSafeSheetName( shtName + "(" + cnt + ")" ); 451 if( tempName.length() >= 31 ) { // 重複時の追加文字分を減らす。 452 tempName = tempName.substring( 0,26 ) + "(" + cnt + ")" ; // cnt3桁まで可能 453 } 454 cnt++; 455 } 456 wkbook.setSheetName( shtNo,tempName ); 457 } 458 } 459 } 460 461 /** 462 * 内部 Workbook の 指定のSheet番号のシート名前を返します。 463 * 464 * シートが存在しない場合は、null を返します。 465 * 466 * この処理を行うと、内部の Sheet にも、ここで見つけた Sheet が設定されます。 467 * 468 * @og.rev 6.0.2.0 (2014/09/19) 新規作成 469 * 470 * @param shtNo シート番号 471 * 472 * @return shtName シート名 473 */ 474 public String getSheetName( final int shtNo ) { 475 final int shLen = wkbook.getNumberOfSheets(); 476 477 String shtName = null; 478 if( shtNo < shLen ) { 479 sheet = wkbook.getSheetAt( shtNo ); // 現在の sheet に設定する。 480 shtName = sheet.getSheetName(); 481 } 482 483 return shtName ; 484 } 485 486 /** 487 * 内部 Workbook の 指定のSheet名のシート番号を返します。 488 * 489 * シートが存在しない場合は、-1 を返します。 490 * この処理を行うと、内部の Sheet にも、ここで見つけた Sheet が設定されます。 491 * シートが存在しない場合、内部の Sheet オブジェクトも null がセットされますのでご注意ください。 492 * 493 * @og.rev 6.0.2.0 (2014/09/19) 新規作成 494 * 495 * @param shtName シート名 496 * 497 * @return シート番号(名前のシートがなければ、-1) 498 */ 499 public int getSheetNo( final String shtName ) { 500 sheet = wkbook.getSheet( shtName ); // シート名がマッチしなければ、null 501 502 return wkbook.getSheetIndex( shtName ) ; // シート名がマッチしなければ、-1 503 } 504 505 /** 506 * Excelの指定Sheetオブジェクトを削除します。 507 * 508 * 削除するシートは、シート番号でFrom-To形式で指定します。 509 * Fromも Toも、削除するシート番号を含みます。 510 * 例えば、0,3 と指定すると、0,1,2,3 の 4シート分を削除します。 511 * 512 * @og.rev 6.0.2.0 (2014/09/19) 新規作成 513 * 514 * @param fromNo 削除する開始シート番号(含む) 515 * @param toNo 削除する終了シート番号(含む) 516 */ 517 public void removeSheet( final int fromNo,final int toNo ) { 518 for( int shtNo=toNo; shtNo>=fromNo; shtNo-- ) { // 逆順に処理します。 519 wkbook.removeSheetAt( shtNo ); 520 } 521 } 522 523 /** 524 * 内部 Workbookの 現在Sheet の最初の行番号を返します。 525 * 526 * 行は、0 から始まります。 527 * この処理は、内部Sheetが作成されているか、null でない場合のみ実行できます。 528 * 529 * @og.rev 6.0.2.0 (2014/09/19) 新規作成 530 * 531 * @return 最初の行番号 532 */ 533 public int getFirstRowNum() { 534 return sheet.getFirstRowNum(); 535 } 536 537 /** 538 * 内部 Workbookの 現在Sheet の最後の行番号を返します。 539 * 540 * 最終行は、含みます。よって、行数は、getLastRowNum()+1になります。 541 * この処理は、内部Sheetが作成されているか、null でない場合のみ実行できます。 542 * 543 * @og.rev 6.0.2.0 (2014/09/19) 新規作成 544 * 545 * @return 最後の行番号 546 */ 547 public int getLastRowNum() { 548 return sheet.getLastRowNum(); 549 } 550 551 /** 552 * Excelの指定行のRowオブジェクトを作成します。 553 * 554 * 指定行の Row オブジェクトが存在しない場合は、新規作成します。 555 * この処理を実行すると、指定行の Rowオブジェクトが内部 Row に設定されます。 556 * この処理は、内部Sheetが作成されているか、null でない場合のみ実行できます。 557 * 558 * この処理を行うと、内部の Rowオブジェクトが設定されます。 559 * 560 * @og.rev 6.0.2.0 (2014/09/19) 新規作成 561 * 562 * @param rowNo 行の番号 563 */ 564 public void createRow( final int rowNo ) { 565 rowObj = sheet.getRow( rowNo ); 566 if( rowObj == null ) { rowObj = sheet.createRow( rowNo ); } 567 } 568 569 /** 570 * Excelの指定行以降の余計なRowオブジェクトを削除します。 571 * 572 * 指定行の Row オブジェクトから、getLastRowNum() までの行を、削除します。 573 * この処理は、内部Sheetが作成されているか、null でない場合のみ実行できます。 574 * 575 * @og.rev 6.0.2.0 (2014/09/19) 新規作成 576 * 577 * @param startRowNum 指定以降の余計な行を削除 578 */ 579 public void removeRow( final int startRowNum ) { 580 final int stR = startRowNum; 581 final int edR = sheet.getLastRowNum(); 582 583 for( int rowNo=edR; rowNo>=stR && rowNo>=0; rowNo-- ) { // 逆順に処理します。 584 final Row rowObj = sheet.getRow( rowNo ); 585 if( rowObj != null ) { sheet.removeRow( rowObj ); } 586 } 587 } 588 589 /** 590 * Excelの処理中のRowオブジェクトの指定カラム以降の余計なCellオブジェクトを削除します。 591 * 592 * 指定行の Row オブジェクトから、getLastCellNum() までのカラムを、削除します。 593 * この処理は、内部Rowが作成されているか、null でない場合のみ実行できます。 594 * 595 * @og.rev 6.0.2.0 (2014/09/19) 新規作成 596 * 597 * @param startCellNum 指定以降の余計なカラムを削除 598 */ 599 public void removeCell( final int startCellNum ) { 600 final int stC = startCellNum; 601 final int edC = rowObj.getLastCellNum(); 602 603 for( int colNo=edC; colNo>=stC; colNo-- ) { // 逆順に処理します。 604 final Cell colObj = rowObj.getCell( colNo ); 605 if( colObj != null ) { rowObj.removeCell( colObj ); } 606 } 607 } 608 609 /** 610 * row にあるセルのオブジェクト値を設定します。 611 * 612 * 行が存在しない場合、行を追加します。 613 * この処理を行うと、内部の Rowオブジェクトがなければ新規作成されます。 614 * 615 * @og.rev 6.0.2.0 (2014/09/19) 新規作成 616 * 617 * @param vals 新しい配列値。 618 * @param rowNo 値が変更される行(無視されます) 619 */ 620 public void setValues( final String[] vals,final int rowNo ) { 621 if( rowObj == null ) { createRow( rowNo ); } 622 623 if( vals != null ) { 624 for( int colNo=0; colNo<vals.length; colNo++ ) { 625 setCellValue( vals[colNo],colNo ); 626 } 627 } 628 } 629 630 /** 631 * row にあるセルのオブジェクト値を設定します。 632 * 633 * 行が存在しない場合、行を追加します。 634 * 引数に、カラムがNUMBER型かどうかを指定することが出来ます。 635 * この処理を行うと、内部の Rowオブジェクトがなければ新規作成されます。 636 * 637 * @og.rev 6.0.2.0 (2014/09/19) 新規作成 638 * 639 * @param vals 新しい配列値。 640 * @param rowNo 値が変更される行(無視されます) 641 * @param isNums セルが、NUMBER型の場合は、true/それ以外は、false 642 */ 643 public void setValues( final String[] vals,final int rowNo,final boolean[] isNums ) { 644 if( rowObj == null ) { createRow( rowNo ); } 645 646 if( vals != null ) { 647 for( int colNo=0; colNo<vals.length; colNo++ ) { 648 setCellValue( vals[colNo],colNo,isNums[colNo] ); 649 } 650 } 651 } 652 653 /** 654 * Excelの指定セルにデータを設定します。 655 * 656 * ここで設定する行は、現在の内部 Row です。 657 * Row を切り替えたい場合は、#createRow( int ) を呼び出してください。 658 * このメソッドでは、データを文字列型として設定します。 659 * この処理は、内部Rowが作成されているか、null でない場合のみ実行できます。 660 * 661 * @og.rev 6.0.2.0 (2014/09/19) 新規作成 662 * 663 * @param dataVal String文字列 664 * @param colNo セルの番号(0,1,2・・・・) 665 * @see #setCellValue( String,int,boolean ) 666 */ 667 public void setCellValue( final String dataVal , final int colNo ) { 668 setCellValue( dataVal,colNo,false ); 669 } 670 671 /** 672 * Excelの指定セルにデータを設定します。 673 * 674 * ここで設定する行は、現在の内部 Row です。 675 * Row を切り替えたい場合は、#createRow( int ) を呼び出してください。 676 * このメソッドでは、引数のデータ型をNUMBER型の場合は、doubleに変換して、 677 * それ以外は文字列としてとして設定します。 678 * この処理は、内部Rowが作成されているか、null でない場合のみ実行できます。 679 * 680 * @og.rev 6.0.2.0 (2014/09/19) 新規作成 681 * 682 * @param dataVal String文字列 683 * @param colNo セルの番号(0,1,2・・・・) 684 * @param isNumber セルが、NUMBER型の場合は、true/それ以外は、false 685 * @see #createRow( int ) 686 * @see #setCellValue( String,int ) 687 */ 688 public void setCellValue( final String dataVal , final int colNo , final boolean isNumber ) { 689 Cell colObj = rowObj.getCell( colNo ); 690 if( colObj == null ) { colObj = rowObj.createCell( colNo ); } 691 692 if( style != null ) { colObj.setCellStyle(style); } 693 694 // CELL_TYPE_NUMERIC 以外は、String扱いします。 695 if( isNumber ) { 696 final Double dbl = parseDouble( dataVal ); 697 if( dbl != null ) { 698 colObj.setCellValue( dbl.doubleValue() ); 699 return ; // Double 変換できた場合は、即抜けます。 700 } 701 } 702 703 final RichTextString richText = createHelper.createRichTextString( dataVal ); 704 colObj.setCellValue( richText ); 705 } 706 707 /** 708 * Excelの指定セルにHyperlinkを設定します。 709 * 710 * ここで設定する行は、現在の内部 Row です。 711 * Row を切り替えたい場合は、#createRow( int ) を呼び出してください。 712 * このメソッドで設定するHyperlinkは、Sheetに対する LINK_DOCUMENT です。 713 * 先に、セルに対する値をセットしておいてください。 714 * Hyperlinkは、文字に対して、下線 と 青字 のスタイル設定を行います。 715 * 716 * Link文字列(シート名) が、null や ゼロ文字列の場合は、処理を行いません。 717 * 718 * @og.rev 6.0.2.0 (2014/09/19) 新規作成 719 * @og.rev 6.5.0.0 (2016/09/30) poi-3.15 対応(Hyperlink.LINK_XXXX → HyperlinkType.XXXX) 720 * 721 * @param linkVal Link文字列(シート名) 722 * @param colNo セルの番号(0,1,2・・・・) 723 * @see #setCellValue( String,int ) 724 */ 725 public void setCellLink( final String linkVal , final int colNo ) { 726 if( linkVal == null || linkVal.isEmpty() ) { return; } 727 728 Cell colObj = rowObj.getCell( colNo ); 729 if( colObj == null ) { colObj = rowObj.createCell( colNo ); } 730 731 if( hLinkStyle == null ) { 732 hLinkStyle = wkbook.createCellStyle(); 733 if( style != null ) { hLinkStyle.cloneStyleFrom(style); } 734 735 final Font font = wkbook.createFont(); 736 font.setColor( IndexedColors.BLUE.getIndex() ); // リンクは青文字 737 font.setUnderline( Font.U_SINGLE ); // 下線付 738 739 hLinkStyle.setFont( font ); 740 } 741 colObj.setCellStyle(hLinkStyle); 742 743 // final Hyperlink hLink = createHelper.createHyperlink( Hyperlink.LINK_DOCUMENT ); // 6.5.0.0 (2016/09/30) poi-3.12 744 final Hyperlink hLink = createHelper.createHyperlink( HyperlinkType.DOCUMENT ); // 6.5.0.0 (2016/09/30) poi-3.15 745 hLink.setAddress( "'" + linkVal + "'!A1" ); 746 colObj.setHyperlink( hLink ); 747 } 748 749 /** 750 * 現在のRow にあるセルの属性値を配列で返します。 751 * 752 * Rowオブジェクトが存在しない場合は、長さ0の配列を返します。 753 * また、Rowオブジェクトの中の セルオブジェクトが存在しない場合は、 754 * null がセットされます。 755 * 756 * この処理は、内部Sheetが作成されているか、null でない場合のみ実行できます。 757 * この処理を実行すると、指定行の Rowオブジェクトが内部 Row に設定されます。 758 * 759 * @og.rev 6.0.2.0 (2014/09/19) 新規作成 760 * @og.rev 6.3.9.0 (2015/11/06) ExcelModel#getValues(int) では、nullは返さない。 761 * @og.rev 6.3.9.1 (2015/11/27) メソッドの出口は、最後の1か所にすべきです(PMD)。 762 * 763 * @param rowNo 行の番号 764 * @return 指定されたセルの属性値。Rowがnullの場合は、長さ0の配列を返します。 765 * @og.rtnNotNull 766 */ 767 public String[] getValues( final int rowNo ) { 768 rowObj = sheet.getRow( rowNo ); 769 770 final int len = rowObj == null ? 0 : rowObj.getLastCellNum(); // 含まないので、length と同じ意味になる。 771 final String[] vals = new String[len]; // 6.3.9.1 (2015/11/27) メソッドの出口 772 773 for( int colNo=0; colNo<len; colNo++ ) { 774 final Cell colObj = rowObj.getCell( colNo ); 775 vals[colNo] = POIUtil.getValue( colObj ); 776 } 777 778 return vals ; 779 } 780 781 /** 782 * 現在のrow にあるセルの属性値を返します。 783 * 784 * セルオブジェクトが存在しない場合は、null を返します。 785 * 786 * この処理は、内部Sheetが作成されているか、null でない場合のみ実行できます。 787 * この処理を実行すると、指定行の Rowオブジェクトが内部 Row に設定されます。 788 * 789 * @og.rev 6.0.2.0 (2014/09/19) 新規作成 790 * @og.rev 6.3.9.1 (2015/11/27) メソッドの出口は、最後の1か所にすべきです(PMD)。 791 * 792 * @param rowNo 値が参照される行 793 * @param colNo 値が参照される列 794 * 795 * @return 指定されたセルの値 T 796 */ 797 public String getValue( final int rowNo, final int colNo ) { 798 rowObj = sheet.getRow( rowNo ); 799 800 return rowObj == null ? null : POIUtil.getValue( rowObj.getCell( colNo ) ); 801 } 802 803 /** 804 * 指定のシートの行・列の箇所に、イメージファイルを挿入します。 805 * 806 * ここでは、セル範囲ではなく、指定の行列の箇所に、アンカーを設定して、画像ファイルを 807 * 挿入します。一応、リサイズして、元の大きさ近くに戻しますが、縦横比が変わってしまいます。 808 * 正確に挿入する場合は、セル範囲の指定と、マージンを指定しなければなりませんが、 809 * 微調整が必要です。 810 * 811 * この処理で使用される Sheetオブジェクトは一時的に作成されます。(キャッシュされません) 812 * 一連処理のどのタイミングで実行しても、内部の状態には影響はありません。 813 * 814 * @og.rev 6.0.2.3 (2014/10/10) 新規作成 815 * 816 * @param imgFile 挿入するイメージファイル名 817 * @param shtNo シート番号 818 * @param rowNo 挿入する行 819 * @param colNo 挿入する列 820 */ 821 public void addImageFile( final String imgFile, final int shtNo, final int rowNo, final int colNo ) { 822 addImageFile( imgFile,shtNo,rowNo,colNo,rowNo,colNo,0,0,0,0 ); 823 } 824 825 /** 826 * 指定のシートの行・列の箇所に、イメージファイルを挿入します。 827 * 828 * ここでは、セル範囲ではなく、指定の行列の箇所に、アンカーを設定して、画像ファイルを 829 * 挿入します。一応、リサイズして、元の大きさ近くに戻しますが、縦横比が変わってしまいます。 830 * 正確に挿入する場合は、セル範囲の指定と、マージンを指定しなければなりませんが、 831 * 微調整が必要です。 832 * 833 * この処理で使用される Sheetオブジェクトは一時的に作成されます。(キャッシュされません) 834 * 一連処理のどのタイミングで実行しても、内部の状態には影響はありません。 835 * 836 * @og.rev 6.0.2.3 (2014/10/10) 新規作成 837 * @og.rev 6.4.6.0 (2016/05/27) poi-3.15 準備 838 * @og.rev 6.8.2.4 (2017/11/20) poi-3.17 で、警告: [rawtypes] raw型が見つかりました対応 839 * 840 * @param imgFile 挿入するイメージファイル名 841 * @param shtNo シート番号 842 * @param row1 挿入する行(開始) 843 * @param col1 挿入する列(開始) 844 * @param row2 挿入する行(終了-含まず) 845 * @param col2 挿入する列(終了-含まず) 846 * @param dx1 開始セルのX軸座標(マージン) 847 * @param dy1 開始セルのY軸座標(マージン) 848 * @param dx2 終了セルのX軸座標(マージン) 849 * @param dy2 終了セルのY軸座標(マージン) 850 */ 851 public void addImageFile( final String imgFile , final int shtNo , 852 final int row1 , final int col1 , final int row2 , final int col2 , 853 final int dx1 , final int dy1 , final int dx2 , final int dy2 ) { 854 final String suffix = ImageUtil.getSuffix( imgFile ); 855 final Integer picType = PICTURE_TYPE.get( suffix ); 856 857 // 実験した結果、bmp,gif,tif については、PICTURE_TYPE_PNG で、挿入できた。 858 // 6.4.1.1 (2016/01/16) PMD refactoring. Avoid if (x != y) ..; else ..; 859 final int pictureType = picType == null ? Workbook.PICTURE_TYPE_PNG : picType.intValue() ; 860 861 final byte[] imgs = ImageUtil.byteImage( imgFile ); 862 863 final int pictureIdx = wkbook.addPicture( imgs, pictureType ); 864 865 final Sheet sheet = wkbook.getSheetAt( shtNo ); 866 // 6.8.2.4 (2017/11/20) poi-3.17 で、警告: [rawtypes] raw型が見つかりました対応 867 final Drawing<?> patriarch = sheet.createDrawingPatriarch(); // 昔は一度しか実行できなかったようです。 868 // final Drawing patriarch = sheet.createDrawingPatriarch(); // 昔は一度しか実行できなかったようです。 869 870 final ClientAnchor anchor = patriarch.createAnchor( dx1,dy1,dx2,dy2,col1,row1,col2,row2 ); 871 // ClientAnchor anchor = createHelper.createClientAnchor(); でも作成可能。 872 873 // MOVE_AND_RESIZE, MOVE_DONT_RESIZE, DONT_MOVE_AND_RESIZE から、決め打ち。 874 // anchor.setAnchorType( ClientAnchor.MOVE_DONT_RESIZE ); // 6.4.6.0 (2016/05/27) poi-3.12 875 anchor.setAnchorType( ClientAnchor.AnchorType.MOVE_DONT_RESIZE ); // 6.4.6.0 (2016/05/27) poi-3.15 876 877 final Picture pic = patriarch.createPicture( anchor, pictureIdx ); 878 // セルの範囲指定がゼロの場合、画像サイズもゼロになる為、リサイズしておく。 879 if( row1 == row2 || col1 == col2 ) { pic.resize(); } // resize すると、anchor のマージンが無視されるようです。 880 } 881 882 /** 883 * 内部 Workbook オブジェクトをファイルに書き出します。 884 * 885 * Excelの形式は、ここで指定する出力ファイルの拡張子ではなく、コンストラクタで 886 * 指定したファイルの拡張子で決まります。 887 * 異なる形式の拡張子を持つファイルを指定した場合、強制的に、オープンした 888 * Workbook の形式の拡張子を追加します。 889 * 890 * 拡張子は、Excel 2007以降の形式(.xlsx)か、Excel 2003以前の形式(.xls) が指定できます。 891 * 拡張子が未設定の場合は、オープンした Workbook の形式に合わせた拡張子を付与します。 892 * 893 * isAutoCellSize=true の場合は、ここで全Sheetに対してCell幅の自動調整が行われます。 894 * 895 * @og.rev 6.0.2.0 (2014/09/19) 新規作成 896 * @og.rev 6.1.0.0 (2014/12/26) 入力ファイルの拡張子判定の対応 897 * @og.rev 6.2.0.0 (2015/02/27) ファイル引数を、String → File に変更 898 * @og.rev 6.5.0.0 (2016/09/30) セルの計算式の再計算をさせる recalcSheetNames 属性の追加。 899 * 900 * @param file セーブするファイル 901 */ 902 public void saveFile( final File file ) { 903 final File saveFile ; 904 String fname = file.getName(); 905 if( fname.toLowerCase(Locale.JAPAN).endsWith( sufix ) ) { 906 saveFile = file; 907 } 908 else { 909 final int idx = fname.lastIndexOf( '.' ); 910 if( idx >= 0 ) { fname = fname.substring( 0,idx ); } 911 saveFile = new File( file.getParent() , fname + sufix ); 912 } 913 914 if( isAutoCellSize ) { POIUtil.autoCellSize( wkbook, maxColCount, dataStartRow ); } 915 916 // 6.5.0.0 (2016/09/30) セルの計算式の再計算をさせる recalcSheetNames 属性の追加。 917 if( recalcSheetNames != null && recalcSheetNames.length > 0 ) { 918 for( final String shtName : recalcSheetNames ) { 919 final Sheet sht = wkbook.getSheet( shtName ); // シート名がマッチしなければ、null 920 if( sht != null ) { sht.setForceFormulaRecalculation(true); } 921 } 922 } 923 924 // こちらの都合で、TitleSheet は、autoCellSize ではなく、Sheet#autoSizeColumn(int) を使用して、自動計算させる。 925 if( addTitleSheet != null ) { makeAddTitleSheet(); } 926 927 OutputStream fileOut = null ; 928 try { 929 fileOut = new BufferedOutputStream( new FileOutputStream( saveFile ) ); // 6.1.0.0 (2014/12/26) 930 wkbook.write( fileOut ); 931 } 932 catch( final IOException ex ) { 933 final String errMsg = "ファイルへ書込み中にエラーが発生しました。" + CR 934 + " File=" + saveFile + CR 935 + ex.getMessage() ; 936 throw new OgRuntimeException( errMsg,ex ); 937 } 938 finally { 939 Closer.ioClose( fileOut ); 940 } 941 } 942 943 /** 944 * 内部 Workbook オブジェクトのSheet一覧のSheetを、先頭に追加します。 945 * 946 * これは、Workbook に含まれる Sheet 一覧を作成する場合に、利用可能です。 947 * 948 * この処理は、内部のWorkbook、Sheetオブジェクトに依存して実行されます。 949 * また、単独ではなく、#saveFile( File ) 実行時に、addTitleSheet が 950 * 設定されている場合のみ、実行されます。 951 * 952 * @og.rev 6.0.2.0 (2014/09/19) 新規作成 953 * 954 * @see #saveFile( File ) 955 * @see #setAddTitleSheet( String ) 956 */ 957 private void makeAddTitleSheet() { 958 sheet = wkbook.createSheet(); 959 final String shtNm = sheet.getSheetName(); // Sheet名の取得 960 wkbook.setSheetOrder( shtNm,0 ); // そのSheetを先頭に移動 961 setSheetName( 0,addTitleSheet ); // そのSheet名を変更 → これが、TitleSheet 962 963 int rowNo = 0; 964 createRow( rowNo++ ); // 先頭行(インスタンス共通のRowオブジェクト)作成 965 setCellValue( "No" , 0 ); 966 setCellValue( "Sheet", 1 ); 967 968 final int shCnt = wkbook.getNumberOfSheets(); 969 for( int shtNo=1; shtNo<shCnt; shtNo++,rowNo++ ) { 970 final String nm = wkbook.getSheetName( shtNo ); 971 972 createRow( rowNo ); // 行の追加作成 973 setCellValue( String.valueOf( rowNo ),0,true ); // 行番号として、数字型で登録 974 setCellValue( nm , 1 ); // シートの値を書き込む 975 setCellLink( nm , 1 ); // シートへのリンクを作成する。 976 } 977 978 sheet.autoSizeColumn( 0 ); 979 sheet.autoSizeColumn( 1 ); 980 } 981 982 /** 983 * Workbook の全Sheetを対象に、空行を取り除き、全体をシュリンクします。 984 * 985 * この処理は、#saveFile( File ) の直前に行うのがよいでしょう。 986 * 987 * ここでは、Row を逆順にスキャンし、Cellが 存在しない間は、行を削除します。 988 * 途中の空行の削除ではなく、最終行からの連続した空行の削除です。 989 * 990 * isCellDel=true を指定すると、Cellの末尾削除を行います。 991 * 有効行の最後のCellから空セルを削除していきます。 992 * 表形式などの場合は、Cellのあるなしで、レイアウトが崩れる場合がありますので 993 * 処理が不要な場合は、isCellDel=false を指定してください。 994 * 995 * @og.rev 6.0.2.0 (2014/09/19) 新規作成 996 * 997 * @param isCellDel Cellの末尾削除を行うかどうか(true:行う/false:行わない) 998 */ 999 public void activeWorkbook( final boolean isCellDel ) { 1000 POIUtil.activeWorkbook( wkbook, isCellDel ); 1001 } 1002 1003 /** 1004 * Workbook の全Sheetを対象に、テキスト変換処理を行います(XSLX限定)。 1005 * 1006 * この処理は、#saveFile( File ) の直前に行うのがよいでしょう。 1007 * #activeWorkbook( boolean ) との順番は構いません。 1008 * 1009 * ・シート名の一覧をピックアップします。 1010 * ・セル値を、セル単位にピックアップします。 1011 * ・オブジェクト文字列を、改行単位にピックアップし、結果を合成します。 1012 * 1013 * ここでは、内部的に、TextConverterインターフェースを作成して処理します。 1014 * 1015 * @og.rev 6.2.4.2 (2015/05/29) テキスト変換処理 1016 * @og.rev 6.3.1.0 (2015/06/28) TextConverterに、引数(cmnt)を追加 1017 * @og.rev 6.3.9.0 (2015/11/06) Java 8 ラムダ式に変更 1018 * 1019 * @param convMap 変換対象を管理するMapオブジェクト 1020 * @see #textConverter( TextConverter ) 1021 */ 1022 public void textConverter( final Map<String,String> convMap ) { 1023 textConverter( 1024 ( val,cmnt ) -> convMap.get( val ) 1025 ); 1026 1027 // textConverter( 1028 // new TextConverter<String,String>() { 1029 // /** 1030 // * 入力文字列を、変換します。 1031 // * 1032 // * @param val 入力文字列 1033 // * @param cmnt コメント 1034 // * @return 変換文字列(変換されない場合は、null) 1035 // */ 1036 // @Override 1037 // public String change( final String val , final String cmnt ) { 1038 // return convMap.get( val ); 1039 // } 1040 // } 1041 // ); 1042 } 1043 1044 /** 1045 * Workbook の全Sheetを対象に、テキスト変換処理を行います(XSLX限定)。 1046 * 1047 * この処理は、#saveFile( File ) の直前に行うのがよいでしょう。 1048 * #activeWorkbook( boolean ) との順番は構いません。 1049 * 1050 * ・シート名の一覧をピックアップします。 1051 * ・セル値を、セル単位内の改行単位にピックアップし、結果を合成ます。 1052 * ・オブジェクト文字列を、改行単位にピックアップし、結果を合成します。 1053 * 1054 * ここでは、シート名、セルテキスト、SimpleShapeオブジェクトのテキストを 1055 * input に、TextConverterインターフェース の change メソッドを呼び出します。 1056 * 戻り値が、null でないなら、元のデータと置き換えます。 1057 * 戻り値が、null の場合は、そのまま読み飛ばします。(なにもしません) 1058 * EXCELへの書き戻しが発生しますので、万一、ファイル破損で、開けなくなる場合を 1059 * 想定して、バックアップファイルは、各自で準備してください。 1060 * 1061 * @og.rev 6.2.4.2 (2015/05/29) テキスト変換処理 1062 * @og.rev 6.2.5.0 (2015/06/05) xsl形式のオブジェクト取得…はできなかった。 1063 * @og.rev 6.3.1.0 (2015/06/28) TextConverterに、引数(cmnt)を追加 1064 * @og.rev 6.3.9.0 (2015/11/06) セルに値をセットするときに、セルタイプを考慮する。 1065 * 1066 * @param conv TextConverterインターフェース 1067 * @see #textConverter( Map ) 1068 */ 1069 @SuppressWarnings(value={"deprecation"}) // poi-3.15 1070 public void textConverter( final TextConverter<String,String> conv ) { 1071 // if( ".xlsx".equals( sufix ) || ".xlsm".equals( sufix ) ) { 1072 final int shCnt = wkbook.getNumberOfSheets(); 1073 for( int shtNo=0; shtNo<shCnt; shtNo++ ) { 1074 final Sheet sht = wkbook.getSheetAt( shtNo ); 1075 // シート名の変換 1076 final String shtNm = conv.change( sht.getSheetName() , "Sheet" ); 1077 if( shtNm != null ) { 1078 setSheetName( shtNo,shtNm ); // 同一シート対策済みのメソッドを呼び出す。 1079 } 1080 1081 // セル値の変換 1082 final int stR = Math.max( sht.getFirstRowNum(),0 ); // stR が、マイナスのケースがある。 1083 final int edR = sht.getLastRowNum(); 1084 1085 for( int rowNo=stR; rowNo<=edR; rowNo++ ) { 1086 final Row rowObj = sht.getRow( rowNo ); 1087 if( rowObj != null ) { 1088 final int stC = Math.max( rowObj.getFirstCellNum(),0 ); // stC が、マイナスのケースがある。 1089 final int edC = rowObj.getLastCellNum(); 1090 for( int colNo=stC; colNo<=edC; colNo++ ) { 1091 final Cell colObj = rowObj.getCell( colNo ); 1092 // if( colObj != null && colObj.getCellType() != Cell.CELL_TYPE_BLANK ) { // 6.5.0.0 (2016/09/30) poi-3.12 1093 if( colObj != null && colObj.getCellTypeEnum() != CellType.BLANK ) { // 6.5.0.0 (2016/09/30) poi-3.15 1094 final String cmnt= "Sht" + shtNo + ":" + POIUtil.getCelKigo( rowNo,colNo ); 1095 final String val = crConv( conv, POIUtil.getValue( colObj ),cmnt ); // 改行対応 1096 if( val != null ) { 1097 POIUtil.setValue( colObj,val ); // 6.3.9.0 (2015/11/06) 1098 // colObj.setCellValue( val ); 1099 } 1100 } 1101 } 1102 } 1103 } 1104 1105 // オブジェクト文字列の変換 1106 if( sht instanceof POIXMLDocumentPart ) { 1107 for( final POIXMLDocumentPart pxdp : ((POIXMLDocumentPart)sht).getRelations() ) { 1108 if( pxdp instanceof XSSFDrawing ) { 1109 for( final XSSFShape shape : ((XSSFDrawing)pxdp).getShapes() ) { 1110 final org.apache.poi.xssf.usermodel.XSSFAnchor anc = shape.getAnchor(); 1111 final String ancSt = "XY(" + anc.getDx1() + "-" + anc.getDy1() + ")" ; 1112 int cnt = 0; 1113 if( shape instanceof XSSFSimpleShape ) { 1114 for( final XSSFTextParagraph para : ((XSSFSimpleShape)shape).getTextParagraphs() ) { 1115 for( final XSSFTextRun text : para.getTextRuns() ) { 1116 final String cmnt= "Sht" + shtNo + ":" + ancSt + ":Tb(" + cnt++ + ")" ; 1117 final String val = crConv( conv,text.getText() , cmnt ); 1118 if( val != null ) { 1119 text.setText( val ); 1120 } 1121 } 1122 } 1123 } 1124 } 1125 } 1126 } 1127 } 1128 // 6.2.5.0 (2015/06/05) xsl形式のオブジェクト取得…はできなかった。 1129 // else if( sht instanceof HSSFSheet ) { 1130 // HSSFPatriarch patri = ((HSSFSheet)sht).getDrawingPatriarch(); 1131 // for( final HSSFShape shape : patri.getChildren() ) { 1132 // if( shape instanceof HSSFTextbox ) { 1133 // HSSFRichTextString rts = ((HSSFSimpleShape)shape).getString(); 1134 // if( rts != null ) { 1135 // final String val = crConv( conv,rts.getString() ); 1136 // if( val != null ) { 1137 // HSSFRichTextString rts2 = new HSSFRichTextString( val ); 1138 // ((HSSFSimpleShape)shape).setString( rts2 ); 1139 // } 1140 // } 1141 // } 1142 // } 1143 // } 1144 } 1145 // } 1146 } 1147 1148 /** 1149 * 現在のシートを選択済み(true)か、非選択済み(false)に設定します。 1150 * 1151 * 通常は、シートは、先頭シート以外は、非選択状態になっています。 1152 * シートを選択済みにすることで、印刷範囲を指定する事ができます。 1153 * 1154 * @og.rev 6.3.9.0 (2015/11/06) 新規追加 1155 * 1156 * @param isSelect true:シート選択/false:非選択 1157 */ 1158 public void sheetSelected( final boolean isSelect ) { 1159 sheet.setSelected( isSelect ); 1160 } 1161 1162 /** 1163 * Workbook の雛形シートのTextConverter した、新しいSheetを作成します。 1164 * 1165 * 正確には、 1166 * 1.雛形シートを、コピーして、新しいSheet(shtName)を、作成します。 1167 * 2.雛形シートが指定されていない場合は、一番最後のシートをコピーします。 1168 * 2.そのシートに対して、TextConverter を行い、文字列変換します。 1169 * 1170 * @og.rev 6.3.9.0 (2015/11/06) 新規追加 1171 * @og.rev 6.5.0.0 (2016/09/30) poi-3.15 対応(Cell.CELL_TYPE_XXXX → CellType.XXXX) 1172 * 1173 * @param conv TextConverterインターフェース 1174 * @param shtName シート名 1175 * @see #textConverter( Map ) 1176 */ 1177 @SuppressWarnings(value={"deprecation"}) // poi-3.15 1178 public void sheetCopy( final TextConverter<String,String> conv , final String shtName ) { 1179 int shtNo = wkbook.getNumberOfSheets() - 1; 1180 if( refSheetIdx >= 0 && refSheetIdx < shtNo ) { // 雛形シートをコピーする。 1181 sheet = wkbook.cloneSheet( refSheetIdx ); 1182 } 1183 else { 1184 sheet = wkbook.cloneSheet( shtNo ); // 最後のシートをコピーします。 1185 } 1186 shtNo++ ; // シート番号を増やしておく。 1187 1188 // シート名の変換 1189 setSheetName( shtNo,shtName ); // 同一シート対策済みのメソッドを呼び出す。 1190 1191 // セル値の変換 1192 final int stR = Math.max( sheet.getFirstRowNum(),0 ); // stR が、マイナスのケースがある。 1193 final int edR = sheet.getLastRowNum(); 1194 1195 for( int rowNo=stR; rowNo<=edR; rowNo++ ) { 1196 final Row rowObj = sheet.getRow( rowNo ); 1197 if( rowObj != null ) { 1198 final int stC = Math.max( rowObj.getFirstCellNum(),0 ); // stC が、マイナスのケースがある。 1199 final int edC = rowObj.getLastCellNum(); 1200 for( int colNo=stC; colNo<=edC; colNo++ ) { 1201 final Cell colObj = rowObj.getCell( colNo ); 1202 // if( colObj != null && colObj.getCellType() != Cell.CELL_TYPE_BLANK ) { // 6.5.0.0 (2016/09/30) poi-3.12 1203 if( colObj != null && colObj.getCellTypeEnum() != CellType.BLANK ) { // 6.5.0.0 (2016/09/30) poi-3.15 1204 final String val = crConv( conv, POIUtil.getValue( colObj ),null ); // 改行対応 1205 if( val != null ) { 1206 POIUtil.setValue( colObj,val ); 1207 // colObj.setCellValue( val ); 1208 } 1209 } 1210 } 1211 } 1212 } 1213 1214 // オブジェクト文字列の変換 1215 if( sheet instanceof POIXMLDocumentPart ) { 1216 for( final POIXMLDocumentPart pxdp : ((POIXMLDocumentPart)sheet).getRelations() ) { 1217 if( pxdp instanceof XSSFDrawing ) { 1218 for( final XSSFShape shape : ((XSSFDrawing)pxdp).getShapes() ) { 1219 // final org.apache.poi.xssf.usermodel.XSSFAnchor anc = shape.getAnchor(); 1220 if( shape instanceof XSSFSimpleShape ) { 1221 for( final XSSFTextParagraph para : ((XSSFSimpleShape)shape).getTextParagraphs() ) { 1222 for( final XSSFTextRun text : para.getTextRuns() ) { 1223 final String val = crConv( conv,text.getText() , null ); 1224 if( val != null ) { 1225 text.setText( val ); 1226 } 1227 } 1228 } 1229 } 1230 } 1231 } 1232 } 1233 } 1234 } 1235 1236 /** 1237 * Workbook の全Sheetを対象に、テキスト変換処理を行います(XSLX限定)。 1238 * 1239 * この処理は、#saveFile( File ) の直前に行うのがよいでしょう。 1240 * #activeWorkbook( boolean ) との順番は構いません。 1241 * 1242 * ・シート名の一覧をピックアップします。 1243 * ・セル値を、セル単位内の改行単位にピックアップし、結果を合成ます。 1244 * ・オブジェクト文字列を、改行単位にピックアップし、結果を合成します。 1245 * 1246 * ここでは、シート名、セルテキスト、SimpleShapeオブジェクトのテキストを 1247 * input に、TextConverterインターフェース の change メソッドを呼び出します。 1248 * 戻り値が、null でないなら、元のデータと置き換えます。 1249 * 戻り値が、null の場合は、そのまま読み飛ばします。(なにもしません) 1250 * EXCELへの書き戻しが発生しますので、万一、ファイル破損で、開けなくなる場合を 1251 * 想定して、バックアップファイルは、各自で準備してください。 1252 * 1253 * @og.rev 6.2.4.2 (2015/05/29) テキスト変換処理 1254 * @og.rev 6.3.1.0 (2015/06/28) TextConverterに、引数(cmnt)を追加 1255 * 1256 * @param conv TextConverterインターフェース 1257 * @param val 改行処理を行う元の値 1258 * @param cmnt コメント 1259 * @return 改行処理の結果の値(対象が無ければ、null) 1260 * @see #textConverter( Map ) 1261 */ 1262 private String crConv( final TextConverter<String,String> conv , final String val , final String cmnt ) { 1263 String rtn = null; 1264 if( val != null ) { 1265 if( val.contains( "\n" ) ) { // 改行がある場合(EXCEL のセル内改行コードは、LF(0A)=\n のみ。 1266 final String[] val2 = val.split( "\\n" ); // 改行で分割する。 1267 boolean flag = false; 1268 for( int i=0; i<val2.length; i++ ) { 1269 final String val3 = conv.change( val2[i],cmnt ); // 6.3.1.0 (2015/06/28) 1270 if( val3 != null ) { val2[i] = val3; flag = true; } 1271 } 1272 if( flag ) { 1273 final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE ); 1274 buf.append( val2[0] ); 1275 for( int i=1; i<val2.length; i++ ) { 1276 buf.append( '\n' ).append( val2[i] ); // LF(\n)で、セパレートしているので、LF のみ追加する。 1277 } 1278 rtn = buf.toString(); 1279 } 1280 } 1281 else { // 改行がない場合 1282 rtn = conv.change( val,cmnt ); // 6.3.1.0 (2015/06/28) 1283 } 1284 } 1285 return rtn; 1286 } 1287 1288 /** 1289 * シート一覧を、内部の Workbook から取得します。 1290 * 1291 * 取得元が、Workbook なので、xls , xlsx どちらの形式でも取り出せます。 1292 * 1293 * EXCEL上のシート名を、配列で返します。 1294 * 1295 * @og.rev 6.2.6.0 (2015/06/19) 新規作成 1296 * 1297 * @return シート名の配列 1298 * @see POIUtil#getSheetNames( Workbook ) 1299 */ 1300 public String[] getSheetNames() { 1301 return POIUtil.getSheetNames( wkbook ); 1302 } 1303 1304 /** 1305 * 名前定義一覧を内部の Workbook から取得します。 1306 * 1307 * EXCEL上に定義された名前を、配列で返します。 1308 * ここでは、名前とFormulaをタブで連結した文字列を配列で返します。 1309 * Name オブジェクトを削除すると、EXCELが開かなくなったりするので、 1310 * 取りあえず一覧を作成して、手動で削除してください。 1311 * なお、名前定義には、非表示というのがありますので、ご注意ください。 1312 * 1313 * @og.rev 6.2.6.0 (2015/06/19) 新規作成 1314 * 1315 * @return 名前定義(名前+TAB+Formula)の配列 1316 * @see POIUtil#getNames( Workbook ) 1317 * @og.rtnNotNull 1318 */ 1319 public String[] getNames() { 1320 return POIUtil.getNames( wkbook ); 1321 } 1322 1323 /** 1324 * 書式のスタイル一覧を内部の Workbook から取得します。 1325 * 1326 * EXCEL上に定義された書式のスタイルを、配列で返します。 1327 * 書式のスタイルの名称は、CellStyle にメソッドが定義されていません。 1328 * 実クラスである HSSFCellStyle にキャストして使用する 1329 * 必要があります。(XSSFCellStyle にも名称を取得するメソッドがありません。) 1330 * 1331 * ※ EXCEL2010 ホームタブ→セルのスタイル は、一つづつしか削除できません。 1332 * マクロは、開発タブ→Visual Basic で、挿入→標準モジュール を開き 1333 * テキストを張り付けてください。 1334 * 実行は、開発タブ→マクロ で、マクロ名を選択して、実行します。 1335 * 最後は、削除してください。 1336 * 1337 * @og.rev 6.2.6.0 (2015/06/19) 新規作成 1338 * 1339 * @return 書式のスタイル一覧 1340 * @see POIUtil#getStyleNames( Workbook ) 1341 * @og.rtnNotNull 1342 */ 1343 public String[] getStyleNames() { 1344 return POIUtil.getStyleNames( wkbook ); 1345 } 1346 1347 /** 1348 * 文字列を Double オブジェクトに変換します。 1349 * 1350 * これは、引数の カンマ(,) を削除した文字列から、Double オブジェクトを生成します。 1351 * 処理中に、文字列が解析可能な double を含まない場合(NumberFormatException) 1352 * また、引数が、null,ゼロ文字列,'_', エラー の時には、null を返します。 1353 * 1354 * @og.rev 6.0.2.0 (2014/09/19) 新規作成 1355 * @og.rev 6.3.9.0 (2015/11/06) もう少し判りやすくする。(処理速度は落ちてます。) 1356 * 1357 * @param value Doubleに変換する元の文字列 1358 * 1359 * @return 変換後のDoubleオブジェクト(エラー発生時や変換不可の場合は、null) 1360 */ 1361 private Double parseDouble( final String value ) { 1362 Double rtn = null ; 1363 1364 try { 1365 if( value == null || value.isEmpty() || value.equals( "_" ) ) { 1366 rtn = null; 1367 } 1368 else if( value.indexOf( ',' ) < 0 ) { 1369 rtn = Double.valueOf( value ); // 6.0.2.4 (2014/10/17) メソッドが非効率だった。 1370 } 1371 else { 1372 // 6.3.9.0 (2015/11/06) もう少し判りやすくする。(処理速度は落ちてます。) 1373 rtn = Double.valueOf( value.replaceAll( ",","" ) ); 1374 1375 } 1376 } 1377 catch( final NumberFormatException ex ) { // 文字列が解析可能な数値を含まない場合 1378 final String errMsg = "Double変換できませんでした。" + CR 1379 + ex.getMessage() + CR 1380 + " value=" + value; 1381 System.err.println( errMsg ); 1382 rtn = null; 1383 } 1384 1385 return rtn ; 1386 } 1387 1388 /** 1389 * アプリケーションのサンプルです。 1390 * 1391 * Usage: java org.opengion.fukurou.model.ExcelModel 入力ファイル名 [出力ファイル名] ・・・ 1392 * 通常は標準出力に行単位に、セルをタブ区切り出力します。 1393 * 出力ファイル名 を指定すると、EXCEL ファイルとしてセーブし直します。 1394 * その場合は、以下のパラメータも使用できます。 1395 * -CS CellStyleを 設定します。 1396 * -AS useAutoCellSizeを 設定します。 1397 * -FN=*** FontNameを 設定します。 1398 * -FP=** FontPointを 設定します。 1399 * -IMG 画像ファイルを挿入します。(-IMG 画像ファイル名 シート番号 行 列)をスペース区切りで続けます。 1400 * 1401 * @og.rev 6.0.2.0 (2014/09/19) 新規作成 1402 * 1403 * @param args コマンド引数配列 1404 */ 1405 public static void main( final String[] args ) { 1406 if( args.length == 0 ) { 1407 final String usage = "Usage: java org.opengion.fukurou.model.ExcelModel 入力ファイル名 [出力ファイル名] ・・・\n" + 1408 "\t-CS CellStyleを 設定します。 \n" + 1409 "\t-TC TextConverterを実行します。 \n" + 1410 "\t-AS useAutoCellSizeを 設定します。 \n" + 1411 "\t-FN=*** FontNameを 設定します。 \n" + 1412 "\t-FP=** FontPointを 設定します。 \n" + 1413 "\t-IMG 画像ファイルを挿入します。 \n" + 1414 "\t (-IMG ファイル名 シート番号 行 列) \n" ; 1415 System.err.println( usage ); 1416 return ; 1417 } 1418 1419 final ExcelModel excel = new ExcelModel( new File( args[0] ) , true ); 1420 1421 excel.activeWorkbook( true ); // 余計な行を削除します。 1422 1423 if( args.length > 1 ) { 1424 final File outFile = new File( args[1] ); // 6.2.0.0 (2015/02/27) 1425 boolean isCS = false; 1426 boolean isAS = false; 1427 boolean isTC = false; // 6.2.4.2 (2015/05/29) テキスト変換処理 1428 String fn = null; 1429 short fp = -1; 1430 1431 for( int i=2; i<args.length; i++ ) { 1432 final String prm = args[i]; 1433 1434 if( "-CS".equalsIgnoreCase( prm ) ) { isCS = true; } // 6.4.1.1 (2016/01/16) PMD refactoring. Position literals first in String comparisons for EqualsIgnoreCase. 1435 if( "-AS".equalsIgnoreCase( prm ) ) { isAS = true; } // 6.4.1.1 (2016/01/16) PMD refactoring. Position literals first in String comparisons for EqualsIgnoreCase. 1436 if( "-TC".equalsIgnoreCase( prm ) ) { isTC = true; } // 6.4.1.1 (2016/01/16) PMD refactoring. Position literals first in String comparisons for EqualsIgnoreCase. 1437 if( prm.startsWith( "-FN" ) ) { fn = prm.substring( 3 ); } 1438 if( prm.startsWith( "-FP" ) ) { fp = Short.parseShort( prm.substring( 3 ) ); } 1439 if( "-IMG".equalsIgnoreCase( prm ) ) { // 6.4.1.1 (2016/01/16) PMD refactoring. Position literals first in String comparisons for EqualsIgnoreCase. 1440 final String img = args[++i]; 1441 final int shtNo = Integer.parseInt( args[++i] ); 1442 final int rowNo = Integer.parseInt( args[++i] ); 1443 final int colNo = Integer.parseInt( args[++i] ); 1444 1445 excel.addImageFile( img,shtNo,rowNo,colNo ); 1446 } 1447 } 1448 1449 if( isCS ) { excel.setCellStyle(); } 1450 excel.useAutoCellSize( isAS ); 1451 excel.setFont( fn,fp ); 1452 1453 // 6.2.4.2 (2015/05/29) テキスト変換処理 1454 if( isTC ) { 1455 // 6.3.9.0 (2015/11/06) Java 8 ラムダ式に変更 1456 // 処理が複数行に別れるのは判りにくいので良くない。 1457 excel.textConverter( 1458 ( val,cmnt ) -> { 1459 System.out.println( val ); // すべてのテキストを読み取る。 1460 return null; // 変換せず。 1461 } 1462 ); 1463 1464 } 1465 1466 excel.saveFile( outFile ); 1467 } 1468 else { 1469 final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE ); 1470 1471 final int shLen = excel.getNumberOfSheets(); 1472 for( int shtNo=0; shtNo<shLen; shtNo++ ) { 1473 final String shtName = excel.getSheetName( shtNo ); 1474 1475 final int stRow = excel.getFirstRowNum(); 1476 final int edRow = excel.getLastRowNum(); 1477 for( int rowNo=stRow; rowNo<=edRow; rowNo++ ) { 1478 buf.setLength(0); // Clearの事 1479 buf.append( shtName ).append( '\t' ).append( rowNo ); 1480 final String[] vals = excel.getValues( rowNo ); 1481 if( vals != null ) { 1482 for( int colNo=0; colNo<vals.length; colNo++ ) { 1483 final String val = vals[colNo] == null ? "" : vals[colNo]; 1484 buf.append( '\t' ).append( val ); 1485 } 1486 } 1487 System.out.println( buf ); 1488 } 1489 System.out.println(); 1490 } 1491 } 1492 } 1493}