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