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.InputStream; 019import java.io.FileInputStream; 020import java.io.BufferedReader; // 6.2.2.0 (2015/03/27) 021import java.io.BufferedInputStream; 022import java.io.FileNotFoundException; 023import java.io.File; 024import java.io.IOException; 025import java.nio.file.Files; // 6.2.2.0 (2015/03/27) 026import java.nio.charset.Charset; // 6.2.2.0 (2015/03/27) 027 028import java.util.Set; // 6.0.2.3 (2014/10/10) 029import java.util.TreeSet; // 6.0.2.3 (2014/10/10) 030import java.util.List; // 6.4.6.0 (2016/05/27) poi-3.15 031// import java.util.ArrayList; // 8.0.1.0 (2021/10/29) 032 033// import org.apache.xmlbeans.XmlException; // 8.0.0.0 (2021/07/31) Delete 034// import org.apache.poi.POITextExtractor; 035import org.apache.poi.extractor.POITextExtractor; // 7.0.0.0 (2018/10/01) poi-3.17.jar → poi-4.0.0.jar 036// import org.apache.poi.extractor.ExtractorFactory; 037// import org.apache.poi.ooxml.extractor.ExtractorFactory; // 7.0.0.0 (2018/10/01) poi-ooxml-3.17.jar → poi-ooxml-4.0.0.jar 038import org.apache.poi.ooxml.extractor.POIXMLExtractorFactory; // 8.0.0.0 (2021/07/31) poi-ooxml-4.0.0.jar → poi-ooxml-5.0.0.jar 039import org.apache.poi.hwpf.HWPFDocument; 040import org.apache.poi.hwpf.usermodel.Range; 041import org.apache.poi.hwpf.usermodel.Paragraph; 042import org.apache.poi.xwpf.usermodel.XWPFDocument; // 6.2.0.0 (2015/02/27) 043import org.apache.poi.xwpf.usermodel.XWPFParagraph; // 6.2.0.0 (2015/02/27) 044import org.apache.poi.hssf.usermodel.HSSFCellStyle; 045import org.apache.poi.hslf.usermodel.HSLFTextParagraph; // 6.4.6.0 (2016/05/27) poi-3.15 046import org.apache.poi.hslf.usermodel.HSLFSlide; // 6.4.6.0 (2016/05/27) poi-3.15 047import org.apache.poi.hslf.usermodel.HSLFSlideShow; // 6.4.6.0 (2016/05/27) poi-3.15 048 049import org.apache.poi.xslf.usermodel.XMLSlideShow; // 6.2.0.0 (2015/02/27) 050// import org.apache.poi.xslf.extractor.XSLFPowerPointExtractor; // 7.0.0.0 (2018/10/01) POI4.0.0 deprecation 051import org.apache.poi.sl.extractor.SlideShowExtractor; // 7.0.0.0 (2018/10/01) POI4.0.0 XSLFPowerPointExtractor → SlideShowExtractor 052import org.apache.poi.xslf.usermodel.XSLFShape; // 7.0.0.0 (2018/10/01) POI4.0.0 XSLFPowerPointExtractor → SlideShowExtractor 053import org.apache.poi.xslf.usermodel.XSLFTextParagraph; // 7.0.0.0 (2018/10/01) POI4.0.0 XSLFPowerPointExtractor → SlideShowExtractor 054 055// import org.apache.poi.openxml4j.exceptions.InvalidFormatException; // 8.0.0.0 (2021/07/31) Delete 056// import org.apache.poi.openxml4j.exceptions.OpenXML4JException ; // 6.1.0.0 (2014/12/26) findBugs // 8.0.0.0 (2021/07/31) Delete 057import org.apache.poi.ss.usermodel.WorkbookFactory; 058import org.apache.poi.ss.usermodel.Workbook; 059import org.apache.poi.ss.usermodel.Sheet; 060import org.apache.poi.ss.usermodel.Row; 061import org.apache.poi.ss.usermodel.Cell; 062import org.apache.poi.ss.usermodel.CellStyle; 063// import org.apache.poi.ss.usermodel.CreationHelper; 064import org.apache.poi.ss.usermodel.RichTextString; 065import org.apache.poi.ss.usermodel.DateUtil; 066// import org.apache.poi.ss.usermodel.FormulaEvaluator; 067import org.apache.poi.ss.usermodel.Name; // 6.0.2.3 (2014/10/10) 068import org.apache.poi.ss.usermodel.CellType; // 6.5.0.0 (2016/09/30) poi-3.15 069import org.apache.poi.ss.util.SheetUtil; 070 071import org.opengion.fukurou.system.OgRuntimeException ; // 6.4.2.0 (2016/01/29) 072import org.opengion.fukurou.util.FileInfo; // 6.2.3.0 (2015/05/01) 073import org.opengion.fukurou.system.ThrowUtil; // 6.4.2.0 (2016/01/29) 074import org.opengion.fukurou.system.Closer; // 6.2.0.0 (2015/02/27) 075import static org.opengion.fukurou.system.HybsConst.CR; // 6.1.0.0 (2014/12/26) refactoring 076import static org.opengion.fukurou.system.HybsConst. BUFFER_MIDDLE ; // 6.4.2.1 (2016/02/05) refactoring 077 078/** 079 * POI による、Excel/Word/PoworPoint等に対する、ユーティリティクラスです。 080 * 081 * 基本的には、ネイティブファイルを読み取り、テキストを取得する機能が主です。 082 * Excel、Word、PowerPoint、Visio、Publisher からのテキスト取得が可能です。 083 * 084 * @og.rev 6.0.2.0 (2014/09/19) 新規作成 085 * @og.rev 6.2.0.0 (2015/02/27) パッケージ変更(util → model) 086 * @og.group その他 087 * 088 * @version 6.0 089 * @author Kazuhiko Hasegawa 090 * @since JDK7.0, 091 */ 092public final class POIUtil { 093 /** このプログラムのVERSION文字列を設定します。 {@value} */ 094 private static final String VERSION = "8.0.3.0 (2021/12/17)" ; 095 096 // 6.2.3.0 (2015/05/01) 097 /** 対象サフィックス {@value} */ 098 public static final String POI_SUFIX = "ppt,pptx,doc,docx,xls,xlsx,xlsm" ; 099 100 /** 101 * すべてが staticメソッドなので、コンストラクタを呼び出さなくしておきます。 102 * 103 */ 104 private POIUtil() {} 105 106 /** 107 * 引数ファイルが、POI関連の拡張子ファイルかどうかを判定します。 108 * 109 * Excel、Word、PowerPoint、Visio、Publisher からのテキスト取得が可能です。 110 * 拡張子が、ppt,pptx,doc,docx,xls,xlsx,xlsm のファイルの場合、true を返します。 111 * 112 * @og.rev 6.2.3.0 (2015/05/01) POI関連の拡張子ファイルかどうかを判定 113 * 114 * @param file 判定するファイル 115 * @return POI関連の拡張子の場合、true 116 */ 117 public static boolean isPOI( final File file ) { 118 return POI_SUFIX.contains( FileInfo.getSUFIX( file ) ); 119 } 120 121 /** 122 * 引数ファイルを、POITextExtractor を使用してテキスト化します。 123 * 124 * Excel、Word、PowerPoint、Visio、Publisher からのテキスト取得が可能です。 125 * 拡張子から、ファイルの種類を自動判別します。 126 * 127 * @og.rev 6.0.2.0 (2014/09/19) 新規作成 128 * @og.rev 6.2.0.0 (2015/02/27) getText → extractor に変更 129 * @og.rev 8.0.0.0 (2021/07/31) ExtractorFactory → POIXMLExtractorFactory に変更 130 * 131 * @param file 入力ファイル名 132 * @return 変換後のテキスト 133 * @og.rtnNotNull 134 */ 135 public static String extractor( final File file ) { 136 // InputStream fis = null; 137 POITextExtractor extractor = null; 138 try { 139 // fis = new BufferedInputStream( new FileInputStream( file ) ); 140 // extractor = ExtractorFactory.createExtractor( fis ); 141 // extractor = ExtractorFactory.createExtractor( file ); 142 extractor = new POIXMLExtractorFactory().create( file , null ); // 8.0.0.0 (2021/07/31) poi-ooxml-4.0.0.jar → poi-ooxml-5.0.0.jar 143 return extractor.getText(); 144 } 145 catch( final FileNotFoundException ex ) { 146 final String errMsg = "ファイルが存在しません[" + file + "]" + CR + ex.getMessage() ; 147 throw new OgRuntimeException( errMsg,ex ); 148 } 149 catch( final IOException ex ) { 150 final String errMsg = "ファイル処理エラー[" + file + "]" + CR + ex.getMessage() ; 151 throw new OgRuntimeException( errMsg,ex ); 152 } 153 // 8.0.0.0 (2021/07/31) poi-ooxml-4.0.0.jar → poi-ooxml-5.0.0.jar 154// catch( final InvalidFormatException ex ) { 155// final String errMsg = "ファイルフォーマットエラー[" + file + "]" + CR + ex.getMessage() ; 156// throw new OgRuntimeException( errMsg,ex ); 157// } 158// catch( final OpenXML4JException ex ) { 159// final String errMsg = "ODF-XML処理エラー[" + file + "]" + CR + ex.getMessage() ; 160// throw new OgRuntimeException( errMsg,ex ); 161// } 162// catch( final XmlException ex ) { 163// final String errMsg = "XML処理エラー[" + file + "]" + CR + ex.getMessage() ; 164// throw new OgRuntimeException( errMsg,ex ); 165// } 166 finally { 167 Closer.ioClose( extractor ); 168 // Closer.ioClose( fis ); 169 } 170 } 171 172 /** 173 * 引数ファイル(Text)を、テキスト化します。 174 * 175 * ここでは、ファイルとエンコードを指定して、ファイルのテキスト全てを読み取ります。 176 * 177 * @og.rev 6.2.2.0 (2015/03/27) 引数ファイル(Text)を、テキスト化。 178 * @og.rev 6.2.3.0 (2015/05/01) textReader → extractor に変更 179 * 180 * @param file 入力ファイル 181 * @param encode エンコード名 182 * @return ファイルのテキスト 183 */ 184 public static String extractor( final File file , final String encode ) { 185 try { 186 // 指定のファイルをバイト列として読み込む 187 final byte[] bytes = Files.readAllBytes( file.toPath() ); 188 // 読み込んだバイト列を エンコードして文字列にする 189 return new String( bytes, encode ); 190 } 191 // catch( final UnsupportedEncodingException ex ) { 192 // final String errMsg = "エンコードが不正です[" + file + "] , ENCODE=[" + encode + "]" ; 193 // throw new OgRuntimeException( errMsg,ex ); 194 // } 195 catch( final IOException ex ) { 196 final String errMsg = "ファイル読込みエラー[" + file + "] , ENCODE=[" + encode + "]" ; 197 throw new OgRuntimeException( errMsg,ex ); 198 } 199 } 200 201 /** 202 * 引数ファイル(Text)を、テキスト化します。 203 * 204 * ここでは、ファイルとエンコードを指定して、ファイルのテキスト全てを読み取ります。 205 * 206 * @og.rev 6.2.2.0 (2015/03/27) 引数ファイル(Text)を、テキスト化。 207 * 208 * @param file 入力ファイル 209 * @param conv イベント処理させるI/F 210 * @param encode エンコード名 211 */ 212 public static void textReader( final File file , final TextConverter<String,String> conv , final String encode ) { 213 BufferedReader reader = null ; 214 215 int rowNo = 0; // 6.2.0.0 (2015/02/27) 行番号 216 try { 217 reader = Files.newBufferedReader( file.toPath() , Charset.forName( encode ) ); 218 219 String line ; 220 while((line = reader.readLine()) != null) { 221 conv.change( line,String.valueOf( rowNo++ ) ); 222 } 223 } 224 catch( final IOException ex ) { 225 final String errMsg = "ファイル読込みエラー[" + file + "] , ENCODE=[" + encode + "] , ROW=[" + rowNo + "]" ; 226 throw new OgRuntimeException( errMsg,ex ); 227 } 228 finally { 229 Closer.ioClose( reader ); 230 } 231 } 232 233 /** 234 * 引数ファイル(Word,PoworPoint,Excel)を、TableModelHelper を使用してテキスト化します。 235 * 236 * ここでは、ファイル名の拡張子で、処理するメソッドを選別します。 237 * 拡張子が、対象かどうかは、#isPOI( File ) メソッドで判定できます。 238 * 239 * TableModelHelper によるイベント処理できますが、TEXTというカラム名を持つ 240 * 表形式オブジェクトの形で処理されます。 241 * また、内部的に、先頭に、# がある場合や、行データが存在しない場合は、 242 * スキップされます。 243 * 244 * @og.rev 6.2.3.0 (2015/05/01) 新規作成 245 * @og.rev 6.2.5.0 (2015/06/05) xls,xlsxは、それぞれ excelReader1,excelReader2 で処理します。 246 * 247 * @param file 入力ファイル 248 * @param conv イベント処理させるI/F 249 */ 250 public static void textReader( final File file , final TextConverter<String,String> conv ) { 251 final String SUFIX = FileInfo.getSUFIX( file ); 252 253 if( "doc".equalsIgnoreCase( SUFIX ) ) { 254 wordReader1( file,conv ); 255 } 256 else if( "docx".equalsIgnoreCase( SUFIX ) ) { 257 wordReader2( file,conv ); 258 } 259 else if( "ppt".equalsIgnoreCase( SUFIX ) ) { 260 pptReader1( file,conv ); 261 } 262 else if( "pptx".equalsIgnoreCase( SUFIX ) ) { 263 pptReader2( file,conv ); 264 } 265 else if( "xls".equalsIgnoreCase( SUFIX ) ) { 266 excelReader1( file,conv ); // 6.2.5.0 (2015/06/05) 267 } 268 else if( "xlsx".equalsIgnoreCase( SUFIX ) || "xlsm".equalsIgnoreCase( SUFIX ) ) { 269 excelReader2( file,conv ); // 6.2.5.0 (2015/06/05) 270 } 271 else { 272 final String errMsg = "拡張子は、" + POI_SUFIX + " にしてください。[" + file + "]" ; 273 throw new OgRuntimeException( errMsg ); 274 } 275 } 276 277 /** 278 * 引数ファイル(Word)を、HWPFDocument を使用してテキスト化します。 279 * 280 * 拡張子(.doc)のファイルを処理します。 281 * TableModelHelper によるイベント処理できますが、TEXTというカラム名を持つ 282 * 表形式オブジェクトの形で処理されます。 283 * また、内部的に、先頭に、# がある場合や、行データが存在しない場合は、 284 * スキップされます。 285 * 286 * @og.rev 6.0.2.0 (2014/09/19) 新規作成 287 * @og.rev 6.2.0.0 (2015/02/27) POIEvent → TableModelHelper に変更 288 * @og.rev 6.2.4.2 (2015/05/29) 改行以外に、「。」で分割します。 289 * 290 * @param file 入力ファイル名 291 * @param conv イベント処理させるI/F 292 */ 293 private static void wordReader1( final File file , final TextConverter<String,String> conv ) { 294 InputStream fis = null; 295 296 try { 297 // 6.2.0.0 (2015/02/27) TableModelHelper 変更に伴う修正 298 299 fis = new BufferedInputStream( new FileInputStream( file ) ); // 6.2.0.0 (2015/02/27) 300 final HWPFDocument doc = new HWPFDocument( fis ); 301 302 int rowNo = 0; // 6.2.0.0 (2015/02/27) 行番号 303 304 // // WordExtractor を使ったサンプル 305 // WordExtractor we = new WordExtractor( doc ); 306 // for( String txt : we.getParagraphText() ) { 307 // String text = WordExtractor.stripFields( txt ) 308 // .replaceAll( "\\x13[^\\x01]+\\x01\\x14" , "" ) 309 // .replaceAll( "\\x0b" , "\n" ).trim(); 310 // helper.value( text.trim(),rowNo++,0 ); // 6.2.0.0 (2015/02/27) イベント変更 311 // } 312 313 // Range,Paragraph を使ったサンプル 314 final Range rng = doc.getRange(); 315 for( int pno=0; pno<rng.numParagraphs(); pno++ ) { 316 final Paragraph para = rng.getParagraph(pno); 317 final String text = Range.stripFields( para.text() ) 318 .replaceAll( "\\x13[^\\x01]+\\x01\\x14" , "" ) 319 .replaceAll( "\\x0b" , "\n" ).trim(); 320 conv.change( text, String.valueOf( rowNo++ ) ); 321 } 322 323 // Range,Paragraph,CharacterRun を使ったサンプル(変な個所で文字が分断される) 324 // final Range rng = doc.getRange(); 325 // for( int pno = 0; pno < rng.numParagraphs(); pno++ ) { 326 // final Paragraph para = rng.getParagraph(pno); 327 // for( int cno = 0; cno < para.numCharacterRuns(); cno++ ) { 328 // final CharacterRun crun = para.getCharacterRun(cno); 329 // String text = Range.stripFields( crun.text() ) 330 // .replaceAll( "\\x13[^\\x01]+\\x01\\x14" , "" ) 331 // .replaceAll( "\\x0b" , "\n" ).trim(); 332 // helper.value( text,rowNo++,0 ); // 6.2.0.0 (2015/02/27) イベント変更 333 // } 334 // } 335 } 336 catch( final IOException ex ) { 337 final String errMsg = "ファイル読込みエラー[" + file + "]" + CR + ex.getMessage() ; 338 throw new OgRuntimeException( errMsg,ex ); 339 } 340 finally { 341 Closer.ioClose( fis ); 342 } 343 } 344 345 /** 346 * 引数ファイル(Word)を、XWPFDocument を使用してテキスト化します。 347 * 348 * 拡張子(.docx)のファイルを処理します。 349 * TableModelHelper によるイベント処理できますが、TEXTというカラム名を持つ 350 * 表形式オブジェクトの形で処理されます。 351 * また、内部的に、先頭に、# がある場合や、行データが存在しない場合は、 352 * スキップされます。 353 * 354 * @og.rev 6.0.2.0 (2014/09/19) 新規作成 355 * @og.rev 6.2.0.0 (2015/02/27) POIEvent → TableModelHelper に変更 356 * @og.rev 6.2.4.2 (2015/05/29) 改行以外に、「。」で分割します。 357 * 358 * @param file 入力ファイル 359 * @param conv イベント処理させるI/F 360 */ 361 private static void wordReader2( final File file , final TextConverter<String,String> conv ) { 362 InputStream fis = null; 363 364 try { 365 // 6.2.0.0 (2015/02/27) TableModelHelper 変更に伴う修正 366 367 fis = new BufferedInputStream( new FileInputStream( file ) ); // 6.2.0.0 (2015/02/27) 368 final XWPFDocument doc = new XWPFDocument( fis ); 369 370 int rowNo = 0; // 6.2.0.0 (2015/02/27) 行番号 371 for( final XWPFParagraph para : doc.getParagraphs() ) { 372 // for( final XWPFRun run : para.getRuns() ) { 373 // helper.value( run.toString(),rowNo++,0 ); // 6.2.0.0 (2015/02/27) イベント変更 374 // } 375 final String text = para.getParagraphText().trim(); 376 conv.change( text, String.valueOf( rowNo++ ) ); 377 } 378 } 379 catch( final IOException ex ) { 380 final String errMsg = "ファイル読込みエラー[" + file + "]" + CR + ex.getMessage() ; 381 throw new OgRuntimeException( errMsg,ex ); 382 } 383 finally { 384 Closer.ioClose( fis ); 385 } 386 } 387 388 /** 389 * 引数ファイル(PoworPoint)を、HSLFSlideShow を使用してテキスト化します。 390 * 391 * 拡張子(.ppt)のファイルを処理します。 392 * TableModelHelper によるイベント処理できますが、TEXTというカラム名を持つ 393 * 表形式オブジェクトの形で処理されます。 394 * また、内部的に、先頭に、# がある場合や、行データが存在しない場合は、 395 * スキップされます。 396 * 397 * @og.rev 6.0.2.0 (2014/09/19) 新規作成 398 * @og.rev 6.2.0.0 (2015/02/27) POIEvent → TableModelHelper に変更 399 * @og.rev 6.4.6.0 (2016/05/27) poi-3.15 準備 400 * 401 * @param file 入力ファイル 402 * @param conv イベント処理させるI/F 403 */ 404 private static void pptReader1( final File file , final TextConverter<String,String> conv ) { 405 InputStream fis = null; 406 407 try { 408 // 6.2.0.0 (2015/02/27) TableModelHelper 変更に伴う修正 409 410 fis = new BufferedInputStream( new FileInputStream( file ) ); // 6.2.0.0 (2015/02/27) 411 412 // 6.4.6.0 (2016/05/27) poi-3.15 413 final HSLFSlideShow ss = new HSLFSlideShow( fis ); 414 final List<HSLFSlide> slides = ss.getSlides(); // 6.4.6.0 (2016/05/27) poi-3.15 415 int rowNo = 0; // 6.2.0.0 (2015/02/27) 行番号 416 for( final HSLFSlide slide : slides ) { // 6.4.6.0 (2016/05/27) poi-3.15 417 for( final List<HSLFTextParagraph> txtList : slide.getTextParagraphs() ) { // 6.4.6.0 (2016/05/27) poi-3.15 418 final String text = HSLFTextParagraph.getText( txtList ); 419 if( text.length() > 0 ) { 420 conv.change( text, String.valueOf( rowNo++ ) ); 421 } 422 } 423 } 424 425 // 6.4.6.0 (2016/05/27) poi-3.12 426 // final SlideShow ss = new SlideShow( new HSLFSlideShow( fis ) ); 427 // final Slide[] slides = ss.getSlides(); 428 // int rowNo = 0; // 6.2.0.0 (2015/02/27) 行番号 429 // for( int sno=0; sno<slides.length; sno++ ) { 430 // final TextRun[] textRun = slides[sno].getTextRuns(); 431 // for( int tno=0; tno<textRun.length; tno++ ) { 432 // final String text = textRun[tno].getText(); 433 // // データとして設定されているレコードのみイベントを発生させる。 434 // if( text.length() > 0 ) { 435 // conv.change( text, String.valueOf( rowNo++ ) ); 436 // } 437 // } 438 // } 439 } 440 catch( final IOException ex ) { 441 final String errMsg = "ファイル読込みエラー[" + file + "]" + CR + ex.getMessage() ; 442 throw new OgRuntimeException( errMsg,ex ); 443 } 444 finally { 445 Closer.ioClose( fis ); 446 } 447 } 448 449 /** 450 * 引数ファイル(PoworPoint)を、XMLSlideShow を使用してテキスト化します。 451 * 452 * 拡張子(.pptx)のファイルを処理します。 453 * TableModelHelper によるイベント処理できますが、TEXTというカラム名を持つ 454 * 表形式オブジェクトの形で処理されます。 455 * また、内部的に、先頭に、# がある場合や、行データが存在しない場合は、 456 * スキップされます。 457 * 458 * @og.rev 6.0.2.0 (2014/09/19) 新規作成 459 * @og.rev 6.2.0.0 (2015/02/27) POIEvent → TableModelHelper に変更 460 * @og.rev 6.2.4.2 (2015/05/29) 行単位に、取り込むようにします。 461 * @og.rev 7.0.0.0 (2018/10/01) 警告:[deprecation] org.apache.poi.xslf.extractorのXSLFPowerPointExtractorは推奨されません (POI4.0.0) 462 * 463 * @param file 入力ファイル 464 * @param conv イベント処理させるI/F 465 */ 466 private static void pptReader2( final File file , final TextConverter<String,String> conv ) { 467 InputStream fis = null; 468 469 try { 470 // 6.2.0.0 (2015/02/27) TableModelEvent 変更に伴う修正 471 472 fis = new BufferedInputStream( new FileInputStream( file ) ); // 6.2.0.0 (2015/02/27) 473 final XMLSlideShow ss = new XMLSlideShow( fis ); 474// final XSLFPowerPointExtractor ext = new XSLFPowerPointExtractor( ss ); 475 final SlideShowExtractor<XSLFShape,XSLFTextParagraph> ext = new SlideShowExtractor<>( ss ); // 7.0.0.0 (2018/10/01) poi-4.0.0 Deprecated. 476 final String[] vals = ext.getText().split( "\\n" ); // 6.2.4.2 (2015/05/29) 行単位に、取り込むようにします。 477 for( int row=0; row<vals.length; row++ ) { 478 conv.change( vals[row], String.valueOf( row ) ); 479 } 480 481 // final XSLFSlide[] slides = ss.getSlides(); 482 // final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE ); 483 // int rowNo = 0; // 6.2.0.0 (2015/02/27) 行番号 484 // for( int sno = 0; sno < slides.length; sno++ ) { 485 // buf.setLength(0); // Clearの事 486 // 487 // // final XSLFTextShape[] shp = slides[sno].getPlaceholders(); 488 // final XSLFShape[] shp = slides[sno].getShapes(); 489 // for( int tno = 0; tno < shp.length; tno++ ) { 490 // // buf.append( shp[tno].getText() ); 491 // buf.append( shp[tno].toString() ); 492 // } 493 // // String text = buf.toString().trim(); 494 // // event.value( text,rowNo++,0 ); // 6.2.0.0 (2015/02/27) イベント変更 495 // helper.value( buf.toString(),rowNo++,0 ); // 6.2.4.2 (2015/05/29) trim() しません。 496 // } 497 } 498 catch( final IOException ex ) { 499 final String errMsg = "ファイル読込みエラー[" + file + "]" + CR + ex.getMessage() ; 500 throw new OgRuntimeException( errMsg,ex ); 501 } 502 finally { 503 Closer.ioClose( fis ); 504 } 505 } 506 507 /** 508 * 引数ファイル(Excel)を、テキスト化します。 509 * 510 * TableModelHelper を与えることで、EXCELデータをテキスト化できます。 511 * ここでは、HSSF(.xls)形式を処理します。 512 * シート名、セル、テキストオブジェクトをテキスト化します。 513 * 514 * @og.rev 6.2.5.0 (2015/06/05) 新規作成 515 * 516 * @param file 入力ファイル 517 * @param conv イベント処理させるI/F 518 * @see org.opengion.fukurou.model.ExcelModel 519 */ 520 public static void excelReader1( final File file , final TextConverter<String,String> conv ) { 521 excelReader2( file , conv ); 522 } 523 524 /** 525 * 引数ファイル(Excel)を、テキスト化します。 526 * 527 * TableModelHelper を与えることで、EXCELデータをテキスト化できます。 528 * ここでは、ExcelModelを使用して、(.xlsx , .xlsm)形式を処理します。 529 * シート名、セル、テキストオブジェクトをテキスト化します。 530 * 531 * @og.rev 6.2.5.0 (2015/06/05) 新規作成 532 * @og.rev 6.3.1.0 (2015/06/28) TextConverterに、引数(cmnt)を追加 533 * @og.rev 6.3.9.0 (2015/11/06) Java 8 ラムダ式に変更 534 * 535 * @param file 入力ファイル 536 * @param conv イベント処理させるI/F 537 * @see org.opengion.fukurou.model.ExcelModel 538 */ 539 public static void excelReader2( final File file , final TextConverter<String,String> conv ) { 540 final ExcelModel excel = new ExcelModel( file, true ); 541 542 // 6.3.9.0 (2015/11/06) Java 8 ラムダ式に変更 543 // textConverter を使いますが、テキストを読み込むだけで、変換しません。 544 excel.textConverter( 545 ( val,cmnt ) -> { 546 conv.change( val,cmnt ); // 変換したくないので、引数の TextConverter を直接渡せない。 547 return null; // nullを返せば、変換しません。 548 } 549 ); 550 } 551 552 /** 553 * Excelの行列記号を、行番号と列番号に分解します。 554 * 555 * Excelの行列記号とは、A1 , B5 , AA23 などの形式を指します。 556 * これを、行番号と列番号に分解します。例えば、A1→0行0列、B5→4行1列、AA23→22行26列 となります。 557 * 分解した結果は、内部変数の、rowNo と colNo にセットされます。 558 * これらは、0 から始まる int型の数字で表します。 559 * 560 * ①行-列形式 561 * 行列は、EXCELオブジェクトに準拠するため、0から始まる整数です。 562 * 0-0 ⇒ A1 , 1-0 ⇒ A2 , 0-1 ⇒ B1 になります。 563 * ②EXCEL表記 564 * EXCEL表記に準拠した、A1,A2,B1 の記述も処理できるように対応します。 565 * なお、A1,A2,B1 の記述は、必ず、英字1文字+数字 にしてください。(A~Zまで) 566 * ③EXCELシート名をキーに割り当てるために、"SHEET" という記号に対応します。 567 * rowNo = -1 をセットします。 568 * 569 * @og.rev 6.0.3.0 (2014/11/13) 新規作成 570 * @og.rev 6.2.6.0 (2015/06/19) 行-列形式と、SHEET文字列判定を採用。 571 * 572 * @param kigo Excelの行列記号( A1 , B5 , AA23 など ) 573 * @return 行と列の番号を持った配列([0]=行=ROW , [1]=列=COL) 574 * @og.rtnNotNull 575 */ 576 public static int[] kigo2rowCol( final String kigo ) { 577 int rowNo = 0; 578 int colNo = -1; // +1 して、26 かける処理をしているので、辻褄合わせ 579 580 // 6.2.6.0 (2015/06/19) 行-列形式と、SHEET文字列判定を採用。 581 if( "SHEET".equalsIgnoreCase( kigo ) ) { 582 rowNo = -1; 583 } 584 else { 585 final int adrs = kigo.indexOf( '-' ); 586 if( adrs > 0 ) { 587 rowNo = Integer.parseInt( kigo.substring( 0,adrs ) ); 588 colNo = Integer.parseInt( kigo.substring( adrs+1 ) ); 589 } 590 else { 591 for( int i=0; i<kigo.length(); i++ ) { 592 final char ch = kigo.charAt(i); 593 if( 'A' <= ch && ch <= 'Z' ) { colNo = (colNo+1)*26 + ch-'A'; } 594 else { 595 // アルファベットでなくなったら、残りは 行番号(ただし、-1する) 596 rowNo = Integer.parseInt( kigo.substring( i ) ) -1; 597 break; 598 } 599 } 600 } 601 } 602 return new int[] { rowNo,colNo }; 603 } 604 605 /** 606 * セルオブジェクト(Cell)から値を取り出します。 607 * 608 * セルオブジェクトが存在しない場合は、null を返します。 609 * それ以外で、うまく値を取得できなかった場合は、ゼロ文字列を返します。 610 * 611 * @og.rev 3.8.5.3 (2006/08/07) 取り出し方法を少し修正 612 * @og.rev 5.5.1.2 (2012/04/06) フォーマットセルを実行して、その結果を再帰的に処理する。 613 * @og.rev 6.0.3.0 (2014/11/13) セルフォーマットエラー時に、RuntimeException を throw しない。 614 * @og.rev 6.4.2.0 (2016/01/29) StringUtil#ogStackTrace(Throwable) を、ThrowUtil#ogStackTrace(String,Throwable) に置き換え。 615 * @og.rev 6.5.0.0 (2016/09/30) poi-3.15 対応(Cell.CELL_TYPE_XXXX → CellType.XXXX) 616 * @og.rev 7.0.0.0 (2018/10/01) 警告:[deprecation] CellのgetCellTypeEnum()は推奨されません (POI4.0.0) 617 * @og.rev 7.3.0.0 (2021/01/06) フォーマットエラー時に、エラーメッセージ取得でもエラーになる。 618 * @og.rev 8.0.0.0 (2021/07/31) FORMULA処理のエラー対応(出来るだけ…) 619 * 620 * @param oCell EXCELのセルオブジェクト 621 * 622 * @return セルの値 623 */ 624 public static String getValue( final Cell oCell ) { 625 if( oCell == null ) { return null; } 626 String strText = ""; 627 // final int nCellType = oCell.getCellType(); // 6.5.0.0 (2016/09/30) poi-3.12 628 // switch(nCellType) { // 6.5.0.0 (2016/09/30) poi-3.12 629// switch( oCell.getCellTypeEnum() ) { // 6.5.0.0 (2016/09/30) poi-3.15 630 switch( oCell.getCellType() ) { // 7.0.0.0 (2018/10/01) poi-4.0.0 Deprecated. 631 // case Cell.CELL_TYPE_NUMERIC: // 6.5.0.0 (2016/09/30) poi-3.12 632 case NUMERIC: // 6.5.0.0 (2016/09/30) poi-3.15 633 strText = getNumericTypeString( oCell ); 634 break; 635 // case Cell.CELL_TYPE_STRING: // 6.5.0.0 (2016/09/30) poi-3.12 636 case STRING: // 6.5.0.0 (2016/09/30) poi-3.15 637 // POI3.0 strText = oCell.getStringCellValue(); 638 final RichTextString richText = oCell.getRichStringCellValue(); 639 if( richText != null ) { 640 strText = richText.getString(); 641 } 642 break; 643 // case Cell.CELL_TYPE_FORMULA: // 6.5.0.0 (2016/09/30) poi-3.12 644 case FORMULA: // 6.5.0.0 (2016/09/30) poi-3.15 645 // POI3.0 strText = oCell.getStringCellValue(); 646 // 5.5.1.2 (2012/04/06) フォーマットセルを実行して、その結果を再帰的に処理する。 647 // final Workbook wb = oCell.getSheet().getWorkbook(); 648 // final CreationHelper crateHelper = wb.getCreationHelper(); 649 // final FormulaEvaluator evaluator = crateHelper.createFormulaEvaluator(); 650 651 try { 652 strText = oCell.getCellFormula(); 653 // 8.0.0.0 (2021/07/31) FORMULA処理のエラー対応(出来るだけ…) 654 // final Cell fCell = evaluator.evaluateInCell(oCell); 655 // strText = getValue( fCell ); 656 } 657 catch( final Throwable th ) { 658 // 7.3.0.0 (2021/01/06) フォーマットエラー時に、エラーメッセージ取得でもエラーになる。 659 final String errMsg = "セルフォーマットが解析できません。"; 660 // final String errMsg = "セルフォーマットが解析できません。Formula=[" + oCell.getCellFormula() + "]"; 661 // + CR + getCellMsg( oCell ); 662 // throw new OgRuntimeException( errMsg,th ); 663 System.err.println( ThrowUtil.ogStackTrace( errMsg,th ) ); // 6.4.2.0 (2016/01/29) 664 } 665 break; 666 // case Cell.CELL_TYPE_BOOLEAN: // 6.5.0.0 (2016/09/30) poi-3.12 667 case BOOLEAN: // 6.5.0.0 (2016/09/30) poi-3.15 668 strText = String.valueOf(oCell.getBooleanCellValue()); 669 break; 670 // case Cell.CELL_TYPE_BLANK : // 6.5.0.0 (2016/09/30) poi-3.12 671 case BLANK : // 6.5.0.0 (2016/09/30) poi-3.15 672 break; 673 // case Cell.CELL_TYPE_ERROR: // 6.5.0.0 (2016/09/30) poi-3.12 674 case ERROR: // 6.5.0.0 (2016/09/30) poi-3.15 675 break; 676 default : 677 break; 678 } 679 return strText ; 680 } 681 682 /** 683 * セルオブジェクト(Cell)に、値をセットします。 684 * 685 * セルオブジェクトが存在しない場合は、何もしません。 686 * 引数は、文字列で渡しますが、セルの形式に合わせて、変換します。 687 * 変換がうまくいかなかった場合は、エラーになりますので、ご注意ください。 688 * 689 * @og.rev 6.3.9.0 (2015/11/06) 新規追加 690 * @og.rev 6.5.0.0 (2016/09/30) poi-3.15 対応(Cell.CELL_TYPE_XXXX → CellType.XXXX) 691 * @og.rev 7.0.0.0 (2018/10/01) 警告:[deprecation] CellのgetCellTypeEnum()は推奨されません (POI4.0.0) 692 * @og.rev 7.3.0.0 (2021/01/06) setCellType( CellType.BLANK )(Deprecated) → setBlank() (poi-4.1.2) 693 * 694 * @param oCell EXCELのセルオブジェクト 695 * @param val セットする値 696 */ 697 public static void setValue( final Cell oCell , final String val ) { 698 if( oCell == null ) { return ; } 699 // if( val == null || val.isEmpty() ) { oCell.setCellType( Cell.CELL_TYPE_BLANK ); } // 6.5.0.0 (2016/09/30) poi-3.12 700 // if( val == null || val.isEmpty() ) { oCell.setCellType( CellType.BLANK ); } // 6.5.0.0 (2016/09/30) poi-3.15 701 if( val == null || val.isEmpty() ) { oCell.setBlank(); } // 7.3.0.0 (2021/01/06) poi-4.1.2 702 703 // switch( oCell.getCellType() ) { // 6.5.0.0 (2016/09/30) poi-3.12 704// switch( oCell.getCellTypeEnum() ) { // 6.5.0.0 (2016/09/30) poi-3.15 705 switch( oCell.getCellType() ) { // 7.0.0.0 (2018/10/01) poi-4.0.0 Deprecated. 706 // case Cell.CELL_TYPE_NUMERIC: // 6.5.0.0 (2016/09/30) poi-3.12 707 case NUMERIC: // 6.5.0.0 (2016/09/30) poi-3.15 708// oCell.setCellValue( Double.valueOf( val ) ); 709 oCell.setCellValue( Double.parseDouble( val ) ); // 7.3.0.0 (2021/01/06) SpotBugs 疑わしいプリミティブ値のボクシング 710 break; 711 // case Cell.CELL_TYPE_BOOLEAN: // 6.5.0.0 (2016/09/30) poi-3.12 712 case BOOLEAN: // 6.5.0.0 (2016/09/30) poi-3.15 713 oCell.setCellValue( "true".equalsIgnoreCase( val ) ); 714 break; 715 default : 716 oCell.setCellValue( val ); 717 break; 718 } 719 } 720 721 /** 722 * セル値が数字の場合に、数字か日付かを判断して、対応する文字列を返します。 723 * 724 * @og.rev 3.8.5.3 (2006/08/07) 新規追加 725 * @og.rev 5.5.7.2 (2012/10/09) HybsDateUtil を利用するように修正します。 726 * @og.rev 6.3.1.0 (2015/06/28) ExcelStyleFormat を使用します。 727 * 728 * @param oCell EXCELのセルオブジェクト 729 * 730 * @return 数字の場合は、文字列に変換した結果を、日付の場合は、"yyyyMMddHHmmss" 形式で返します。 731 */ 732 public static String getNumericTypeString( final Cell oCell ) { 733 final String strText ; 734 735 final double dd = oCell.getNumericCellValue() ; 736 if( DateUtil.isCellDateFormatted( oCell ) ) { 737 // strText = DateSet.getDate( DateUtil.getJavaDate( dd ).getTime() , "yyyyMMddHHmmss" ); // 5.5.7.2 (2012/10/09) HybsDateUtil を利用 738 strText = ExcelStyleFormat.dateFormat( dd ); 739 } 740 else { 741 // final NumberFormat numFormat = NumberFormat.getInstance(); 742 // if( numFormat instanceof DecimalFormat ) { 743 // ((DecimalFormat)numFormat).applyPattern( "#.####" ); 744 // } 745 // strText = numFormat.format( dd ); 746 final String fmrs = oCell.getCellStyle().getDataFormatString(); 747 strText = ExcelStyleFormat.getNumberValue( fmrs,dd ); 748 } 749 return strText ; 750 } 751 752 /** 753 * 全てのSheetに対して、autoSizeColumn設定を行います。 754 * 755 * 重たい処理なので、ファイルの書き出し直前に一度だけ実行するのがよいでしょう。 756 * autoSize設定で、カラム幅が大きすぎる場合、現状では、 757 * 初期カラム幅のmaxColCount倍を限度に設定します。 758 * ただし、maxColCount がマイナスの場合は、無制限になります。 759 * 760 * @og.rev 6.0.2.0 (2014/09/19) 新規作成 761 * @og.rev 6.8.2.4 (2017/11/20) rowObj のnull対策(poi-3.17) 762 * 763 * @param wkbook 処理対象のWorkbook 764 * @param maxColCount 最大幅を標準セル幅の何倍にするかを指定。マイナスの場合は、無制限 765 * @param dataStRow データ行の開始位置。未設定時は、-1 766 */ 767 public static void autoCellSize( final Workbook wkbook , final int maxColCount , final int dataStRow ) { 768 final int shCnt = wkbook.getNumberOfSheets(); 769 770 for( int shNo=0; shNo<shCnt; shNo++ ) { 771 final Sheet sht = wkbook.getSheetAt( shNo ); 772 final int defW = sht.getDefaultColumnWidth(); // 標準カラムの文字数 773 final int maxWidth = defW*256*maxColCount ; // Widthは、文字数(文字幅)*256*最大セル数 774 775 int stR = sht.getFirstRowNum(); 776 final int edR = sht.getLastRowNum(); 777 778 // 6.8.2.4 (2017/11/20) rowObj のnull対策(poi-3.17) 779 // poi-3.15 でも同じ現象が出ていますが、sht.getRow( stR ) で、Rowオブジェクトにnullが返ってきます。 780 // なんとなく、最後の行だけ、返ってきている感じです。 781 // 頻繁には使わないと思いますので、最大カラム数=256 として処理します。 782 783 final Row rowObj = sht.getRow( stR ); 784 // Row rowObj = sht.getRow( stR ); 785 // if( rowObj == null ) { 786 // for( int i=stR+1; i<edR; i++ ) { 787 // rowObj = sht.getRow( i ); 788 // if( rowObj != null ) { break; } 789 // } 790 // } 791 792 final int stC = rowObj == null ? 0 : rowObj.getFirstCellNum(); // 6.8.2.4 (2017/11/20) rowObj のnull対策 793 final int edC = rowObj == null ? 256 : rowObj.getLastCellNum(); // 含まない (xlsxでは、最大 16,384 列 794 795 // SheetUtil を使用して、計算範囲を指定します。 796 if( stR < dataStRow ) { stR = dataStRow; } // 計算範囲 797 for( int colNo=stC; colNo<edC; colNo++ ) { 798 final double wpx = SheetUtil.getColumnWidth( sht,colNo,true,stR,edR ); 799 if( wpx >= 0.0d ) { // Cellがないと、マイナス値が戻る。 800 int wd = (int)Math.ceil(wpx * 256) ; 801 if( maxWidth >= 0 && wd > maxWidth ) { wd = maxWidth; } // 最大値が有効な場合は、置き換える 802 sht.setColumnWidth( colNo,wd ); 803 } 804 } 805 806 // Sheet#autoSizeColumn(int) を使用して、自動計算させる場合。 807 // for( int colNo=stC; colNo<edC; colNo++ ) { 808 // sht.autoSizeColumn( colNo ); 809 // if( maxWidth >= 0 ) { // 最大値が有効な場合は、置き換える 810 // int wd = sht.getColumnWidth( colNo ); 811 // if( wd > maxWidth ) { sht.setColumnWidth( colNo,maxWidth ); } 812 // } 813 // } 814 } 815 } 816 817// /** 818// * 指定の Workbook の全Sheetを対象に、実際の有効行と有効カラムを取得します。 819// * 820// * ※ 現在、唯一LibreOfficeでのみ、xslx 変換できますが、有効行とカラムが 821// * シュリンクされず、無駄な行とカラムが存在します。 822// * これは、xsl で出力されたファイルから有効な値を取得して、xslxに適用させるための 823// * 機能で、本来きちんとした有効範囲の xslx が生成されれば、不要な処理です。 824// * 825// * 配列は、[0]=行の最大値(Sheet#getLastRowNum())と、[1]は有効行の中の列の 826// * 最大値(Row#getLastCellNum())を、シートごとにListに追加していきます。 827// * 828// * @og.rev 8.0.1.0 (2021/10/29) 全Sheetを対象に、実際の有効行と有効カラムを取得 829// * @og.rev 8.0.3.0 (2021/12/17) 処理が中途半端だったので、廃止します。 830// * 831// * @param wkbook 処理対象のWorkbook 832// * @return シートごとの有効行の配列リスト 833// * @see #activeWorkbook( Workbook,List ) 834// */ 835// public static List<int[]> getLastRowCellNum( final Workbook wkbook ) { 836// final List<int[]> rcList = new ArrayList<>(); // シートごとの有効行の配列リスト 837// 838// final int shCnt = wkbook.getNumberOfSheets(); 839// for( int shNo=0; shNo<shCnt; shNo++ ) { 840// final Sheet sht = wkbook.getSheetAt( shNo ); 841// final int stR = sht.getFirstRowNum(); 842// final int edR = sht.getLastRowNum(); 843// int lastNo = 0; // 行の有効最大値 844// for( int rowNo=edR; rowNo>=stR && rowNo>=0; rowNo-- ) { // 逆順に処理します。 845// final Row rowObj = sht.getRow( rowNo ); 846// if( rowObj != null ) { 847// final int edC = rowObj.getLastCellNum(); // 列の有効最大値 848// if( lastNo < edC ) { lastNo = edC; } // シート内での列の最大有効値 849// } 850// } 851// rcList.add( new int[] {edR,lastNo} ); // 有効行の配列 852// } 853// return rcList; 854// } 855 856 /** 857 * 指定の Workbook の全Sheetを対象に、空行を取り除き、全体をシュリンクします。 858 * 859 * この処理は、#saveFile( String ) の直前に行うのがよいでしょう。 860 * 861 * ここでは、Row を逆順にスキャンし、Cellが 存在しない間は、行を削除します。 862 * 途中の空行の削除ではなく、最終行からの連続した空行の削除です。 863 * 864 * isCellDel=true を指定すると、Cellの末尾削除を行います。 865 * 有効行の最後のCellから空セルを削除していきます。 866 * 表形式などの場合は、Cellのあるなしで、レイアウトが崩れる場合がありますので 867 * 処理が不要な場合は、isCellDel=false を指定してください。 868 * 869 * @og.rev 6.0.2.0 (2014/09/19) 新規作成 870 * @og.rev 6.0.2.3 (2014/10/10) CellStyle の有無も判定基準に含めます。 871 * @og.rev 6.0.2.5 (2014/10/31) Cellの開始、終了番号が、マイナスのケースの対応 872 * @og.rev 6.5.0.0 (2016/09/30) poi-3.15 対応(Cell.CELL_TYPE_XXXX → CellType.XXXX) 873 * @og.rev 7.0.0.0 (2018/10/01) 警告:[deprecation] CellのgetCellTypeEnum()は推奨されません (POI4.0.0) 874 * @og.rev 8.0.1.0 (2021/10/29) CellStyle は not null になったための修正 875 * 876 * @param wkbook 処理対象のWorkbook 877 * @param isCellDel Cellの末尾削除を行うかどうか(true:行う/false:行わない) 878 * @see #activeWorkbook( Workbook,List ) 879 */ 880 public static void activeWorkbook( final Workbook wkbook , final boolean isCellDel ) { 881 final int shCnt = wkbook.getNumberOfSheets(); 882 for( int shNo=0; shNo<shCnt; shNo++ ) { 883 final Sheet sht = wkbook.getSheetAt( shNo ); 884 885 final int stR = sht.getFirstRowNum(); 886 final int edR = sht.getLastRowNum(); 887 888 boolean isRowDel = true; // 行の削除は、Cellが見つかるまで。 889 for( int rowNo=edR; rowNo>=stR && rowNo>=0; rowNo-- ) { // 逆順に処理します。 890 final Row rowObj = sht.getRow( rowNo ); 891 if( rowObj != null ) { 892 final int stC = rowObj.getFirstCellNum(); 893 final int edC = rowObj.getLastCellNum(); 894 // 8.0.1.0 (2021/10/29) 各行の最初のセルスタイルをベースとして比較する。 895 896 if( stC >= 0 && edC >= 0 ) { // 8.0.3.0 (2021/12/17) 存在しない場合もある。 897 final CellStyle endCellStyle = rowObj.getCell( stC ).getCellStyle(); // nullチェック入れてない… 898 for( int colNo=edC; colNo>=stC && colNo>=0; colNo-- ) { // 6.0.2.5 (2014/10/31) stC,edC が、マイナスのケースがある。 899 final Cell colObj = rowObj.getCell( colNo ); 900 if( colObj != null ) { 901 final String val = getValue( colObj ); 902 if( colObj.getCellType() != CellType.BLANK && val != null && val.length() > 0 ) { // 7.0.0.0 (2018/10/01) poi-4.0.0 Deprecated. 903 isRowDel = false; // 一つでも現れれば、行の削除は中止 904 break; 905 } 906 // 6.0.2.3 (2014/10/10) CellStyle の有無も判定基準に含めます。 907 // 8.0.1.0 (2021/10/29) 各行の最初のセルスタイルをベースとして比較する。 908 // else if( colObj.getCellStyle() != null ) { 909 else if( ! endCellStyle.equals(colObj.getCellStyle()) ) { 910 isRowDel = false; // 一つでも現れれば、行の削除は中止 911 break; 912 } 913 else if( isCellDel ) { 914 rowObj.removeCell( colObj ); // CELL_TYPE_BLANK の場合は、削除 915 } 916 } 917 } 918 } 919 if( isRowDel ) { sht.removeRow( rowObj ); } 920 else if( !isCellDel ) { break; } // Cell の末尾削除を行わない場合は、break すればよい。 921 } 922 } 923 } 924 } 925 926 /** 927 * 指定の Workbook の全Sheetを対象に、実際の有効行と有効カラムを元に全体をシュリンクします。 928 * 929 * ※ 現在、唯一LibreOfficeでのみ、xslx 変換できますが、有効行とカラムが 930 * シュリンクされず、無駄な行とカラムが存在します。 931 * これは、xsl で出力されたファイルから有効な値を取得して、xslxに適用させるための 932 * 機能で、本来きちんとした有効範囲の xslx が生成されれば、不要な処理です。 933 * 934 * 引数のListオブジェクトに従って、無条件に処理を行います。 935 * 936 * @og.rev 8.0.1.0 (2021/10/29) 全Sheetを対象に、実際の有効行と有効カラムを取得 937 * @og.rev 8.0.3.0 (2021/12/17) シート毎の行数Listに変更。 938 * 939 * @param wkbook 処理対象のWorkbook 940// * @param rcList シートごとの有効行の配列リスト 941 * @param rowCntList シートごとの有効行の配列リスト 942// * @see #getLastRowCellNum( Workbook ) 943 * @see #activeWorkbook( Workbook,boolean ) 944 */ 945// public static void activeWorkbook( final Workbook wkbook , final List<int[]> rcList ) { 946 public static void activeWorkbook( final Workbook wkbook , final List<Integer> rowCntList ) { 947 final int shCnt = wkbook.getNumberOfSheets(); 948 for( int shNo=0; shNo<shCnt; shNo++ ) { 949 final Sheet sht = wkbook.getSheetAt( shNo ); 950// final int[] rowcol = rcList.get(shNo); // シート内の有効行と列 951 final int stR = rowCntList.get(shNo); // シート内の有効行と列 952 953// final int stR = rowcol[0]; 954 final int edR = sht.getLastRowNum(); // 参考程度 955 // edR~stRまでの行は、無条件に削除します。 956 for( int rowNo=edR; rowNo>=stR && rowNo>=0; rowNo-- ) { // 逆順に処理します。 957 final Row rowObj = sht.getRow( rowNo ); 958 if( rowObj != null ) { 959 sht.removeRow( rowObj ); 960 } 961 } 962 963 // カラム列の削除は保留 964 // // stR~0までの行は、有効行なので、カラムの処理を考えます。 965// // final int stC = rowcol[1]; // シートの中での有効カラムの最大値 966 // final int stC = 0; 967 // for( int rowNo=stR; rowNo>=0; rowNo-- ) { // 逆順に処理します。 968 // final Row rowObj = sht.getRow( rowNo ); 969 // if( rowObj != null ) { 970 // final int edC = rowObj.getLastCellNum(); // 参考程度 971 // // edC~stCまでのカラムは、無条件に削除します。 972 // for( int colNo=edC; colNo>=stC && colNo>=0; colNo-- ) { // 6.0.2.5 (2014/10/31) stC,edC が、マイナスのケースがある。 973 // final Cell colObj = rowObj.getCell( colNo ); 974 // if( colObj != null ) { 975 // if( colObj.getCellType() == CellType.BLANK ) { 976 // rowObj.removeCell( colObj ); 977 // } 978 // else { 979 // break; 980 // } 981 // } 982 // } 983 // } 984 // } 985 } 986 } 987 988 /** 989 * ファイルから、Workbookオブジェクトを新規に作成します。 990 * 991 * @og.rev 6.0.2.3 (2014/10/10) 新規作成 992 * @og.rev 6.2.0.0 (2015/02/27) ファイル引数を、String → File に変更 993 * @og.rev 7.0.0.0 (2018/10/01) poi-4.0.0 例外InvalidFormatExceptionは対応するtry文の本体ではスローされません 994 * 995 * @param file 入力ファイル 996 * @return Workbookオブジェクト 997 * @og.rtnNotNull 998 */ 999 public static Workbook createWorkbook( final File file ) { 1000 InputStream fis = null; 1001 try { 1002 // File オブジェクトでcreate すると、ファイルがオープンされたままになってしまう。 1003 fis = new BufferedInputStream( new FileInputStream( file ) ); 1004 return WorkbookFactory.create( fis ); 1005 } 1006 catch( final IOException ex ) { 1007 final String errMsg = "ファイル読込みエラー[" + file + "]" + CR + ex.getMessage() ; 1008 throw new OgRuntimeException( errMsg,ex ); 1009 } 1010 // 7.0.0.0 (2018/10/01) poi-4.0.0 対応するtry文の本体ではスローされません 1011// catch( final InvalidFormatException ex ) { 1012// final String errMsg = "ファイル形式エラー[" + file + "]" + CR + ex.getMessage() ; 1013// throw new OgRuntimeException( errMsg,ex ); 1014// } 1015 finally { 1016 Closer.ioClose( fis ); 1017 } 1018 } 1019 1020 /** 1021 * シート一覧を、Workbook から取得します。 1022 * 1023 * 取得元が、Workbook なので、xls , xlsx どちらの形式でも取り出せます。 1024 * 1025 * EXCEL上のシート名を、配列で返します。 1026 * 1027 * @og.rev 6.0.2.3 (2014/10/10) 新規作成 1028 * 1029 * @param wkbook Workbookオブジェクト 1030 * @return シート名の配列 1031 */ 1032 public static String[] getSheetNames( final Workbook wkbook ) { 1033 final int shCnt = wkbook.getNumberOfSheets(); 1034 1035 String[] shtNms = new String[shCnt]; 1036 1037 for( int i=0; i<shCnt; i++ ) { 1038 final Sheet sht = wkbook.getSheetAt( i ); 1039 shtNms[i] = sht.getSheetName(); 1040 } 1041 1042 return shtNms; 1043 } 1044 1045 /** 1046 * 名前定義一覧を取得します。 1047 * 1048 * EXCEL上に定義された名前を、配列で返します。 1049 * ここでは、名前とFormulaをタブで連結した文字列を配列で返します。 1050 * Name オブジェクトを削除すると、EXCELが開かなくなったりするので、 1051 * 取りあえず一覧を作成して、手動で削除してください。 1052 * なお、名前定義には、非表示というのがありますので、ご注意ください。 1053 * 1054 * ◆ 非表示になっている名前の定義を表示にする EXCEL VBA マクロ 1055 * http://dev.classmethod.jp/tool/excel-delete-name/ 1056 * Sub VisibleNames() 1057 * Dim name 1058 * For Each name In ActiveWorkbook.Names 1059 * If name.Visible = False Then 1060 * name.Visible = True 1061 * End If 1062 * Next 1063 * MsgBox "すべての名前の定義を表示しました。", vbOKOnly 1064 * End Sub 1065 * 1066 * ※ EXCEL2010 数式タブ→名前の管理 で、複数選択で、削除できます。 1067 * ただし、非表示の名前定義は、先に表示しないと、削除できません。 1068 * ◆ 名前の一括削除 EXCEL VBA マクロ 1069 * http://komitsudo.blog70.fc2.com/blog-entry-104.html 1070 * Sub DeleteNames() 1071 * Dim name 1072 * On Error Resume Next 1073 * For Each name In ActiveWorkbook.Names 1074 * If Not name.BuiltIn Then 1075 * name.Delete 1076 * End If 1077 * Next 1078 * MsgBox "すべての名前の定義を削除しました。", vbOKOnly 1079 * End Sub 1080 * 1081 * @og.rev 6.0.2.3 (2014/10/10) 新規作成 1082 * @og.rev 7.0.0.0 (2018/10/01) 警告:[deprecation] WorkbookのgetNameAt(int)は推奨されません (POI4.0.0) 1083 * 1084 * @param wkbook Workbookオブジェクト 1085 * @return 名前定義(名前+TAB+Formula)の配列 1086 * @og.rtnNotNull 1087 */ 1088 public static String[] getNames( final Workbook wkbook ) { 1089// final int cnt = wkbook.getNumberOfNames(); 1090 1091 final Set<String> nmSet = new TreeSet<>(); 1092 1093 // 7.0.0.0 (2018/10/01) 警告:[deprecation] WorkbookのgetNameAt(int)は推奨されません (POI4.0.0) 1094// for( int i=0; i<cnt; i++ ) { 1095 for( final Name nm : wkbook.getAllNames() ) { 1096 String name = null; 1097 String ref = null; 1098 1099// final Name nm = wkbook.getNameAt(i); 1100 try { 1101 name = nm.getNameName(); 1102 ref = nm.getRefersToFormula(); 1103 } 1104 // catch( final Exception ex ) { // 6.1.0.0 (2014/12/26) refactoring 1105 catch( final RuntimeException ex ) { 1106 final String errMsg = "POIUtil:RefersToFormula Error! name=[" + name + "]" + ex.getMessage() ; 1107 System.out.println( errMsg ); 1108 // Excel97形式の場合、getRefersToFormula() でエラーが発生することがある。 1109 } 1110 1111 nmSet.add( name + "\t" + ref ); 1112 1113 // 削除するとEXCELが壊れる? なお、削除時には逆順で廻さないとアドレスがずれます。 1114 // if( nm.isDeleted() ) { wkbook.removeName(i); } 1115 } 1116 1117 return nmSet.toArray( new String[nmSet.size()] ); 1118 } 1119 1120 /** 1121 * 書式のスタイル一覧を取得します。 1122 * 1123 * EXCEL上に定義された書式のスタイルを、配列で返します。 1124 * 書式のスタイルの名称は、CellStyle にメソッドが定義されていません。 1125 * 実クラスである HSSFCellStyle にキャストして使用する 1126 * 必要があります。(XSSFCellStyle にも名称を取得するメソッドがありません。) 1127 * 1128 * ※ EXCEL2010 ホームタブ→セルのスタイル は、一つづつしか削除できません。 1129 * マクロは、開発タブ→Visual Basic で、挿入→標準モジュール を開き 1130 * テキストを張り付けてください。 1131 * 実行は、開発タブ→マクロ で、マクロ名を選択して、実行します。 1132 * 最後は、削除してください。 1133 * 1134 * ◆ スタイルの一括削除 EXCEL VBA マクロ 1135 * http://komitsudo.blog70.fc2.com/blog-entry-104.html 1136 * Sub DeleteStyle() 1137 * Dim styl 1138 * On Error Resume Next 1139 * For Each styl In ActiveWorkbook.Styles 1140 * If Not styl.BuiltIn Then 1141 * styl.Delete 1142 * End If 1143 * Next 1144 * MsgBox "すべての追加スタイルを削除しました。", vbOKOnly 1145 * End Sub 1146 * 1147 * ◆ 名前の表示、削除、スタイルの削除の一括実行 EXCEL VBA マクロ 1148 * Sub AllDelete() 1149 * Call VisibleNames 1150 * Call DeleteNames 1151 * Call DeleteStyle 1152 * MsgBox "すべての処理を完了しました。", vbOKOnly 1153 * End Sub 1154 * 1155 * @og.rev 6.0.2.3 (2014/10/10) 新規作成 1156 * 1157 * @param wkbook Workbookオブジェクト 1158 * @return 書式のスタイル一覧 1159 * @og.rtnNotNull 1160 */ 1161 public static String[] getStyleNames( final Workbook wkbook ) { 1162 final int cnt = wkbook.getNumCellStyles(); // return 値は、short 1163 1164 final Set<String> nmSet = new TreeSet<>(); 1165 1166 for( int s=0; s<cnt; s++ ) { 1167 final CellStyle cs = wkbook.getCellStyleAt( (short)s ); 1168 if( cs instanceof HSSFCellStyle ) { 1169 final HSSFCellStyle hcs = (HSSFCellStyle)cs; 1170 final String name = hcs.getUserStyleName(); 1171 // 6.4.1.1 (2016/01/16) PMD refactoring. Avoid if (x != y) ..; else ..; 1172 if( name == null ) { // この処理は不要かも。 1173 final HSSFCellStyle pst = hcs.getParentStyle(); 1174 if( pst != null ) { 1175 final String pname = pst.getUserStyleName(); 1176 if( pname != null ) { nmSet.add( pname ); } 1177 } 1178 } 1179 else { 1180 nmSet.add( name ); 1181 } 1182 } 1183 } 1184 1185 return nmSet.toArray( new String[nmSet.size()] ); 1186 } 1187 1188 /** 1189 * セル情報を返します。 1190 * 1191 * エラー発生時に、どのセルでエラーが発生したかの情報を取得できるようにします。 1192 * 1193 * @og.rev 6.0.2.0 (2014/09/19) 新規作成 1194 * @og.rev 6.0.3.0 (2014/11/13) セル情報を作成する時に、値もセットします。 1195 * @og.rev 6.2.2.0 (2015/03/27) celKigo を求めるロジック変更 1196 * @og.rev 6.3.1.0 (2015/06/28) rowNo(行番号)も引数に取るようにします。 1197 * 1198 * @param oCell EXCELのセルオブジェクト 1199 * @return セル情報の文字列 1200 */ 1201 public static String getCellMsg( final Cell oCell ) { 1202 String lastMsg = null; 1203 1204 if( oCell != null ) { 1205 final String shtNm = oCell.getSheet().getSheetName(); 1206 final int rowNo = oCell.getRowIndex(); 1207 final int celNo = oCell.getColumnIndex(); 1208 1209 // 6.0.3.0 (2014/11/13) セル情報を作成する時に、値もセットします。 1210 lastMsg = " Sheet=" + shtNm + ", Row=" + rowNo + ", Cel=" + celNo 1211 + "(" + getCelKigo(rowNo,celNo) + ") , Val=" + oCell.toString() ; 1212 } 1213 1214 return lastMsg; 1215 } 1216 1217 /** 1218 * Excelの行番号,列番号より、セル記号を求めます。 1219 * 1220 * 行番号は、0から始まる数字ですが、記号化する場合は、1から始まります。 1221 * Excelの列記号とは、A,B,C,…,Z,AA,AB,…,ZZ,AAA,AAB,… と続きます。 1222 * つまり、アルファベットだけの、26進数になります。(ゼロの扱いが少し特殊です) 1223 * 列番号は、0から始まる数字で、0=A,1=B,2=C,…,25=Z,26=AA,27=AB,…,701=ZZ,702=AAA,703=AAB,… 1224 * EXCELの行列記号にする場合は、この列記号に、行番号を、+1して付ければよいだけです。 1225 * (※ 列番号に+1するのは、内部では0から始まる列番号ですが、表示上は1から始まります) 1226 * 1227 * @og.rev 6.2.2.0 (2015/03/27) celKigo を求めるロジック変更 1228 * @og.rev 6.3.1.0 (2015/06/28) rowNo(行番号)も引数に取るようにします。 1229 * 1230 * @param rowNo 行番号(0,1,2,…) 1231 * @param colNo 列番号(0,1,2,…) 1232 * @return Excelの列記号(A1,B2,C3,…) 1233 */ 1234 public static String getCelKigo( final int rowNo,final int colNo ) { 1235 final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE ); 1236 int cnt = colNo; 1237 while( cnt >= 26 ) { 1238 buf.append( (char)('A'+cnt%26) ); 1239 cnt = cnt/26-1; 1240 } 1241 buf.append( (char)('A'+cnt%26) ) 1242 .reverse() // append で逆順に付けているので、反転して返します。 1243 .append( rowNo+1 ); 1244 1245 return buf.toString(); 1246 } 1247 1248 /** 1249 * アプリケーションのサンプルです。 1250 * 1251 * 入力ファイル名 は必須で、第一引数固定です。 1252 * 第二引数は、処理方法で、-ALL か、-LINE を指定します。何も指定しなければ、-ALL です。 1253 * 第三引数を指定した場合は、Encode を指定します。 1254 * 1255 * Usage: java org.opengion.fukurou.model.POIUtil 入力ファイル名 [処理方式] [エンコード] 1256 * -A(LL) ・・・ ALL 一括処理(初期値) 1257 * -L(INE) ・・・ LINE 行単位処理 1258 * -S(heet) ・・・ Sheet名一覧 1259 * -N(AME) ・・・ NAME:名前定義 1260 * -C(ellStyle) ・・・ CellStyle:書式のスタイル 1261 * 1262 * @og.rev 6.0.2.0 (2014/09/19) 新規作成 1263 * @og.rev 6.2.3.0 (2015/05/01) パラメータ変更、textReader → extractor に変更 1264 * @og.rev 6.2.4.2 (2015/05/29) 引数判定の true/false の処理が逆でした。 1265 * @og.rev 6.3.9.0 (2015/11/06) Java 8 ラムダ式に変更 1266 * 1267 * @param args コマンド引数配列 1268 */ 1269 public static void main( final String[] args ) { 1270 final String usageMsg = "Usage: java org.opengion.fukurou.model.POIUtil 入力ファイル名 [処理方式] [エンコード]" + "\n" + 1271 "\t -A(LL) ・・・ ALL 一括処理(初期値) \n" + 1272 "\t -L(INE) ・・・ LINE 行単位処理 \n" + 1273 "\t -S(heet) ・・・ Sheet名一覧 \n" + 1274 "\t -N(AME) ・・・ NAME:名前定義 \n" + 1275 "\t -C(ellStyle) ・・・ CellStyle:書式のスタイル \n" ; 1276 if( args.length == 0 ) { 1277 System.err.println( usageMsg ); 1278 return ; 1279 } 1280 1281 final File file = new File( args[0] ); 1282 final char type = args.length >= 2 ? args[1].charAt(1) : 'A' ; 1283 final String encode = args.length >= 3 ? args[2] : null ; // 6.2.4.2 (2015/05/29) true/false の処理が逆でした。 1284 1285 switch( type ) { 1286 case 'A' : if( encode == null ) { 1287 System.out.println( POIUtil.extractor( file ) ); 1288 } 1289 else { 1290 System.out.println( POIUtil.extractor( file,encode ) ); 1291 } 1292 break; 1293 // 6.3.9.0 (2015/11/06) Java 8 ラムダ式に変更 1294 case 'L' : final TextConverter<String,String> conv = 1295 ( val,cmnt ) -> { 1296 System.out.println( "val=" + val + " , cmnt=" + cmnt ); 1297 return null; 1298 }; 1299 1300 // new TextConverter<String,String>() { 1301 // /** 1302 // * 入力文字列を、変換します。 1303 // * 1304 // * @param val 入力文字列 1305 // * @param cmnt コメント 1306 // * @return 変換文字列(変換されない場合は、null) 1307 // */ 1308 // @Override 1309 // public String change( final String val , final String cmnt ) { 1310 // System.out.println( "val=" + val + " , cmnt=" + cmnt ); 1311 // return null; 1312 // } 1313 // }; 1314 1315 if( encode == null ) { 1316 POIUtil.textReader( file,conv ); 1317 } 1318 else { 1319 POIUtil.textReader( file,conv,encode ); 1320 } 1321 break; 1322 case 'S' : final String[] shts = POIUtil.getSheetNames( POIUtil.createWorkbook(file) ); 1323 System.out.println( "No:\tSheetName" ); 1324 for( int i=0; i<shts.length; i++ ) { 1325 System.out.println( i + "\t" + shts[i] ); 1326 } 1327 break; 1328 case 'N' : final String[] nms = POIUtil.getNames( POIUtil.createWorkbook(file) ); 1329 System.out.println( "No:\tName\tFormula" ); 1330 for( int i=0; i<nms.length; i++ ) { 1331 System.out.println( i + "\t" + nms[i] ); 1332 } 1333 break; 1334 case 'C' : final String[] sns = POIUtil.getStyleNames( POIUtil.createWorkbook(file) ); 1335 System.out.println( "No:\tStyleName" ); 1336 for( int i=0; i<sns.length; i++ ) { 1337 System.out.println( i + "\t" + sns[i] ); 1338 } 1339 break; 1340 default : System.err.println( usageMsg ); 1341 break; 1342 } 1343 } 1344}