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