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