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.util; 017 018import java.io.InputStream; 019import java.io.FileInputStream; 020import java.io.BufferedInputStream; 021import java.io.IOException; 022 023import java.text.DecimalFormat; 024import java.text.NumberFormat; 025import java.util.Locale; 026 027import org.apache.poi.extractor.ExtractorFactory; 028import org.apache.poi.POITextExtractor; 029 030import org.apache.poi.hwpf.HWPFDocument; 031import org.apache.poi.hwpf.usermodel.Range; 032import org.apache.poi.hwpf.usermodel.Section; 033import org.apache.poi.hwpf.usermodel.Paragraph; 034import org.apache.poi.hwpf.usermodel.CharacterRun; 035// import org.apache.poi.hwpf.usermodel.HeaderStories; 036 037import org.apache.poi.hslf.HSLFSlideShow; 038import org.apache.poi.hslf.usermodel.SlideShow; 039import org.apache.poi.hslf.model.Slide; 040import org.apache.poi.hslf.model.TextRun; 041 042import org.apache.poi.openxml4j.exceptions.InvalidFormatException; 043 044import org.apache.poi.ss.usermodel.WorkbookFactory; 045import org.apache.poi.ss.usermodel.Workbook; 046import org.apache.poi.ss.usermodel.Sheet; 047import org.apache.poi.ss.usermodel.Row; 048import org.apache.poi.ss.usermodel.Cell; 049 050import org.apache.poi.ss.usermodel.CreationHelper; 051 052import org.apache.poi.ss.usermodel.DateUtil; 053import org.apache.poi.ss.usermodel.RichTextString; 054import org.apache.poi.ss.usermodel.FormulaEvaluator; 055 056import org.opengion.fukurou.util.Closer; 057import org.opengion.fukurou.util.HybsDateUtil; 058 059/** 060 * POI による、Excel/Word/PoworPoint等に対する、ユーティリティクラスです。 061 * 062 * 基本的には、ネイティブファイルを読み取り、テキストを取得する機能が主です。 063 * Excel、Word、PowerPoint、Visio、Publisher からのテキスト取得が可能です。 064 * 065 * @og.rev 6.0.2.0 (2014/08/29) 新規作成 066 * @og.group その他 067 * 068 * @version 6.0 069 * @author Kazuhiko Hasegawa 070 * @since JDK7.0, 071 */ 072public class POIUtil { 073 /** このプログラムのVERSION文字列を設定します。 {@value} */ 074 private static final String VERSION = "6.0.2.0 (2014/08/29)" ; 075 076 private static final String CR = System.getProperty("line.separator"); 077 078 /** 079 * 引数ファイルを、POITextExtractor を使用してテキスト化します。 080 * 081 * Excel、Word、PowerPoint、Visio、Publisher からのテキスト取得が可能です。 082 * 拡張子から、ファイルの種類を自動判別します。 083 * 084 * @og.rev 6.0.2.0 (2014/08/29) 新規作成 085 * 086 * @param fname 入力ファイル名 087 * @return 変換後のテキスト 088 */ 089 public static final String getText( final String fname ) { 090 InputStream fis = null; 091 POITextExtractor extractor = null; 092 try { 093 fis = new BufferedInputStream( new FileInputStream( fname ) ); 094 extractor = ExtractorFactory.createExtractor( fis ); 095 return extractor.getText(); 096 } 097 catch( Exception ex ) { 098 String errMsg = "ファイル処理エラー[" + fname + "]" + CR + ex.getMessage() ; 099 throw new RuntimeException( errMsg,ex ); 100 } 101 finally { 102 Closer.ioClose( extractor ); 103 Closer.ioClose( fis ); 104 } 105 } 106 107 /** 108 * 引数ファイル(Word)を、HWPFDocument を使用してテキスト化します。 109 * 110 * POIEventは、唯一の void readText( String,String int... ) メソッドを持ち、 111 * 処理単位に、このメソッドが呼ばれます。つまり、内部にメモリを溜め込みません。 112 * int...可変長引数は、0:Section番号 1:page番号 2:Paragraph番号 がセットされます。 113 * 114 * POIEventは、データとして設定されているレコードのみCallされます。 115 * 116 * @og.rev 6.0.2.0 (2014/08/29) 新規作成 117 * 118 * @param fname 入力ファイル名 119 * @param event イベント処理させるI/F 120 */ 121 public static final void wordReader( final String fname , final POIEvent event ) { 122 InputStream fis = null; 123 StringBuilder buf = new StringBuilder(200); 124 125 try { 126 fis = new BufferedInputStream( new FileInputStream( fname ) ); 127 HWPFDocument doc = new HWPFDocument( fis ); 128 Range r = doc.getRange(); 129 // HeaderStories header = new HeaderStories( doc ); 130 131 int pCnt = 0; // ページ番号 132 for (int sno = 0; sno < r.numSections(); sno++) { 133 Section sec = r.getSection(sno); 134 String title = null; // HeaderStories からうまく取れなかった。 135 for (int pno = 0; pno < sec.numParagraphs(); pno++) { 136 Paragraph para = sec.getParagraph(pno); 137 138 if( para.text().indexOf("\f") >= 0 ) { 139 pCnt++; 140 // title = header.getHeader( pCnt ).trim(); 141 } 142 143 buf.setLength(0); // Clearの事 144 for (int cno = 0; cno < para.numCharacterRuns(); cno++) { 145 CharacterRun run = para.getCharacterRun(cno); 146 // Removes any fields (eg macros, page markers etc) from the string 147 buf.append( Range.stripFields( run.text() ) ); 148 } 149 String text = buf.toString().trim(); 150 // データとして設定されているレコードのみイベントを発生させる。 151 if( text.length() > 0 ) { 152 if( title == null ) { title = text; } // Section の最初の有効なParagraphをタイトルにする。 153 event.readText( text,title,sno,pCnt,pno ); // テキスト タイトル 0:Section番号 1:page番号 2:Paragraph番号 154 } 155 } 156 } 157 } 158 catch( IOException ex ) { 159 String errMsg = "ファイル読込みエラー[" + fname + "]" + CR + ex.getMessage() ; 160 throw new RuntimeException( errMsg,ex ); 161 } 162 finally { 163 Closer.ioClose( fis ); 164 } 165 } 166 167 /** 168 * 引数ファイル(PoworPoint)を、HSLFSlideShow を使用してテキスト化します。 169 * 170 * POIEventは、唯一の void readText( String,String int... ) メソッドを持ち、 171 * 処理単位に、このメソッドが呼ばれます。つまり、内部にメモリを溜め込みません。 172 * int...可変長引数は、0:Slide番号 1:TextRun番号 がセットされます。 173 * 174 * POIEventは、データとして設定されているレコードのみCallされます。 175 * 176 * @og.rev 6.0.2.0 (2014/08/29) 新規作成 177 * 178 * @param fname 入力ファイル名 179 * @param event イベント処理させるI/F 180 */ 181 public static final void pptReader( final String fname , final POIEvent event ) { 182 InputStream fis = null; 183 184 try { 185 fis = new BufferedInputStream( new FileInputStream( fname ) ); 186 SlideShow ss = new SlideShow( new HSLFSlideShow( fis ) ); 187 Slide[] slides = ss.getSlides(); 188 for (int sno = 0; sno < slides.length; sno++) { 189 String title = slides[sno].getTitle(); // title は、あまり当てにならない。 190 TextRun[] textRun = slides[sno].getTextRuns(); 191 for (int tno = 0; tno < textRun.length; tno++) { 192 String text = textRun[tno].getText().trim(); 193 // データとして設定されているレコードのみイベントを発生させる。 194 if( text.length() > 0 ) { 195 event.readText( text,title,sno,tno ); // テキスト タイトル 0:Slide番号 1:TextRun番号 196 } 197 } 198 } 199 } 200 catch( IOException ex ) { 201 String errMsg = "ファイル読込みエラー[" + fname + "]" + CR + ex.getMessage() ; 202 throw new RuntimeException( errMsg,ex ); 203 } 204 finally { 205 Closer.ioClose( fis ); 206 } 207 } 208 209 /** 210 * 引数ファイル(Excel)を、Workbook を使用してテキスト化します。 211 * 212 * POIEventは、唯一の void readText( String,String int... ) メソッドを持ち、 213 * 処理単位に、このメソッドが呼ばれます。つまり、内部にメモリを溜め込みません。 214 * int...可変長引数は、0:Sheet番号 1:Row番号 2:Cell番号 がセットされます。 215 * 216 * EXCEL については、詳細に処理するための、ExcelModel クラスが別にあります。 217 * この処理は、簡易的に処理する場合に、使用できます。 218 * 219 * POIEventは、データとして設定されているレコードのみCallされます。 220 * 221 * @og.rev 6.0.2.0 (2014/08/29) 新規作成 222 * 223 * @param fname 入力ファイル名 224 * @param event イベント処理させるI/F 225 * @see org.opengion.fukurou.model.ExcelModel 226 * @see #excelReader( String , POIEvent , boolean ) 227 */ 228 public static final void excelReader( final String fname , final POIEvent event ) { 229 excelReader( fname,event,false ); 230 } 231 232 /** 233 * 引数ファイル(Excel)を、Workbook を使用してテキスト化します。 234 * 235 * POIEventは、唯一の void readText( String,String int... ) メソッドを持ち、 236 * 処理単位に、このメソッドが呼ばれます。つまり、内部にメモリを溜め込みません。 237 * int...可変長引数は、0:Sheet番号 1:Row番号 2:Cell番号 がセットされます。 238 * 239 * EXCEL については、詳細に処理するための、ExcelModel クラスが別にあります。 240 * この処理は、簡易的に処理する場合に、使用できます。 241 * 242 * isFull属性に true を設定すると、すべての行列について、POIEvent が Callされます。 243 * 行が存在しない場合、カラムが存在しない場合は、null が設定されます。 244 * 行が存在しない場合のカラム番号は、-1 が設定されます。 245 * 246 * @og.rev 6.0.2.0 (2014/08/29) 新規作成 247 * 248 * @param fname 入力ファイル名 249 * @param event イベント処理させるI/F 250 * @param isFull すべての行列について、イベント処理する場合は、true 251 * @see org.opengion.fukurou.model.ExcelModel 252 * @see #excelReader( String , POIEvent ) 253 */ 254 public static final void excelReader( final String fname , final POIEvent event , final boolean isFull ) { 255 InputStream fis = null; 256 257 try { 258 fis = new BufferedInputStream( new FileInputStream( fname ) ); 259 Workbook wkbook = WorkbookFactory.create( fis ); 260 for (int x = 0; x < wkbook.getNumberOfSheets(); x++) { 261 Sheet sheet = wkbook.getSheetAt( x ); 262 String sName = sheet.getSheetName(); 263 int stR = isFull ? 0 : sheet.getFirstRowNum(); // fullの場合は、0 から開始 264 int edR = sheet.getLastRowNum(); 265 for (int y = stR; y <= edR; y++) { // getLastRowNum は、含む 266 Row rowObj = sheet.getRow( y ); 267 if( rowObj != null ) { 268 int stC = isFull ? 0 : rowObj.getFirstCellNum(); // fullの場合は、0 から開始 269 int edC = rowObj.getLastCellNum(); 270 for (int z = stC; z <= edC; z++) { // getLastCellNum は、含む 271 Cell colObj = rowObj.getCell( z ); 272 if( colObj != null && colObj.getCellType() != Cell.CELL_TYPE_BLANK ) { 273 String text = getValue( colObj ); 274 event.readText( text,sName,x,y,z ); // Cellテキスト Sheet名 0:Sheet番号 1:Row番号 2:Cell番号 275 } 276 else if( isFull ) { 277 // カラムが存在しない場合のイベント 278 event.readText( null,sName,x,y,z ); // Cellテキスト Sheet名 0:Sheet番号 1:Row番号 2:Cell番号 279 } 280 } 281 } 282 else if( isFull ) { 283 // 行が存在しない場合のイベント 284 event.readText( null,sName,x,y,-1 ); // Cellテキスト Sheet名 0:Sheet番号 1:Row番号 2:Cell番号 285 } 286 } 287 } 288 } 289 catch( IOException ex ) { 290 String errMsg = "ファイル読込みエラー[" + fname + "]" + CR + ex.getMessage() ; 291 throw new RuntimeException( errMsg,ex ); 292 } 293 catch( InvalidFormatException ex ) { 294 String errMsg = "ファイル形式エラー[" + fname + "]" + CR + ex.getMessage() ; 295 throw new RuntimeException( errMsg,ex ); 296 } 297 finally { 298 Closer.ioClose( fis ); 299 } 300 } 301 302 /** 303 * セルオブジェクト(Cell)から値を取り出します。 304 * 305 * セルオブジェクトが存在しない場合は、null を返します。 306 * 307 * @og.rev 3.8.5.3 (2006/08/07) 取り出し方法を少し修正 308 * @og.rev 5.5.1.2 (2012/04/06) フォーマットセルを実行して、その結果を再帰的に処理する。 309 * 310 * @param oCell EXCELのセルオブジェクト 311 * 312 * @return セルの値 313 */ 314 public static String getValue( final Cell oCell ) { 315 if( oCell == null ) { return null; } 316 317 String strText = ""; 318 int nCellType = oCell.getCellType(); 319 switch(nCellType) { 320 case Cell.CELL_TYPE_NUMERIC: 321 strText = getNumericTypeString( oCell ); 322 break; 323 case Cell.CELL_TYPE_STRING: 324 // POI3.0 strText = oCell.getStringCellValue(); 325 RichTextString richText = oCell.getRichStringCellValue(); 326 if( richText != null ) { 327 strText = richText.getString(); 328 } 329 break; 330 case Cell.CELL_TYPE_FORMULA: 331 // POI3.0 strText = oCell.getStringCellValue(); 332 // 5.5.1.2 (2012/04/06) フォーマットセルを実行して、その結果を再帰的に処理する。 333 Workbook wb = oCell.getSheet().getWorkbook(); 334 CreationHelper crateHelper = wb.getCreationHelper(); 335 FormulaEvaluator evaluator = crateHelper.createFormulaEvaluator(); 336 337 try { 338 strText = getValue(evaluator.evaluateInCell(oCell)); 339 } 340 catch ( Throwable th ) { 341 String errMsg = "セルフォーマットが解析できません。[" + oCell.getCellFormula() + "]" 342 + CR + getCellMsg( oCell ); 343 throw new RuntimeException( errMsg,th ); 344 } 345 break; 346 case Cell.CELL_TYPE_BOOLEAN: 347 strText = String.valueOf(oCell.getBooleanCellValue()); 348 break; 349 case Cell.CELL_TYPE_BLANK : 350 case Cell.CELL_TYPE_ERROR: 351 break; 352 default : 353 break; 354 } 355 return strText.trim(); 356 } 357 358 /** 359 * セル値が数字の場合に、数字か日付かを判断して、対応する文字列を返します。 360 * 361 * @og.rev 3.8.5.3 (2006/08/07) 新規追加 362 * @og.rev 5.5.7.2 (2012/10/09) HybsDateUtil を利用するように修正します。 363 * 364 * @param oCell EXCELのセルオブジェクト 365 * 366 * @return 数字の場合は、文字列に変換した結果を、日付の場合は、"yyyyMMddHHmmss" 形式で返します。 367 */ 368 public static String getNumericTypeString( final Cell oCell ) { 369 final String strText ; 370 371 double dd = oCell.getNumericCellValue() ; 372 if( DateUtil.isCellDateFormatted( oCell ) ) { 373 strText = HybsDateUtil.getDate( DateUtil.getJavaDate( dd ).getTime() , "yyyyMMddHHmmss" ); // 5.5.7.2 (2012/10/09) HybsDateUtil を利用 374 } 375 else { 376 NumberFormat numFormat = NumberFormat.getInstance(); 377 if( numFormat instanceof DecimalFormat ) { 378 ((DecimalFormat)numFormat).applyPattern( "#.####" ); 379 } 380 strText = numFormat.format( dd ); 381 } 382 return strText ; 383 } 384 385 /** 386 * セル情報を返します。 387 * 388 * エラー発生時に、どのセルでエラーが発生したかの情報を取得できるようにします。 389 * 390 * @og.rev 6.0.2.0 (2014/08/29) 新規作成 391 * 392 * @param oCell EXCELのセルオブジェクト 393 * @return セル情報の文字列 394 */ 395 public static String getCellMsg( final Cell oCell ) { 396 String lastMsg = null; 397 398 if( oCell != null ) { 399 int rowNo = oCell.getRowIndex(); 400 int celNo = oCell.getColumnIndex(); 401 String shtNm = oCell.getSheet().getSheetName(); 402 403 lastMsg = " Sheet=" + shtNm + ", Row=" + rowNo + ", Cel=" + celNo ; 404 } 405 406 return lastMsg; 407 } 408 409 /** 410 * アプリケーションのサンプルです。 411 * 412 * 入力ファイル名 は必須で、第一引数固定です。 413 * 第二引数は、処理方法で、指定しない場合は、-ALL として処理します。 414 * 415 * Usage: java org.opengion.fukurou.util.POIUtil 入力ファイル名 [-A(LL)/-W(ORD)/-P(PT)/-E(XCEL)/-F(ULL EXCEL)] 416 * -A(LL) ・・・ ALL 一括処理(初期値) 417 * -W(ORD) ・・・ Word 418 * -P(PT) ・・・ PoworPoint 419 * -E(XCEL) ・・・ Excel 420 * -F(ULL EXCEL) ・・・ Full Excel 421 * 422 * @og.rev 6.0.2.0 (2014/08/29) 新規作成 423 * 424 * @param args コマンド引数配列 425 */ 426 public static void main( final String[] args ) { 427 final String usageMsg = "Usage: java org.opengion.fukurou.util.POIUtil 入力ファイル名 [-A(LL)/-W(ORD)/-P(PT)/-E(XCEL)/-F(ULL EXCEL)]" ; 428 if( args.length == 0 ) { 429 System.err.println( usageMsg ); 430 return ; 431 } 432 433 String fname = args[0]; 434 char type = (args.length == 1) ? 'A' : args[1].charAt(1) ; 435 436 switch( type ) { 437 case 'A' : System.out.println( POIUtil.getText(fname) ); 438 break; 439 case 'W' : POIUtil.wordReader( 440 fname, 441 new POIEvent() { 442 public void readText( final String text , final String cmnt , final int... args ) { 443 System.out.println( "S[" + args[0] + "],P[" + fname + "],G[" + args[2] + "],C[" + cmnt + "]=" + text ); 444 } 445 } 446 ); 447 break; 448 case 'P' : POIUtil.pptReader( 449 fname, 450 new POIEvent() { 451 public void readText( final String text , final String cmnt , final int... args ) { 452 // System.out.println( "S[" + args[0] + "],T[" + fname + "],C[" + cmnt + "]=" + text ); 453 System.out.println( "S[" + args[0] + "],T[" + fname + "]=" + text ); 454 } 455 } 456 ); 457 break; 458 case 'E' : POIUtil.excelReader( 459 fname, 460 new POIEvent() { 461 public void readText( final String text , final String cmnt , final int... args ) { 462 System.out.println( cmnt + ":S[" + args[0] + "],R[" + fname + "],C[" + args[2] + "]=" + text ); 463 } 464 } 465 ); 466 break; 467 case 'F' : StringBuilder buf = new StringBuilder(); 468 POIUtil.excelReader( 469 fname, 470 new POIEvent() { 471 int bkSht = -1; // シートブレーク 472 int bkRow = -1; // 行ブレーク 473 474 public void readText( final String text , final String cmnt , final int... args ) { 475 if( bkSht != args[0] ) { // シートブレーク 476 if( buf.length() > 0 ) { 477 System.out.println( buf ); 478 buf.setLength(0); // Clearの事 479 } 480 System.out.println( "SheetName=" + cmnt + ":S[" + args[0] + "]" ); 481 bkSht = args[0]; 482 bkRow = 0; 483 } 484 if( bkRow != args[1] ) { // 行ブレーク 485 bkRow = args[1]; 486 buf.append( CR ); 487 } 488 buf.append( "\t" ); 489 if( text != null ) { buf.append( text ); } 490 } 491 }, 492 true // Full 行列処理 493 ); 494 System.out.println( buf ); // 処理が終了したかどうかはイベントでは判らない。 495 break; 496 default : System.err.println( usageMsg ); 497 break; 498 } 499 } 500}