/*
 * 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.model;

import java.io.InputStream;
import java.io.FileInputStream;
import java.io.BufferedReader;							// 6.2.2.0 (2015/03/27)
import java.io.BufferedInputStream;
import java.io.FileNotFoundException;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;								// 6.2.2.0 (2015/03/27)
import java.nio.charset.Charset;						// 6.2.2.0 (2015/03/27)

// import java.text.DecimalFormat;
// import java.text.NumberFormat;
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.xmlbeans.XmlException;
import org.apache.poi.POITextExtractor;
import org.apache.poi.extractor.ExtractorFactory;
import org.apache.poi.hwpf.HWPFDocument;
import org.apache.poi.hwpf.usermodel.Range;
import org.apache.poi.hwpf.usermodel.Paragraph;
import org.apache.poi.xwpf.usermodel.XWPFDocument;		// 6.2.0.0 (2015/02/27)
import org.apache.poi.xwpf.usermodel.XWPFParagraph;		// 6.2.0.0 (2015/02/27)
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.xslf.usermodel.XMLSlideShow;					// 6.2.0.0 (2015/02/27)
import org.apache.poi.xslf.extractor.XSLFPowerPointExtractor;		// 6.2.0.0 (2015/02/27)
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.openxml4j.exceptions.OpenXML4JException ;		// 6.1.0.0 (2014/12/26) findBugs
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.system.OgRuntimeException ;					// 6.4.2.0 (2016/01/29)
// import org.opengion.fukurou.system.DateSet;							// 6.4.2.0 (2016/01/29)
import org.opengion.fukurou.util.FileInfo;								// 6.2.3.0 (2015/05/01)
// import org.opengion.fukurou.util.StringUtil;							// 6.2.0.0 (2015/02/27)
import org.opengion.fukurou.system.ThrowUtil;							// 6.4.2.0 (2016/01/29)
// import org.opengion.fukurou.util.HybsDateUtil;						// 6.2.0.0 (2015/02/27)
import org.opengion.fukurou.system.Closer;								// 6.2.0.0 (2015/02/27)
import static org.opengion.fukurou.system.HybsConst.CR;					// 6.1.0.0 (2014/12/26) refactoring
import static org.opengion.fukurou.system.HybsConst. BUFFER_MIDDLE ;	// 6.4.2.1 (2016/02/05) refactoring

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

	// 6.2.3.0 (2015/05/01)
	public static final String POI_SUFIX = "ppt,pptx,doc,docx,xls,xlsx,xlsm" ;

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

	/**
	 * 引数ファイルが、POI関連の拡張子ファイルかどうかを判定します。
	 *
	 * Excel、Word、PowerPoint、Visio、Publisher からのテキスト取得が可能です。
	 * 拡張子が、ppt,pptx,doc,docx,xls,xlsx,xlsm のファイルの場合、true を返します。
	 *
	 * @og.rev 6.2.3.0 (2015/05/01) POI関連の拡張子ファイルかどうかを判定
	 *
	 * @param	file 判定するファイル
	 * @return	POI関連の拡張子の場合、true
	 */
	public static boolean isPOI( final File file ) {
		return POI_SUFIX.contains( FileInfo.getSUFIX( file ) );
	}

	/**
	 * 引数ファイルを、POITextExtractor を使用してテキスト化します。
	 *
	 * Excel、Word、PowerPoint、Visio、Publisher からのテキスト取得が可能です。
	 * 拡張子から、ファイルの種類を自動判別します。
	 *
	 * @og.rev 6.0.2.0 (2014/09/19) 新規作成
	 * @og.rev 6.2.0.0 (2015/02/27) getText → extractor に変更
	 *
	 * @param	file 入力ファイル名
	 * @return	変換後のテキスト
	 * @og.rtnNotNull
	 */
	public static String extractor( final File file ) {
	//	InputStream fis = null;
		POITextExtractor extractor = null;
		try {
	//		fis = new BufferedInputStream( new FileInputStream( file ) );
	//		extractor = ExtractorFactory.createExtractor( fis );
			extractor = ExtractorFactory.createExtractor( file );
			return extractor.getText();
		}
		catch( FileNotFoundException ex ) {
			final String errMsg = "ファイルが存在しません[" + file + "]" + CR + ex.getMessage() ;
			throw new OgRuntimeException( errMsg,ex );
		}
		catch( IOException ex ) {
			final String errMsg = "ファイル処理エラー[" + file + "]" + CR + ex.getMessage() ;
			throw new OgRuntimeException( errMsg,ex );
		}
		catch( InvalidFormatException ex ) {
			final String errMsg = "ファイルフォーマットエラー[" + file + "]" + CR + ex.getMessage() ;
			throw new OgRuntimeException( errMsg,ex );
		}
		catch( OpenXML4JException ex ) {
			final String errMsg = "ODF-XML処理エラー[" + file + "]" + CR + ex.getMessage() ;
			throw new OgRuntimeException( errMsg,ex );
		}
		catch( XmlException ex ) {
			final String errMsg = "XML処理エラー[" + file + "]" + CR + ex.getMessage() ;
			throw new OgRuntimeException( errMsg,ex );
		}
		finally {
			Closer.ioClose( extractor );
	//		Closer.ioClose( fis );
		}
	}

	/**
	 * 引数ファイル(Text)を、テキスト化します。
	 *
	 * ここでは、ファイルとエンコードを指定して、ファイルのテキスト全てを読み取ります。
	 *
	 * @og.rev 6.2.2.0 (2015/03/27) 引数ファイル(Text)を、テキスト化。
	 * @og.rev 6.2.3.0 (2015/05/01) textReader → extractor に変更
	 *
	 * @param	file 入力ファイル
	 * @param	encode エンコード名
	 * @return	ファイルのテキスト
	 */
	public static String extractor( final File file , final String encode ) {
		try {
			// 指定のファイルをバイト列として読み込む
			final byte[] bytes = Files.readAllBytes( file.toPath() );
			// 読み込んだバイト列を エンコードして文字列にする
			return new String( bytes, encode );
		}
	//	catch ( UnsupportedEncodingException ex ) {
	//		final String errMsg = "エンコードが不正です[" + file + "] , ENCODE=[" + encode + "]"  ;
	//		throw new OgRuntimeException( errMsg,ex );
	//	}
		catch ( IOException ex ) {
			final String errMsg = "ファイル読込みエラー[" + file + "] , ENCODE=[" + encode + "]"  ;
			throw new OgRuntimeException( errMsg,ex );
		}
	}

	/**
	 * 引数ファイル(Text)を、テキスト化します。
	 *
	 * ここでは、ファイルとエンコードを指定して、ファイルのテキスト全てを読み取ります。
	 *
	 * @og.rev 6.2.2.0 (2015/03/27) 引数ファイル(Text)を、テキスト化。
	 *
	 * @param	file 入力ファイル
//	 * @param	helper イベント処理させるI/F
	 * @param	conv   イベント処理させるI/F
	 * @param	encode エンコード名
	 */
//	public static void textReader( final File file , final TableModelHelper helper , final String encode ) {
	public static void textReader( final File file , final TextConverter<String,String> conv , final String encode ) {
		BufferedReader reader = null ;

		int rowNo = 0;		// 6.2.0.0 (2015/02/27) 行番号
		try {
			reader = Files.newBufferedReader( file.toPath() , Charset.forName( encode ) );

			String line ;
			while((line = reader.readLine()) != null) {
//				helper.value( line,rowNo++,0 );				// 6.2.0.0 (2015/02/27) イベント変更
				conv.change( line,String.valueOf( rowNo++ ) );
			}
		}
		catch ( IOException ex ) {
			final String errMsg = "ファイル読込みエラー[" + file + "] , ENCODE=[" + encode + "] , ROW=[" + rowNo + "]"  ;
			throw new OgRuntimeException( errMsg,ex );
		}
		finally {
			Closer.ioClose( reader );
//			helper.endFile( file );					// 6.2.0.0 (2015/02/27)
		}
	}

	/**
	 * 引数ファイル(Word,PoworPoint,Excel)を、TableModelHelper を使用してテキスト化します。
	 *
	 * ここでは、ファイル名の拡張子で、処理するメソッドを選別します。
	 * 拡張子が、対象かどうかは、#isPOI( File ) メソッドで判定できます。
	 *
	 * TableModelHelper によるイベント処理できますが、TEXTというカラム名を持つ
	 * 表形式オブジェクトの形で処理されます。
	 * また、内部的に、先頭に、# がある場合や、行データが存在しない場合は、
	 * スキップされます。
	 *
	 * @og.rev 6.2.3.0 (2015/05/01) 新規作成
	 * @og.rev 6.2.5.0 (2015/06/05) xls,xlsxは、それぞれ excelReader1,excelReader2 で処理します。
	 *
	 * @param	file 入力ファイル
//	 * @param	helper イベント処理させるI/F
	 * @param	conv   イベント処理させるI/F
	 */
//	public static void textReader( final File file , final TableModelHelper helper ) {
	public static void textReader( final File file , final TextConverter<String,String> conv ) {
		final String SUFIX = FileInfo.getSUFIX( file );

		if( "doc".equalsIgnoreCase( SUFIX ) ) {
//			wordReader1( file,helper );
			wordReader1( file,conv );
		}
		else if( "docx".equalsIgnoreCase( SUFIX ) ) {
//			wordReader2( file,helper );
			wordReader2( file,conv );
		}
		else if( "ppt".equalsIgnoreCase( SUFIX ) ) {
//			pptReader1( file,helper );
			pptReader1( file,conv );
		}
		else if( "pptx".equalsIgnoreCase( SUFIX ) ) {
//			pptReader2( file,helper );
			pptReader2( file,conv );
		}
		else if( "xls".equalsIgnoreCase( SUFIX ) ) {
//			excelReader1( file,helper );							// 6.2.5.0 (2015/06/05)
			excelReader1( file,conv );								// 6.2.5.0 (2015/06/05)
		}
		else if( "xlsx".equalsIgnoreCase( SUFIX ) || "xlsm".equalsIgnoreCase( SUFIX ) ) {
//			excelReader2( file,helper );							// 6.2.5.0 (2015/06/05)
			excelReader2( file,conv );								// 6.2.5.0 (2015/06/05)
		}
		else {
			final String errMsg = "拡張子は、" +  POI_SUFIX + " にしてください。[" + file + "]" ;
			throw new OgRuntimeException( errMsg );
		}
	}

	/**
	 * 引数ファイル(Word)を、HWPFDocument を使用してテキスト化します。
	 *
	 * 拡張子(.doc)のファイルを処理します。
	 * TableModelHelper によるイベント処理できますが、TEXTというカラム名を持つ
	 * 表形式オブジェクトの形で処理されます。
	 * また、内部的に、先頭に、# がある場合や、行データが存在しない場合は、
	 * スキップされます。
	 *
	 * @og.rev 6.0.2.0 (2014/09/19) 新規作成
	 * @og.rev 6.2.0.0 (2015/02/27) POIEvent → TableModelHelper に変更
	 * @og.rev 6.2.4.2 (2015/05/29) 改行以外に、「。」で分割します。
	 *
	 * @param	file 入力ファイル名
//	 * @param	helper イベント処理させるI/F
	 * @param	conv   イベント処理させるI/F
	 */
//	private static void wordReader1( final File file , final TableModelHelper helper ) {
	private static void wordReader1( final File file , final TextConverter<String,String> conv ) {
		InputStream fis  = null;

		try {
			// 6.2.0.0 (2015/02/27) TableModelHelper 変更に伴う修正
//			helper.startFile( file );

			fis = new BufferedInputStream( new FileInputStream( file ) );		// 6.2.0.0 (2015/02/27)
			final HWPFDocument doc = new HWPFDocument( fis );

			int rowNo = 0;		// 6.2.0.0 (2015/02/27) 行番号

	//		// WordExtractor を使ったサンプル
	//		WordExtractor we = new WordExtractor( doc );
	//		for( String txt : we.getParagraphText() ) {
	//			String text = WordExtractor.stripFields( txt )
	//							.replaceAll( "\\x13[^\\x01]+\\x01\\x14" , "" )
	//							.replaceAll( "\\x0b" , "\n" ).trim();
	//			helper.value( text.trim(),rowNo++,0 );				// 6.2.0.0 (2015/02/27) イベント変更
	//		}

			// Range,Paragraph を使ったサンプル
			final Range rng = doc.getRange();
			for( int pno=0; pno<rng.numParagraphs(); pno++ ) {
				final Paragraph para = rng.getParagraph(pno);
				final String text = Range.stripFields( para.text() )
								.replaceAll( "\\x13[^\\x01]+\\x01\\x14" , "" )
								.replaceAll( "\\x0b" , "\n" ).trim();
//				helper.value( text,rowNo++,0 );				// 6.2.0.0 (2015/02/27) イベント変更
				conv.change( text, String.valueOf( rowNo++ ) );
			}

			// Range,Paragraph,CharacterRun を使ったサンプル(変な個所で文字が分断される)
	//		final Range rng = doc.getRange();
	//		for( int pno = 0; pno < rng.numParagraphs(); pno++ ) {
	//			final Paragraph para = rng.getParagraph(pno);
	//			for( int cno = 0; cno < para.numCharacterRuns(); cno++ ) {
	//				final CharacterRun crun = para.getCharacterRun(cno);
	//				String text = Range.stripFields( crun.text() )
	//								.replaceAll( "\\x13[^\\x01]+\\x01\\x14" , "" )
	//								.replaceAll( "\\x0b" , "\n" ).trim();
	//				helper.value( text,rowNo++,0 );				// 6.2.0.0 (2015/02/27) イベント変更
	//			}
	//		}
		}
		catch( IOException ex ) {
			final String errMsg = "ファイル読込みエラー[" + file + "]" + CR + ex.getMessage() ;
			throw new OgRuntimeException( errMsg,ex );
		}
		finally {
			Closer.ioClose( fis );
//			helper.endFile( file );					// 6.2.0.0 (2015/02/27)
		}
	}

	/**
	 * 引数ファイル(Word)を、XWPFDocument を使用してテキスト化します。
	 *
	 * 拡張子(.docx)のファイルを処理します。
	 * TableModelHelper によるイベント処理できますが、TEXTというカラム名を持つ
	 * 表形式オブジェクトの形で処理されます。
	 * また、内部的に、先頭に、# がある場合や、行データが存在しない場合は、
	 * スキップされます。
	 *
	 * @og.rev 6.0.2.0 (2014/09/19) 新規作成
	 * @og.rev 6.2.0.0 (2015/02/27) POIEvent → TableModelHelper に変更
	 * @og.rev 6.2.4.2 (2015/05/29) 改行以外に、「。」で分割します。
	 *
	 * @param	file 入力ファイル
//	 * @param	helper イベント処理させるI/F
	 * @param	conv   イベント処理させるI/F
	 */
//	private static void wordReader2( final File file , final TableModelHelper helper ) {
	private static void wordReader2( final File file , final TextConverter<String,String> conv ) {
		InputStream fis  = null;

		try {
			// 6.2.0.0 (2015/02/27) TableModelHelper 変更に伴う修正
//			helper.startFile( file );

			fis = new BufferedInputStream( new FileInputStream( file ) );		// 6.2.0.0 (2015/02/27)
			final XWPFDocument doc = new XWPFDocument( fis );

			int rowNo = 0;		// 6.2.0.0 (2015/02/27) 行番号
			for( final XWPFParagraph para : doc.getParagraphs() ) {
	//			for( final XWPFRun run : para.getRuns() ) {
	//				helper.value( run.toString(),rowNo++,0 );				// 6.2.0.0 (2015/02/27) イベント変更
	//			}
				final String text = para.getParagraphText().trim();
//				helper.value( text,rowNo++,0 );					// 6.2.0.0 (2015/02/27) イベント変更
				conv.change( text, String.valueOf( rowNo++ ) );
			}
		}
		catch( IOException ex ) {
			final String errMsg = "ファイル読込みエラー[" + file + "]" + CR + ex.getMessage() ;
			throw new OgRuntimeException( errMsg,ex );
		}
		finally {
			Closer.ioClose( fis );
//			helper.endFile( file );					// 6.2.0.0 (2015/02/27)
		}
	}

	/**
	 * 引数ファイル(PoworPoint)を、HSLFSlideShow を使用してテキスト化します。
	 *
	 * 拡張子(.ppt)のファイルを処理します。
	 * TableModelHelper によるイベント処理できますが、TEXTというカラム名を持つ
	 * 表形式オブジェクトの形で処理されます。
	 * また、内部的に、先頭に、# がある場合や、行データが存在しない場合は、
	 * スキップされます。
	 *
	 * @og.rev 6.0.2.0 (2014/09/19) 新規作成
	 * @og.rev 6.2.0.0 (2015/02/27) POIEvent → TableModelHelper に変更
	 *
	 * @param	file 入力ファイル
//	 * @param	helper イベント処理させるI/F
	 * @param	conv   イベント処理させるI/F
	 */
//	private static void pptReader1( final File file , final TableModelHelper helper ) {
	private static void pptReader1( final File file , final TextConverter<String,String> conv ) {
		InputStream fis  = null;

		try {
			// 6.2.0.0 (2015/02/27) TableModelHelper 変更に伴う修正
//			helper.startFile( file );

			fis = new BufferedInputStream( new FileInputStream( file ) );		// 6.2.0.0 (2015/02/27)
			final SlideShow ss = new SlideShow( new HSLFSlideShow( fis ) );
			final Slide[] slides = ss.getSlides();
			int rowNo = 0;		// 6.2.0.0 (2015/02/27) 行番号
			for( int sno=0; sno<slides.length; sno++ ) {
		//		final String    title   = slides[sno].getTitle();				// title は、あまり当てにならない。
				final TextRun[] textRun = slides[sno].getTextRuns();
				for( int tno=0; tno<textRun.length; tno++ ) {
					final String text = textRun[tno].getText();
					// データとして設定されているレコードのみイベントを発生させる。
					if( text.length() > 0 ) {
//						helper.value( text,rowNo++,0 );					// 6.2.0.0 (2015/02/27) イベント変更
						conv.change( text, String.valueOf( rowNo++ ) );
					}
				}
			}
		}
		catch( IOException ex ) {
			final String errMsg = "ファイル読込みエラー[" + file + "]" + CR + ex.getMessage() ;
			throw new OgRuntimeException( errMsg,ex );
		}
		finally {
			Closer.ioClose( fis );
//			helper.endFile( file );					// 6.2.0.0 (2015/02/27)
		}
	}

	/**
	 * 引数ファイル(PoworPoint)を、XMLSlideShow を使用してテキスト化します。
	 *
	 * 拡張子(.pptx)のファイルを処理します。
	 * TableModelHelper によるイベント処理できますが、TEXTというカラム名を持つ
	 * 表形式オブジェクトの形で処理されます。
	 * また、内部的に、先頭に、# がある場合や、行データが存在しない場合は、
	 * スキップされます。
	 *
	 * @og.rev 6.0.2.0 (2014/09/19) 新規作成
	 * @og.rev 6.2.0.0 (2015/02/27) POIEvent → TableModelHelper に変更
	 * @og.rev 6.2.4.2 (2015/05/29) 行単位に、取り込むようにします。
	 *
	 * @param	file 入力ファイル
//	 * @param	helper イベント処理させるI/F
	 * @param	conv   イベント処理させるI/F
	 */
//	private static void pptReader2( final File file , final TableModelHelper helper ) {
	private static void pptReader2( final File file , final TextConverter<String,String> conv ) {
		InputStream fis  = null;

		try {
			// 6.2.0.0 (2015/02/27) TableModelEvent 変更に伴う修正
//			helper.startFile( file );

			fis = new BufferedInputStream( new FileInputStream( file ) );		// 6.2.0.0 (2015/02/27)
			final XMLSlideShow ss = new XMLSlideShow( fis );
			final XSLFPowerPointExtractor ext = new XSLFPowerPointExtractor( ss );
			final String[] vals = ext.getText().split( "\\n" );		// 6.2.4.2 (2015/05/29) 行単位に、取り込むようにします。
			for( int row=0; row<vals.length; row++ ) {
//				helper.value( vals[row],row,0 );
				conv.change( vals[row], String.valueOf( row ) );
			}

	//		final XSLFSlide[] slides = ss.getSlides();
	//		final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE );
	//		int rowNo = 0;		// 6.2.0.0 (2015/02/27) 行番号
	//		for( int sno = 0; sno < slides.length; sno++ ) {
	//			buf.setLength(0);		// Clearの事
    //
	//	//		final XSLFTextShape[] shp = slides[sno].getPlaceholders();
	//			final XSLFShape[] shp = slides[sno].getShapes();
	//			for( int tno = 0; tno < shp.length; tno++ ) {
	//	//			buf.append( shp[tno].getText() );
	//				buf.append( shp[tno].toString() );
	//			}
	//	//		String text = buf.toString().trim();
	//	//		event.value( text,rowNo++,0 );					// 6.2.0.0 (2015/02/27) イベント変更
	//			helper.value( buf.toString(),rowNo++,0 );		// 6.2.4.2 (2015/05/29) trim() しません。
	//		}
		}
		catch( IOException ex ) {
			final String errMsg = "ファイル読込みエラー[" + file + "]" + CR + ex.getMessage() ;
			throw new OgRuntimeException( errMsg,ex );
		}
		finally {
			Closer.ioClose( fis );
//			helper.endFile( file );					// 6.2.0.0 (2015/02/27)
		}
	}

	/**
	 * 引数ファイル(Excel)を、テキスト化します。
	 *
	 * TableModelHelper を与えることで、EXCELデータをテキスト化できます。
	 * ここでは、HSSF(.xls)形式を処理します。
	 * シート名、セル、テキストオブジェクトをテキスト化します。
	 *
	 * @og.rev 6.2.5.0 (2015/06/05) 新規作成
	 *
	 * @param	file 入力ファイル
//	 * @param	helper イベントオブジェクト(TableModelHelper)
	 * @param	conv   イベント処理させるI/F
	 * @see		org.opengion.fukurou.model.ExcelModel
	 */
//	public static void excelReader1( final File file , final TableModelHelper helper ) {
	public static void excelReader1( final File file , final TextConverter<String,String> conv ) {
//		excelReader2( file , helper );
		excelReader2( file , conv );
	}

	/**
	 * 引数ファイル(Excel)を、テキスト化します。
	 *
	 * TableModelHelper を与えることで、EXCELデータをテキスト化できます。
	 * ここでは、ExcelModelを使用して、(.xlsx , .xlsm)形式を処理します。
	 * シート名、セル、テキストオブジェクトをテキスト化します。
	 *
	 * @og.rev 6.2.5.0 (2015/06/05) 新規作成
	 * @og.rev 6.3.1.0 (2015/06/28) TextConverterに、引数(cmnt)を追加
	 * @og.rev 6.3.9.0 (2015/11/06) Java 8 ラムダ式に変更
	 *
	 * @param	file 入力ファイル
//	 * @param	helper イベントオブジェクト(TableModelHelper)
	 * @param	conv   イベント処理させるI/F
	 * @see		org.opengion.fukurou.model.ExcelModel
	 */
//	public static void excelReader2( final File file , final TableModelHelper helper ) {
	public static void excelReader2( final File file , final TextConverter<String,String> conv ) {
		final ExcelModel excel = new ExcelModel( file, true );

		// 6.3.9.0 (2015/11/06) Java 8 ラムダ式に変更
		// textConverter を使いますが、テキストを読み込むだけで、変換しません。
		excel.textConverter(
			( val,cmnt ) -> {
				conv.change( val,cmnt );	// 変換したくないので、引数の TextConverter を直接渡せない。
				return null;				// nullを返せば、変換しません。
			}
		);

//		// textConverter を使いますが、テキストを読み込むだけで、変換しません。
//		excel.textConverter(
//			new TextConverter<String,String>() {
//				private int row ;
//				/** 
//				 * 入力文字列を、変換します。
//				 *
//				 * @param	val  入力文字列
//				 * @param	cmnt コメント
//				 * @return	変換文字列(変換されない場合は、null)
//				 */
//				@Override
////				public String change( final String val ) {		// すべてのテキストを読み取る。
//				public String change( final String val , final String cmnt ) {		// すべてのテキストを読み取る。
////					helper.value( val  , row   , 0 );
////					helper.value( cmnt , row++ , 1 );
//					conv.change( val,cmnt );
//					return null;								// nullを返せば、変換しません。
//				}
//			}
//		);
	}

	/**
	 * Excelの行列記号を、行番号と列番号に分解します。
	 *
	 * Excelの行列記号とは、A1 , B5 , AA23 などの形式を指します。
	 * これを、行番号と列番号に分解します。例えば、A1→0行0列、B5→4行1列、AA23→22行26列 となります。
	 * 分解した結果は、内部変数の、rowNo と colNo にセットされます。
	 * これらは、0 から始まる int型の数字で表します。
	 *
	 *   ①行-列形式
	 *     行列は、EXCELオブジェクトに準拠するため、０から始まる整数です。
	 *     0-0 ⇒ A1 , 1-0 ⇒ A2 , 0-1 ⇒ B1 になります。
	 *   ②EXCEL表記
	 *     EXCEL表記に準拠した、A1,A2,B1 の記述も処理できるように対応します。
	 *     なお、A1,A2,B1 の記述は、必ず、英字1文字＋数字 にしてください。(A～Zまで)
	 *   ③EXCELシート名をキーに割り当てるために、"SHEET" という記号に対応します。
	 *     rowNo = -1 をセットします。
	 *
	 * @og.rev 6.0.3.0 (2014/11/13) 新規作成
	 * @og.rev 6.2.6.0 (2015/06/19) 行-列形式と、SHEET文字列判定を採用。
	 *
	 * @param	kigo	Excelの行列記号( A1 , B5 , AA23 など )
	 * @return	行と列の番号を持った配列([0]=行=ROW , [1]=列=COL)
	 * @og.rtnNotNull
	 */
	public static int[] kigo2rowCol( final String kigo ) {
		int rowNo = 0;
		int colNo = -1;										// +1 して、26 かける処理をしているので、辻褄合わせ

		// 6.2.6.0 (2015/06/19) 行-列形式と、SHEET文字列判定を採用。
		if( "SHEET".equalsIgnoreCase( kigo ) ) {
			rowNo = -1;
		}
		else {
			final int adrs = kigo.indexOf( '-' );
			if( adrs > 0 ) {
				rowNo = Integer.parseInt( kigo.substring( 0,adrs ) );
				colNo = Integer.parseInt( kigo.substring( adrs+1 ) );
			}
			else {
				for( int i=0; i<kigo.length(); i++ ) {
					final char ch = kigo.charAt(i);
					if( 'A' <= ch && ch <= 'Z' ) { colNo = (colNo+1)*26 + ch-'A'; }
					else {
						// アルファベットでなくなったら、残りは 行番号(ただし、-1する)
						rowNo = Integer.parseInt( kigo.substring( i ) ) -1;
						break;
					}
				}
			}
		}
		return new int[] { rowNo,colNo };
	}

	/**
	 * セルオブジェクト(Cell)から値を取り出します。
	 *
	 * セルオブジェクトが存在しない場合は、null を返します。
	 * それ以外で、うまく値を取得できなかった場合は、ゼロ文字列を返します。
	 *
	 * @og.rev 3.8.5.3 (2006/08/07) 取り出し方法を少し修正
	 * @og.rev 5.5.1.2 (2012/04/06) フォーマットセルを実行して、その結果を再帰的に処理する。
	 * @og.rev 6.0.3.0 (2014/11/13) セルフォーマットエラー時に、RuntimeException を throw しない。
	 * @og.rev 6.4.2.0 (2016/01/29) StringUtil#ogStackTrace(Throwable) を、ThrowUtil#ogStackTrace(String,Throwable) に置き換え。
	 *
	 * @param	oCell EXCELのセルオブジェクト
	 *
	 * @return	セルの値
	 */
	public static String getValue( final Cell oCell ) {
		if( oCell == null ) { return null; }
		String strText = "";
		final 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();
					final 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) フォーマットセルを実行して、その結果を再帰的に処理する。
					final Workbook wb = oCell.getSheet().getWorkbook();
					final CreationHelper crateHelper = wb.getCreationHelper();
					final FormulaEvaluator evaluator = crateHelper.createFormulaEvaluator();

					try {
						strText = getValue(evaluator.evaluateInCell(oCell));
					}
					catch ( Throwable th ) {
						final String errMsg = "セルフォーマットが解析できません。Formula=[" + oCell.getCellFormula() + "]"
									+ CR + getCellMsg( oCell );
	//					throw new OgRuntimeException( errMsg,th );
//						System.err.println( errMsg );								// 6.0.3.0 (2014/11/13)
//						System.err.println( StringUtil.ogStackTrace( th ) );
						System.err.println( ThrowUtil.ogStackTrace( errMsg,th ) );	// 6.4.2.0 (2016/01/29)
					}
					break;
			case Cell.CELL_TYPE_BOOLEAN:
					strText = String.valueOf(oCell.getBooleanCellValue());
					break;
			case Cell.CELL_TYPE_BLANK :
					break;
			case Cell.CELL_TYPE_ERROR:
					break;
			default :
				break;
		}
		return strText ;
	}

	/**
	 * セルオブジェクト(Cell)に、値をセットします。
	 *
	 * セルオブジェクトが存在しない場合は、何もしません。
	 * 引数は、文字列で渡しますが、セルの形式に合わせて、変換します。
	 * 変換がうまくいかなかった場合は、エラーになりますので、ご注意ください。
	 *
	 * @og.rev 6.3.9.0 (2015/11/06) 新規追加
	 *
	 * @param	oCell EXCELのセルオブジェクト
	 * @param	val   セットする値
	 */
	public static void setValue( final Cell oCell , final String val ) {
		if( oCell == null ) { return ; }
		if( val == null || val.isEmpty() ) { oCell. setCellType( Cell.CELL_TYPE_BLANK ); }

		switch( oCell.getCellType() ) {
			case Cell.CELL_TYPE_NUMERIC:
					oCell.setCellValue( Double.valueOf( val ) );
					break;
			case Cell.CELL_TYPE_BOOLEAN:
					oCell.setCellValue( "true".equalsIgnoreCase( val ) );
					break;
			default :
					oCell.setCellValue( val );
					break;
		}
	}

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

		final double dd = oCell.getNumericCellValue() ;
		if( DateUtil.isCellDateFormatted( oCell ) ) {
	//		strText = DateSet.getDate( DateUtil.getJavaDate( dd ).getTime() , "yyyyMMddHHmmss" );	// 5.5.7.2 (2012/10/09) HybsDateUtil を利用
			strText = ExcelStyleFormat.dateFormat( dd );
		}
		else {
	//		final NumberFormat numFormat = NumberFormat.getInstance();
	//		if( numFormat instanceof DecimalFormat ) {
	//			((DecimalFormat)numFormat).applyPattern( "#.####" );
	//		}
	//		strText = numFormat.format( dd );
			final String fmrs = oCell.getCellStyle().getDataFormatString();
			strText = ExcelStyleFormat.getNumberValue( fmrs,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 ) {
		final int shCnt = wkbook.getNumberOfSheets();

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

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

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

			// SheetUtil を使用して、計算範囲を指定します。
			if( stR < dataStRow ) { stR = dataStRow; }		// 計算範囲
			for( int colNo=stC; colNo<edC; colNo++ ) {
				final 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 の有無も判定基準に含めます。
	 * @og.rev 6.0.2.5 (2014/10/31) Cellの開始、終了番号が、マイナスのケースの対応
	 *
	 * @param	wkbook		処理対象のWorkbook
	 * @param	isCellDel	Cellの末尾削除を行うかどうか(true:行う/false:行わない)
	 */
	public static void activeWorkbook( final Workbook wkbook , final boolean isCellDel ) {
		final int shCnt = wkbook.getNumberOfSheets();
		for( int shNo=0; shNo<shCnt; shNo++ ) {
			final Sheet sht = wkbook.getSheetAt( shNo );

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

			boolean isRowDel = true;											// 行の削除は、Cellが見つかるまで。
			for( int rowNo=edR; rowNo>=stR && rowNo>=0; rowNo-- ) {				// 逆順に処理します。
				final Row rowObj = sht.getRow( rowNo );
				if( rowObj != null ) {
					final int stC = rowObj.getFirstCellNum();
					final int edC = rowObj.getLastCellNum();
					for( int colNo=edC; colNo>=stC && colNo>=0; colNo-- ) {		// 6.0.2.5 (2014/10/31) stC,edC が、マイナスのケースがある。
						final Cell colObj = rowObj.getCell( colNo );
						if( colObj != null ) {
							final String val = 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) 新規作成
	 * @og.rev 6.2.0.0 (2015/02/27) ファイル引数を、String → File に変更
	 *
	 * @param	file	入力ファイル
	 * @return	Workbookオブジェクト
	 * @og.rtnNotNull
	 */
	public static Workbook createWorkbook( final File file ) {
		InputStream fis = null;
		try {
			// File オブジェクトでcreate すると、ファイルがオープンされたままになってしまう。
			fis = new BufferedInputStream( new FileInputStream( file ) );
			return WorkbookFactory.create( fis );
		}
		catch( IOException ex ) {
			final String errMsg = "ファイル読込みエラー[" + file + "]" + CR + ex.getMessage() ;
			throw new OgRuntimeException( errMsg,ex );
		}
		catch( InvalidFormatException ex ) {
			final String errMsg = "ファイル形式エラー[" + file + "]" + CR + ex.getMessage() ;
			throw new OgRuntimeException( errMsg,ex );
		}
		finally {
			Closer.ioClose( fis );
		}
	}

	/**
	 * シート一覧を、Workbook から取得します。
	 *
	 * 取得元が、Workbook なので、xls , xlsx どちらの形式でも取り出せます。
	 *
	 * EXCEL上のシート名を、配列で返します。
	 *
	 * @og.rev 6.0.2.3 (2014/10/10) 新規作成
	 *
	 * @param	wkbook Workbookオブジェクト
	 * @return	シート名の配列
	 */
	public static String[] getSheetNames( final Workbook wkbook ) {
		final int shCnt = wkbook.getNumberOfSheets();

		String[] shtNms = new String[shCnt];

		for( int i=0; i<shCnt; i++ ) {
			final 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
	 *        For Each name In ActiveWorkbook.Names
	 *            If name.Visible = False Then
	 *                name.Visible = True
	 *            End If
	 *        Next
	 *        MsgBox "すべての名前の定義を表示しました。", vbOKOnly
	 *    End Sub
	 *
	 * ※ EXCEL2010 数式タブ→名前の管理 で、複数選択で、削除できます。
	 *    ただし、非表示の名前定義は、先に表示しないと、削除できません。
	 * ◆ 名前の一括削除 EXCEL VBA マクロ
	 * http://komitsudo.blog70.fc2.com/blog-entry-104.html
	 *    Sub DeleteNames()
	 *        Dim name
	 *        On Error Resume Next
	 *        For Each name In ActiveWorkbook.Names
	 *            If Not name.BuiltIn Then
	 *                name.Delete
	 *            End If
	 *        Next
	 *        MsgBox "すべての名前の定義を削除しました。", vbOKOnly
	 *    End Sub
	 *
	 * @og.rev 6.0.2.3 (2014/10/10) 新規作成
	 *
	 * @param	wkbook Workbookオブジェクト
	 * @return	名前定義(名前+TAB+Formula)の配列
	 * @og.rtnNotNull
	 */
	public static String[] getNames( final Workbook wkbook ) {
		final int cnt = wkbook.getNumberOfNames();

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

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

			final Name nm = wkbook.getNameAt(i);
			try {
				name = nm.getNameName();
				ref  = nm.getRefersToFormula();
			}
	//		catch( Exception ex ) {					// 6.1.0.0 (2014/12/26) refactoring
			catch( RuntimeException ex ) {
				final String errMsg = "POIUtil:RefersToFormula Error! name=[" + name + "]" + ex.getMessage() ;
				System.out.println( errMsg );
				// 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 にも名称を取得するメソッドがありません。)
	 *
	 * ※ EXCEL2010 ホームタブ→セルのスタイル は、一つづつしか削除できません。
	 *    マクロは、開発タブ→Visual Basic で、挿入→標準モジュール を開き
	 *    テキストを張り付けてください。
	 *    実行は、開発タブ→マクロ で、マクロ名を選択して、実行します。
	 *    最後は、削除してください。
	 *
	 * ◆ スタイルの一括削除 EXCEL VBA マクロ
	 * http://komitsudo.blog70.fc2.com/blog-entry-104.html
	 *    Sub DeleteStyle()
	 *        Dim styl
	 *        On Error Resume Next
	 *        For Each styl In ActiveWorkbook.Styles
	 *            If Not styl.BuiltIn Then
	 *                styl.Delete
	 *            End If
	 *        Next
	 *        MsgBox "すべての追加スタイルを削除しました。", vbOKOnly
	 *    End Sub
	 *
	 * ◆ 名前の表示、削除、スタイルの削除の一括実行 EXCEL VBA マクロ
	 *    Sub AllDelete()
	 *        Call VisibleNames
	 *        Call DeleteNames
	 *        Call DeleteStyle
	 *        MsgBox "すべての処理を完了しました。", vbOKOnly
	 *    End Sub
	 *
	 * @og.rev 6.0.2.3 (2014/10/10) 新規作成
	 *
	 * @param	wkbook Workbookオブジェクト
	 * @return	書式のスタイル一覧
	 * @og.rtnNotNull
	 */
	public static String[] getStyleNames( final Workbook wkbook ) {
		final int cnt = wkbook.getNumCellStyles();		// return 値は、short

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

		for( int s=0; s<cnt; s++ ) {
			final CellStyle cs = wkbook.getCellStyleAt( (short)s );
			if( cs instanceof HSSFCellStyle ) {
				final HSSFCellStyle hcs = (HSSFCellStyle)cs;
				final String name = hcs.getUserStyleName();
				// 6.4.1.1 (2016/01/16) PMD refactoring. Avoid if (x != y) ..; else ..;
				if( name == null ) {							// この処理は不要かも。
					final HSSFCellStyle pst = hcs.getParentStyle();
					if( pst != null ) {
						final String pname = pst.getUserStyleName();
						if( pname != null ) { nmSet.add( pname ); }
					}
				}
				else {
					nmSet.add( name );
				}
//				if( name != null ) { nmSet.add( name ); }
//				else {												// この処理は不要かも。
//					final HSSFCellStyle pst = hcs.getParentStyle();
//					if( pst != null ) {
//						final 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) 新規作成
	 * @og.rev 6.0.3.0 (2014/11/13) セル情報を作成する時に、値もセットします。
	 * @og.rev 6.2.2.0 (2015/03/27) celKigo を求めるロジック変更
	 * @og.rev 6.3.1.0 (2015/06/28) rowNo(行番号)も引数に取るようにします。
	 *
	 * @param	oCell EXCELのセルオブジェクト
	 * @return	セル情報の文字列
	 */
	public static String getCellMsg( final Cell oCell ) {
		String lastMsg = null;

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

			// 6.0.3.0 (2014/11/13) セル情報を作成する時に、値もセットします。
			lastMsg = " Sheet=" + shtNm + ", Row=" + rowNo + ", Cel=" + celNo
//						 + "(" + getCelKigo(celNo) + ") , Val=" + oCell.toString() ;
						 + "(" + getCelKigo(rowNo,celNo) + ") , Val=" + oCell.toString() ;
		}

		return lastMsg;
	}

	/**
	 * Excelの行番号,列番号より、セル記号を求めます。
	 *
	 * 行番号は、0から始まる数字ですが、記号化する場合は、１から始まります。
	 * Excelの列記号とは、A,B,C,…,Z,AA,AB,…,ZZ,AAA,AAB,… と続きます。
	 * つまり、アルファベットだけの、２６進数になります。(ゼロの扱いが少し特殊です)
	 * 列番号は、0から始まる数字で、0=A,1=B,2=C,…,25=Z,26=AA,27=AB,…,701=ZZ,702=AAA,703=AAB,…
	 * EXCELの行列記号にする場合は、この列記号に、行番号を、＋１して付ければよいだけです。
	 * (※ 列番号に＋１するのは、内部では０から始まる列番号ですが、表示上は１から始まります)
	 *
	 * @og.rev 6.2.2.0 (2015/03/27) celKigo を求めるロジック変更
	 * @og.rev 6.3.1.0 (2015/06/28) rowNo(行番号)も引数に取るようにします。
	 *
	 * @param	rowNo	行番号(0,1,2,…)
	 * @param	colNo	列番号(0,1,2,…)
	 * @return	Excelの列記号(A1,B2,C3,…)
	 */
//	public static String getCelKigo( final int colNo ) {
	public static String getCelKigo( final int rowNo,final int colNo ) {
		final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE );
		int cnt = colNo;
		while( cnt >= 26 ) {
			buf.append( (char)('A'+cnt%26) );
			cnt = cnt/26-1;
		}
		buf.append( (char)('A'+cnt%26) )
			.reverse()								// append で逆順に付けているので、反転して返します。
			.append( rowNo+1 );

//		return buf.reverse().toString();			// append で逆順に付けているので、反転して返します。
		return buf.toString();
	}

	/**
	 * アプリケーションのサンプルです。
	 *
	 * 入力ファイル名 は必須で、第一引数固定です。
	 * 第二引数は、処理方法で、-ALL か、-LINE を指定します。何も指定しなければ、-ALL です。
	 * 第三引数を指定した場合は、Encode を指定します。
	 *
	 * Usage: java org.opengion.fukurou.model.POIUtil 入力ファイル名 [処理方式] [エンコード]
	 *   -A(LL)        ･･･ ALL 一括処理(初期値)
	 *   -L(INE)       ･･･ LINE 行単位処理
	 *   -S(heet)      ･･･ Sheet名一覧
	 *   -N(AME)       ･･･ NAME:名前定義
	 *   -C(ellStyle)  ･･･ CellStyle:書式のスタイル
	 *
	 * @og.rev 6.0.2.0 (2014/09/19) 新規作成
	 * @og.rev 6.2.3.0 (2015/05/01) パラメータ変更、textReader → extractor に変更
	 * @og.rev 6.2.4.2 (2015/05/29) 引数判定の true/false の処理が逆でした。
	 * @og.rev 6.3.9.0 (2015/11/06) Java 8 ラムダ式に変更
	 *
	 * @param	args	コマンド引数配列
	 */
	public static void main( final String[] args ) {
		final String usageMsg = "Usage: java org.opengion.fukurou.model.POIUtil 入力ファイル名 [処理方式] [エンコード]" + "\n" +
								"\t -A(LL)        ･･･ ALL 一括処理(初期値)      \n" +
								"\t -L(INE)       ･･･ LINE 行単位処理           \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 ;
		}

		final File file = new File( args[0] );
		final char type     = args.length >= 2 ? args[1].charAt(1) : 'A' ;
		final String encode = args.length >= 3 ? args[2] : null ;				// 6.2.4.2 (2015/05/29) true/false の処理が逆でした。

		switch( type ) {
			case 'A' :  if( encode == null ) {
							System.out.println( POIUtil.extractor( file ) );
						}
						else {
							System.out.println( POIUtil.extractor( file,encode ) );
						}
						break;
//			case 'L' : final TableModelHelper helper =
//							new TableModelHelper() {
//								/**
//								 * 読み取り状態の時に、rowNo,colNo にあるセルの値を引数にイベントが発生します。
//								 *
//								 * @param   val     文字列値
//								 * @param   rowNo   行番号(0～)
//								 * @param   colNo   列番号(0～)
//								 * @return  読み取りするかどうか(true:読み取りする/false:読み取りしない)
//								 */
//								protected boolean value( final String val,final int rowNo,final int colNo ) {
//									System.out.println( "RC[" + rowNo + "," + colNo + "]=" + val );
//									return true;
//								}
//							} ;
			// 6.3.9.0 (2015/11/06) Java 8 ラムダ式に変更
			case 'L' : final TextConverter<String,String> conv =
								( val,cmnt ) -> {
									System.out.println( "val=" + val + " , cmnt=" + cmnt );
									return null;
								};

					//		new TextConverter<String,String>() {
					//			/** 
					//			 * 入力文字列を、変換します。
					//			 *
					//			 * @param	val  入力文字列
					//			 * @param	cmnt コメント
					//			 * @return	変換文字列(変換されない場合は、null)
					//			 */
					//			@Override
					//			public String change( final String val , final String cmnt ) {
					//				System.out.println( "val=" + val + " , cmnt=" + cmnt );
					//				return null;
					//			}
					//		};

						if( encode == null ) {
//							POIUtil.textReader( file,helper );
							POIUtil.textReader( file,conv );
						}
						else {
//							POIUtil.textReader( file,helper,encode );
							POIUtil.textReader( file,conv,encode );
						}
						break;
			case 'S' :  final String[] shts = POIUtil.getSheetNames( POIUtil.createWorkbook(file) );
						System.out.println( "No:\tSheetName" );
						for( int i=0; i<shts.length; i++ ) {
							System.out.println( i + "\t" + shts[i] );
						}
						break;
			case 'N' :  final String[] nms = POIUtil.getNames( POIUtil.createWorkbook(file) );
						System.out.println( "No:\tName\tFormula" );
						for( int i=0; i<nms.length; i++ ) {
							System.out.println( i + "\t" + nms[i] );
						}
						break;
			case 'C' :  final String[] sns = POIUtil.getStyleNames( POIUtil.createWorkbook(file) );
						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;
		}
	}
}
