/*
 * Copyright (c) 2009 The openGion Project.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */
package org.opengion.fukurou.util;

import java.io.InputStream;
import java.io.FileInputStream;
import java.io.BufferedInputStream;
import java.io.IOException;

import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.Locale;
import java.util.Set;							// 6.0.2.3 (2014/10/10)
import java.util.TreeSet;						// 6.0.2.3 (2014/10/10)

import org.apache.poi.extractor.ExtractorFactory;
import org.apache.poi.POITextExtractor;

import org.apache.poi.hwpf.HWPFDocument;
import org.apache.poi.hwpf.usermodel.Range;
import org.apache.poi.hwpf.usermodel.Section;
import org.apache.poi.hwpf.usermodel.Paragraph;
import org.apache.poi.hwpf.usermodel.CharacterRun;
// import org.apache.poi.hwpf.usermodel.HeaderStories;

import org.apache.poi.hssf.usermodel.HSSFCellStyle;

import org.apache.poi.hslf.HSLFSlideShow;
import org.apache.poi.hslf.usermodel.SlideShow;
import org.apache.poi.hslf.model.Slide;
import org.apache.poi.hslf.model.TextRun;

import org.apache.poi.openxml4j.exceptions.InvalidFormatException;

import org.apache.poi.ss.usermodel.WorkbookFactory;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.CreationHelper;
import org.apache.poi.ss.usermodel.RichTextString;
import org.apache.poi.ss.usermodel.DateUtil;
import org.apache.poi.ss.usermodel.FormulaEvaluator;
import org.apache.poi.ss.usermodel.Name;				// 6.0.2.3 (2014/10/10)

import org.apache.poi.ss.util.SheetUtil;

import org.opengion.fukurou.util.Closer;
import org.opengion.fukurou.util.HybsDateUtil;

/**
 * POI による、Excel/Word/PoworPoint等に対する、ユーティリティクラスです。
 *
 * 基本的には、ネイティブファイルを読み取り、テキストを取得する機能が主です。
 * Excel、Word、PowerPoint、Visio、Publisher からのテキスト取得が可能です。
 *
 * @og.rev 6.0.2.0 (2014/09/19) 新規作成
 * @og.group その他
 *
 * @version  6.0
 * @author   Kazuhiko Hasegawa
 * @since    JDK7.0,
 */
public final class POIUtil {
	/** このプログラムのVERSION文字列を設定します。	{@value} */
	private static final String VERSION = "6.0.2.3 (2014/10/10)" ;

	private static final String CR = System.getProperty("line.separator");

	/**
	 * すべてが staticメソッドなので、コンストラクタを呼び出さなくしておきます。
	 *
	 */
	private POIUtil() {}

	/**
	 * 引数ファイルを、POITextExtractor を使用してテキスト化します。
	 *
	 * Excel、Word、PowerPoint、Visio、Publisher からのテキスト取得が可能です。
	 * 拡張子から、ファイルの種類を自動判別します。
	 *
	 * @og.rev 6.0.2.0 (2014/09/19) 新規作成
	 *
	 * @param	fname 入力ファイル名
	 * @return	変換後のテキスト
	 */
	public static final String getText( final String fname ) {
		InputStream fis = null;
		POITextExtractor extractor = null;
		try {
			fis = new BufferedInputStream( new FileInputStream( fname ) );
			extractor = ExtractorFactory.createExtractor( fis );
			return extractor.getText();
		}
		catch( Exception ex ) {
			String errMsg = "ファイル処理エラー[" + fname + "]" + CR + ex.getMessage() ;
			throw new RuntimeException( errMsg,ex );
		}
		finally {
			Closer.ioClose( extractor );
			Closer.ioClose( fis );
		}
	}

	/**
	 * 引数ファイル(Word)を、HWPFDocument を使用してテキスト化します。
	 *
	 * POIEventは、唯一の void readText( String,String int... ) メソッドを持ち、
	 * 処理単位に、このメソッドが呼ばれます。つまり、内部にメモリを溜め込みません。
	 * int...可変長引数は、0:Section番号 1:page番号 2:Paragraph番号 がセットされます。
	 *
	 * POIEventは、データとして設定されているレコードのみCallされます。
	 *
	 * @og.rev 6.0.2.0 (2014/09/19) 新規作成
	 *
	 * @param	fname 入力ファイル名
	 * @param	event イベント処理させるI/F
	 */
	public static final void wordReader( final String fname , final POIEvent event ) {
		InputStream fis = null;
		StringBuilder buf = new StringBuilder(200);

		try {
			fis = new BufferedInputStream( new FileInputStream( fname ) );
			HWPFDocument doc = new HWPFDocument( fis );
			Range r = doc.getRange();
			// HeaderStories header = new HeaderStories( doc );

			int pCnt = 0;		// ページ番号
			for (int sno = 0; sno < r.numSections(); sno++) {
				Section sec = r.getSection(sno);
				String title = null;									// HeaderStories からうまく取れなかった。
				for (int pno = 0; pno < sec.numParagraphs(); pno++) {
					Paragraph para = sec.getParagraph(pno);

					if( para.text().indexOf("\f") >= 0 ) {
						pCnt++;
			//			title = header.getHeader( pCnt ).trim();
					}

					buf.setLength(0);		// Clearの事
					for (int cno = 0; cno < para.numCharacterRuns(); cno++) {
						CharacterRun run = para.getCharacterRun(cno);
						// Removes any fields (eg macros, page markers etc) from the string
						buf.append( Range.stripFields( run.text() ) );
					}
					String text = buf.toString().trim();
					// データとして設定されているレコードのみイベントを発生させる。
					if( text.length() > 0 ) {
						if( title == null ) { title = text; }		// Section の最初の有効なParagraphをタイトルにする。
						event.readText( text,title,sno,pCnt,pno );	// テキスト タイトル 0:Section番号 1:page番号 2:Paragraph番号
					}
				}
			}
		}
		catch( IOException ex ) {
			String errMsg = "ファイル読込みエラー[" + fname + "]" + CR + ex.getMessage() ;
			throw new RuntimeException( errMsg,ex );
		}
		finally {
			Closer.ioClose( fis );
		}
	}

	/**
	 * 引数ファイル(PoworPoint)を、HSLFSlideShow を使用してテキスト化します。
	 *
	 * POIEventは、唯一の void readText( String,String int... ) メソッドを持ち、
	 * 処理単位に、このメソッドが呼ばれます。つまり、内部にメモリを溜め込みません。
	 * int...可変長引数は、0:Slide番号 1:TextRun番号 がセットされます。
	 *
	 * POIEventは、データとして設定されているレコードのみCallされます。
	 *
	 * @og.rev 6.0.2.0 (2014/09/19) 新規作成
	 *
	 * @param	fname 入力ファイル名
	 * @param	event イベント処理させるI/F
	 */
	public static final void pptReader( final String fname , final POIEvent event ) {
		InputStream fis = null;

		try {
			fis = new BufferedInputStream( new FileInputStream( fname ) );
			SlideShow ss = new SlideShow( new HSLFSlideShow( fis ) );
			Slide[] slides = ss.getSlides();
			for (int sno = 0; sno < slides.length; sno++) {
				String    title   = slides[sno].getTitle();				// title は、あまり当てにならない。
				TextRun[] textRun = slides[sno].getTextRuns();
				for (int tno = 0; tno < textRun.length; tno++) {
					String text = textRun[tno].getText().trim();
					// データとして設定されているレコードのみイベントを発生させる。
					if( text.length() > 0 ) {
						event.readText( text,title,sno,tno );			// テキスト タイトル 0:Slide番号 1:TextRun番号
					}
				}
			}
		}
		catch( IOException ex ) {
			String errMsg = "ファイル読込みエラー[" + fname + "]" + CR + ex.getMessage() ;
			throw new RuntimeException( errMsg,ex );
		}
		finally {
			Closer.ioClose( fis );
		}
	}

	/**
	 * 引数ファイル(Excel)を、Workbook を使用してテキスト化します。
	 *
	 * POIEventは、唯一の void readText( String,String int... ) メソッドを持ち、
	 * 処理単位に、このメソッドが呼ばれます。つまり、内部にメモリを溜め込みません。
	 * int...可変長引数は、0:Sheet番号 1:Row番号 2:Cell番号 がセットされます。
	 *
	 * EXCEL については、詳細に処理するための、ExcelModel クラスが別にあります。
	 * この処理は、簡易的に処理する場合に、使用できます。
	 *
	 * POIEventは、データとして設定されているレコードのみCallされます。
	 *
	 * @og.rev 6.0.2.0 (2014/09/19) 新規作成
	 *
	 * @param	fname 入力ファイル名
	 * @param	event イベント処理させるI/F
	 * @see		org.opengion.fukurou.model.ExcelModel
	 * @see		#excelReader( String , POIEvent , boolean )
	 */
	public static final void excelReader( final String fname , final POIEvent event ) {
		excelReader( fname,event,false );
	}

	/**
	 * 引数ファイル(Excel)を、Workbook を使用してテキスト化します。
	 *
	 * POIEventは、唯一の void readText( String,String int... ) メソッドを持ち、
	 * 処理単位に、このメソッドが呼ばれます。つまり、内部にメモリを溜め込みません。
	 * int...可変長引数は、0:Sheet番号 1:Row番号 2:Cell番号 がセットされます。
	 *
	 * EXCEL については、詳細に処理するための、ExcelModel クラスが別にあります。
	 * この処理は、簡易的に処理する場合に、使用できます。
	 *
	 * isFull属性に true を設定すると、すべての行列について、POIEvent が Callされます。
	 * 行が存在しない場合、カラムが存在しない場合は、null が設定されます。
	 * 行が存在しない場合のカラム番号は、-1 が設定されます。
	 *
	 * @og.rev 6.0.2.0 (2014/09/19) 新規作成
	 *
	 * @param	fname 入力ファイル名
	 * @param	event イベント処理させるI/F
	 * @param	isFull すべての行列について、イベント処理する場合は、true
	 * @see		org.opengion.fukurou.model.ExcelModel
	 * @see		#excelReader( String , POIEvent )
	 */
	public static final void excelReader( final String fname , final POIEvent event , final boolean isFull ) {
//		InputStream fis = null;

//		try {
//			fis = new BufferedInputStream( new FileInputStream( fname ) );
//			Workbook wkbook = WorkbookFactory.create( fis );
			Workbook wkbook = POIUtil.createWorkbook( fname );		// 6.0.2.3 (2014/10/10) メソッド利用
			for (int x=0; x<wkbook.getNumberOfSheets(); x++) {
				Sheet sheet = wkbook.getSheetAt( x );
				String sName = sheet.getSheetName();
				int stR = isFull ? 0 : sheet.getFirstRowNum();				// fullの場合は、0 から開始
				int edR = sheet.getLastRowNum();
				for (int y=stR; y<=edR; y++) {								// getLastRowNum は、含む
					Row rowObj = sheet.getRow( y );
					if( rowObj != null ) {
						int stC = isFull ? 0 : rowObj.getFirstCellNum();	// fullの場合は、0 から開始
						int edC = rowObj.getLastCellNum();
						for (int z=stC; z<edC; z++) {						// getLastCellNum は、含まない
							Cell colObj = rowObj.getCell( z );
							if( colObj != null && colObj.getCellType() != Cell.CELL_TYPE_BLANK ) { 
								String text = getValue( colObj );
								event.readText( text,sName,x,y,z );			// Cellテキスト Sheet名 0:Sheet番号 1:Row番号 2:Cell番号
							}
							else if( isFull ) {
								// カラムが存在しない場合のイベント
								event.readText( null,sName,x,y,z );			// Cellテキスト Sheet名 0:Sheet番号 1:Row番号 2:Cell番号
							}
						}
					}
					else if( isFull ) {
						// 行が存在しない場合のイベント
						event.readText( null,sName,x,y,-1 );				// Cellテキスト Sheet名 0:Sheet番号 1:Row番号 2:Cell番号
					}
				}
			}
//		}
//		catch( IOException ex ) {
//			String errMsg = "ファイル読込みエラー[" + fname + "]" + CR + ex.getMessage() ;
//			throw new RuntimeException( errMsg,ex );
//		}
//		catch( InvalidFormatException ex ) {
//			String errMsg = "ファイル形式エラー[" + fname + "]" + CR + ex.getMessage() ;
//			throw new RuntimeException( errMsg,ex );
//		}
//		finally {
//			Closer.ioClose( fis );
//		}
	}

	/**
	 * セルオブジェクト(Cell)から値を取り出します。
	 *
	 * セルオブジェクトが存在しない場合は、null を返します。
	 *
	 * @og.rev 3.8.5.3 (2006/08/07) 取り出し方法を少し修正
	 * @og.rev 5.5.1.2 (2012/04/06) フォーマットセルを実行して、その結果を再帰的に処理する。
	 *
	 * @param	oCell EXCELのセルオブジェクト
	 *
	 * @return	セルの値
	 */
	public static String getValue( final Cell oCell ) {
		if( oCell == null ) { return null; }

		String strText = "";
		int nCellType = oCell.getCellType();
		switch(nCellType) {
			case Cell.CELL_TYPE_NUMERIC:
					strText = getNumericTypeString( oCell );
					break;
			case Cell.CELL_TYPE_STRING:
	// POI3.0		strText = oCell.getStringCellValue();
					RichTextString richText = oCell.getRichStringCellValue();
					if( richText != null ) {
						strText = richText.getString();
					}
					break;
			case Cell.CELL_TYPE_FORMULA:
	// POI3.0		strText = oCell.getStringCellValue();
					// 5.5.1.2 (2012/04/06) フォーマットセルを実行して、その結果を再帰的に処理する。
					Workbook wb = oCell.getSheet().getWorkbook();
					CreationHelper crateHelper = wb.getCreationHelper();
					FormulaEvaluator evaluator = crateHelper.createFormulaEvaluator();

					try {
						strText = getValue(evaluator.evaluateInCell(oCell));
					}
					catch ( Throwable th ) {
						String errMsg = "セルフォーマットが解析できません。[" + oCell.getCellFormula() + "]"
									+ CR + getCellMsg( oCell );
						throw new RuntimeException( errMsg,th );
					}
					break;
			case Cell.CELL_TYPE_BOOLEAN:
					strText = String.valueOf(oCell.getBooleanCellValue());
					break;
			case Cell.CELL_TYPE_BLANK :
			case Cell.CELL_TYPE_ERROR:
					break;
			default :
				break;
		}
		return strText.trim();
	}

	/**
	 * セル値が数字の場合に、数字か日付かを判断して、対応する文字列を返します。
	 *
	 * @og.rev 3.8.5.3 (2006/08/07) 新規追加
	 * @og.rev 5.5.7.2 (2012/10/09) HybsDateUtil を利用するように修正します。
	 *
	 * @param	oCell EXCELのセルオブジェクト
	 *
	 * @return	数字の場合は、文字列に変換した結果を、日付の場合は、"yyyyMMddHHmmss" 形式で返します。
	 */
	public static String getNumericTypeString( final Cell oCell ) {
		final String strText ;

		double dd = oCell.getNumericCellValue() ;
		if( DateUtil.isCellDateFormatted( oCell ) ) {
			strText = HybsDateUtil.getDate( DateUtil.getJavaDate( dd ).getTime() , "yyyyMMddHHmmss" );	// 5.5.7.2 (2012/10/09) HybsDateUtil を利用
		}
		else {
			NumberFormat numFormat = NumberFormat.getInstance();
			if( numFormat instanceof DecimalFormat ) {
				((DecimalFormat)numFormat).applyPattern( "#.####" );
			}
			strText = numFormat.format( dd );
		}
		return strText ;
	}

	/**
	 * 全てのSheetに対して、autoSizeColumn設定を行います。
	 *
	 * 重たい処理なので、ファイルの書き出し直前に一度だけ実行するのがよいでしょう。
	 * autoSize設定で、カラム幅が大きすぎる場合、現状では、
	 * 初期カラム幅のmaxColCount倍を限度に設定します。
	 * ただし、maxColCount がマイナスの場合は、無制限になります。
	 *
	 * @og.rev 6.0.2.0 (2014/09/19) 新規作成
	 *
	 * @param	wkbook		処理対象のWorkbook
	 * @param	maxColCount	最大幅を標準セル幅の何倍にするかを指定。マイナスの場合は、無制限
	 * @param	dataStRow	データ行の開始位置。未設定時は、-1
	 */
	public static void autoCellSize( final Workbook wkbook , final int maxColCount , final int dataStRow ) {
		int shCnt = wkbook.getNumberOfSheets();

		for( int shNo=0; shNo<shCnt; shNo++ ) {
			Sheet sht = wkbook.getSheetAt( shNo );
			int defW = sht.getDefaultColumnWidth();		// 標準カラムの文字数
			int maxWidth = defW*256*maxColCount ;		// Widthは、文字数(文字幅)*256*最大セル数

			int stR = sht.getFirstRowNum();
			int edR = sht.getLastRowNum();

			Row rowObj = sht.getRow( stR );
			int stC = rowObj.getFirstCellNum();
			int edC = rowObj.getLastCellNum();				// 含まない

			// SheetUtil を使用して、計算範囲を指定します。
			if( stR < dataStRow ) { stR = dataStRow; }		// 計算範囲
			for( int colNo=stC; colNo<edC; colNo++ ) {
				double wpx = SheetUtil.getColumnWidth( sht,colNo,true,stR,edR );
				if( wpx >= 0.0d ) {							// Cellがないと、マイナス値が戻る。
					int wd = (int)Math.ceil(wpx * 256) ;
					if( maxWidth >= 0 && wd > maxWidth ) { wd = maxWidth; }	// 最大値が有効な場合は、置き換える
					sht.setColumnWidth( colNo,wd );
				}
			}

			// Sheet#autoSizeColumn(int) を使用して、自動計算させる場合。
	//		for( int colNo=stC; colNo<edC; colNo++ ) {
	//			sht.autoSizeColumn( colNo );
	//			if( maxWidth >= 0 ) {					// 最大値が有効な場合は、置き換える
	//				int wd = sht.getColumnWidth( colNo );
	//				if( wd > maxWidth ) { sht.setColumnWidth( colNo,maxWidth ); }
	//			}
	//		}
		}
	}

	/**
	 * 指定の Workbook の全Sheetを対象に、空行を取り除き、全体をシュリンクします。
	 *
	 * この処理は、#saveFile( String ) の直前に行うのがよいでしょう。
	 *
	 * ここでは、Row を逆順にスキャンし、Cellが 存在しない間は、行を削除します。
	 * 途中の空行の削除ではなく、最終行かららの連続した空行の削除です。
	 * 
	 * isCellDel=true を指定すると、Cellの末尾削除を行います。
	 * 有効行の最後のCellから空セルを削除していきます。
	 * 表形式などの場合は、Cellのあるなしで、レイアウトが崩れる場合がありますので
	 * 処理が不要な場合は、isCellDel=false を指定してください。
	 *
	 * @og.rev 6.0.2.0 (2014/09/19) 新規作成
	 * @og.rev 6.0.2.3 (2014/10/10) CellStyle の有無も判定基準に含めます。
	 *
	 * @param	wkbook		処理対象のWorkbook
	 * @param	isCellDel	Cellの末尾削除を行うかどうか(true:行う/false:行わない)
	 */
	public static void activeWorkbook( final Workbook wkbook , final boolean isCellDel ) {
		int shCnt = wkbook.getNumberOfSheets();
		for( int shNo=0; shNo<shCnt; shNo++ ) {
			Sheet sht = wkbook.getSheetAt( shNo );

			int stR = sht.getFirstRowNum();
			int edR = sht.getLastRowNum();

			boolean isRowDel = true;								// 行の削除は、Cellが見つかるまで。
			for( int rowNo=edR; rowNo>=stR; rowNo-- ) {				// 逆順に処理します。
				Row rowObj = sht.getRow( rowNo );
				if( rowObj != null ) {
					int stC = rowObj.getFirstCellNum();
					int edC = rowObj.getLastCellNum();
					for( int colNo=edC; colNo>=stC; colNo-- ) {
						Cell colObj = rowObj.getCell( colNo );
						if( colObj != null ) {
							String val = POIUtil.getValue( colObj );
							if( colObj.getCellType() != Cell.CELL_TYPE_BLANK && val != null && val.length() > 0 ) { 
								isRowDel = false;					// 一つでも現れれば、行の削除は中止
								break;
							}
							// 6.0.2.3 (2014/10/10) CellStyle の有無も判定基準に含めます。
							else if( colObj.getCellStyle() != null ) {
								isRowDel = false;					// 一つでも現れれば、行の削除は中止
								break;
							}
							else if( isCellDel ) {
								rowObj.removeCell( colObj );		// CELL_TYPE_BLANK の場合は、削除
							}
						}
					}
					if( isRowDel ) { sht.removeRow( rowObj );	}
					else if( !isCellDel ) { break; }				// Cell の末尾削除を行わない場合は、break すればよい。
				}
			}
		}
	}

	/**
	 * ファイルから、Workbookオブジェクトを新規に作成します。
	 *
	 * @og.rev 6.0.2.3 (2014/10/10) 新規作成
	 *
	 * @param	fname	入力ファイル
	 * @return	Workbookオブジェクト
	 */
	public static Workbook createWorkbook( final String fname ) {
		InputStream fis = null;
		try {
			// File オブジェクトでcreate すると、ファイルがオープンされたままになってしまう。
			fis = new BufferedInputStream( new FileInputStream( fname ) );
			return WorkbookFactory.create( fis );
		}
		catch( IOException ex ) {
			String errMsg = "ファイル読込みエラー[" + fname + "]" + CR + ex.getMessage() ;
			throw new RuntimeException( errMsg,ex );
		}
		catch( InvalidFormatException ex ) {
			String errMsg = "ファイル形式エラー[" + fname + "]" + CR + ex.getMessage() ;
			throw new RuntimeException( errMsg,ex );
		}
		finally {
			Closer.ioClose( fis );
		}
	}

	/**
	 * シート一覧を取得します。
	 *
	 * EXCEL上のシート名を、配列で返します。
	 *
	 * @og.rev 6.0.2.3 (2014/10/10) 新規作成
	 *
	 * @param	wkbook Workbookオブジェクト
	 * @return	シート名の配列
	 */
	public static String[] getSheetNames( final Workbook wkbook ) {
		int shCnt = wkbook.getNumberOfSheets();

		String[] shtNms = new String[shCnt];

		for( int i=0; i<shCnt; i++ ) {
			Sheet sht = wkbook.getSheetAt( i );
			shtNms[i] = sht.getSheetName();
		}

		return shtNms;
	}

	/**
	 * 名前定義一覧を取得します。
	 *
	 * EXCEL上に定義された名前を、配列で返します。
	 * ここでは、名前とFormulaをタブで連結した文字列を配列で返します。
	 * Name オブジェクトを削除すると、EXCELが開かなくなったりするので、
	 * 取りあえず一覧を作成して、手動で削除してください。
	 * なお、名前定義には、非表示というのがありますので、ご注意ください。
	 *
	 * ◆ 非表示になっている名前の定義を表示にする EXCEL VBA マクロ
	 * http://dev.classmethod.jp/tool/excel-delete-name/
	 *   Sub VisibleNames()
	 *      Dim name As Object
	 *      For Each name In Names
	 *          If name.Visible = False Then
	 *              name.Visible = True
	 *          End If
	 *      Next
	 *      MsgBox "すべての名前の定義を表示しました。", vbOKOnly
	 *   End Sub
	 *
	 * ◆ 名前の一括削除 EXCEL VBA マクロ
	 * http://komitsudo.blog70.fc2.com/blog-entry-104.html
	 *   Sub delname()
	 *      Dim s
	 *      On Error Resume Next
	 *      For Each s In ActiveWorkbook.Names
	 *          If Not s.BuiltIn Then
	 *              s.Delete
	 *          End If
	 *      Next
	 *   End Sub
	 *
	 * @og.rev 6.0.2.3 (2014/10/10) 新規作成
	 *
	 * @param	wkbook Workbookオブジェクト
	 * @return	名前定義(名前+TAB+Formula)の配列
	 */
	public static String[] getNames( final Workbook wkbook ) {
		int cnt = wkbook.getNumberOfNames();

		Set<String> nmSet = new TreeSet<String>();

		for( int i=0; i<cnt; i++ ) {
			String name	= null;
			String ref	= null;

			Name nm = wkbook.getNameAt(i);
			try {
				name = nm.getNameName();
				ref  = nm.getRefersToFormula();
			}
			catch( Exception ex ) {
				// Excel97形式の場合、getRefersToFormula() でエラーが発生することがある。
			}

			nmSet.add( name + "\t" + ref );

			// 削除するとEXCELが壊れる？ なお、削除時には逆順で廻さないとアドレスがずれます。
			// if( nm.isDeleted() ) { wkbook.removeName(i); }
		}

		return nmSet.toArray( new String[nmSet.size()] );
	}

	/**
	 * 書式のスタイル一覧を取得します。
	 *
	 * EXCEL上に定義された書式のスタイルを、配列で返します。
	 * 書式のスタイルの名称は、CellStyle にメソッドが定義されていません。
	 * 実クラスである HSSFCellStyle にキャストして使用する
	 * 必要があります。(XSSFCellStyle にも名称を取得するメソッドがありません。)
	 *
	 * ◆ スタイルの一括削除 EXCEL VBA マクロ
	 * http://komitsudo.blog70.fc2.com/blog-entry-104.html
	 *   Sub delstyle()
	 *      Dim s
	 *      On Error Resume Next
	 *      For Each s In ActiveWorkbook.Styles
	 *          If Not s.BuiltIn Then
	 *              s.Delete
	 *          End If
	 *      Next
	 *   End Sub
	 *
	 * @og.rev 6.0.2.3 (2014/10/10) 新規作成
	 *
	 * @param	wkbook Workbookオブジェクト
	 * @return	書式のスタイル一覧
	 */
	public static String[] getStyleNames( final Workbook wkbook ) {
		int cnt = wkbook.getNumCellStyles();		// return 値は、short

		Set<String> nmSet = new TreeSet<String>();

		for( int s=0; s<cnt; s++ ) {
			CellStyle cs = wkbook.getCellStyleAt( (short)s );
			if( cs instanceof HSSFCellStyle ) {
				HSSFCellStyle hcs = (HSSFCellStyle)cs;
				String name = hcs.getUserStyleName();
				if( name != null ) { nmSet.add( name ); }
				else {												// この処理は不要かも。
					HSSFCellStyle pst = hcs.getParentStyle();
					if( pst != null ) {
						String pname = pst.getUserStyleName();
						if( pname != null ) { nmSet.add( pname ); }
					}
				}
			}
		}

		return nmSet.toArray( new String[nmSet.size()] );
	}

	/**
	 * セル情報を返します。
	 *
	 * エラー発生時に、どのセルでエラーが発生したかの情報を取得できるようにします。
	 *
	 * @og.rev 6.0.2.0 (2014/09/19) 新規作成
	 *
	 * @param	oCell EXCELのセルオブジェクト
	 * @return	セル情報の文字列
	 */
	public static String getCellMsg( final Cell oCell ) {
		String lastMsg = null;

		if( oCell != null ) {
			int rowNo = oCell.getRowIndex();
			int celNo = oCell.getColumnIndex();
			String shtNm = oCell.getSheet().getSheetName();

			lastMsg = " Sheet=" + shtNm + ", Row=" + rowNo + ", Cel=" + celNo ;
		}

		return lastMsg;
	}

	/**
	 * アプリケーションのサンプルです。
	 *
	 * 入力ファイル名 は必須で、第一引数固定です。
	 * 第二引数は、処理方法で、指定しない場合は、-ALL として処理します。
	 *
	 * Usage: java org.opengion.fukurou.util.POIUtil 入力ファイル名 [パラメータ]
	 *   -A(LL)        ･･･ ALL 一括処理(初期値)
	 *   -W(ORD)       ･･･ Word
	 *   -P(PT)        ･･･ PoworPoint
	 *   -E(XCEL)      ･･･ Excel
	 *   -F(ULL EXCEL) ･･･ Full Excel
	 *   -S(heet)      ･･･ Sheet名一覧
	 *   -N(AME)       ･･･ NAME:名前定義
	 *   -C(ellStyle)  ･･･ CellStyle:書式のスタイル
	 *
	 * @og.rev 6.0.2.0 (2014/09/19) 新規作成
	 *
	 * @param	args	コマンド引数配列
	 */
	public static void main( final String[] args ) {
		final String usageMsg = "Usage: java org.opengion.fukurou.util.POIUtil 入力ファイル名 [パラメータ]" + "\n" +
								"\t -A(LL)        ･･･ ALL 一括処理(初期値)      \n" +
								"\t -W(ORD)       ･･･ Word                      \n" +
								"\t -P(PT)        ･･･ PoworPoint                \n" +
								"\t -E(XCEL)      ･･･ Excel                     \n" +
								"\t -F(ULL EXCEL) ･･･ Full Excel                \n" +
								"\t -S(heet)      ･･･ Sheet名一覧               \n" +
								"\t -N(AME)       ･･･ NAME:名前定義             \n" +
								"\t -C(ellStyle)  ･･･ CellStyle:書式のスタイル  \n" ;
		if( args.length == 0 ) {
			System.err.println( usageMsg );
			return ;
		}

		String fname = args[0];
		char   type  = (args.length == 1) ? 'A' : args[1].charAt(1) ;

		switch( type ) {
			case 'A' :  System.out.println( POIUtil.getText(fname) );
						break;
			case 'W' :  POIUtil.wordReader(
							fname,
							new POIEvent() {
								public void readText( final String text , final String cmnt , final int... args ) {
									System.out.println( "S[" + args[0] + "],P[" + fname + "],G[" + args[2] + "],C[" + cmnt + "]=" + text );
								}
							}
						);
						break;
			case 'P' :  POIUtil.pptReader(
							fname,
							new POIEvent() {
								public void readText( final String text , final String cmnt , final int... args ) {
						//			System.out.println( "S[" + args[0] + "],T[" + fname + "],C[" + cmnt + "]=" + text );
									System.out.println( "S[" + args[0] + "],T[" + fname + "]=" + text );
								}
							}
						);
						break;
			case 'E' :  POIUtil.excelReader(
							fname,
							new POIEvent() {
								public void readText( final String text , final String cmnt , final int... args ) {
									System.out.println( cmnt + ":S[" + args[0] + "],R[" + fname + "],C[" + args[2] + "]=" + text );
								}
							}
						);
						break;
			case 'F' :  StringBuilder buf = new StringBuilder();
						POIUtil.excelReader(
							fname,
							new POIEvent() {
								int bkSht = -1;		// シートブレーク
								int bkRow = -1;		// 行ブレーク
								
								public void readText( final String text , final String cmnt , final int... args ) {
									if( bkSht != args[0] ) {			// シートブレーク
										if( buf.length() > 0 ) {
											System.out.println( buf );
											buf.setLength(0);			// Clearの事
										}
										System.out.println( "SheetName=" + cmnt + ":S[" + args[0] + "]" );
										bkSht = args[0];
										bkRow = 0;
									}
									if( bkRow != args[1] ) {			// 行ブレーク
										bkRow = args[1];
										buf.append( CR );
									}
									buf.append( "\t" );
									if( text != null ) { buf.append( text ); }
								}
							},
							true		// Full 行列処理
						);
						System.out.println( buf );				// 処理が終了したかどうかはイベントでは判らない。
						break;
			case 'S' :  String[] shts = POIUtil.getSheetNames( POIUtil.createWorkbook(fname) );
						System.out.println( "No:\tSheetName" );
						for( int i=0; i<shts.length; i++ ) {
							System.out.println( i + "\t" + shts[i] );
						}
						break;
			case 'N' :  String[] nms = POIUtil.getNames( POIUtil.createWorkbook(fname) );
						System.out.println( "No:\tName\tFormula" );
						for( int i=0; i<nms.length; i++ ) {
							System.out.println( i + "\t" + nms[i] );
						}
						break;
			case 'C' :  String[] sns = POIUtil.getStyleNames( POIUtil.createWorkbook(fname) );
						System.out.println( "No:\tStyleName" );
						for( int i=0; i<sns.length; i++ ) {
							System.out.println( i + "\t" + sns[i] );
						}
						break;
			default :   System.err.println( usageMsg );
						break;
		}
	}
}
