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