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.util.NoSuchElementException; 019 020/** 021 * CSVTokenizer は、CSVファイルのデータを順次分割する StringTokenizer と非常に 022 * 良く似たクラスです。 023 * 024 * StringTokenizer では、デリミタが連続する場合も、1つのデリミタとするため、 025 * データが存在しない場合の表現が出来ませんでした。(例えば、AA,BB,,DD など) 026 * また、デリミタをデータ中に含む場合の処理が出来ません。( AA,BB,"cc,dd",EE など) 027 * この、CSVTokenizer クラスでは、データが存在しない場合もトークンとして返します。 028 * また、ダブルコーテーション("")で囲まれた範囲のデリミタは無視します。 029 * ただし、デリミタとしては、常に1種類の cher 文字 しか指定できません。 030 * 031 * @version 4.0 032 * @author Kazuhiko Hasegawa 033 * @since JDK5.0, 034 */ 035public class CSVTokenizer { 036 private int currentPosition; 037 private int maxPosition; 038 private String str; 039 private char delimChar ; 040 private boolean inQuoteFlag ; // "" クオート処理を行うかどうか 041 042 /** 043 * CSV 形式の 文字列を解析する CSVTokenizer のインスタンスを作成する。 044 * 045 * @param str CSV形式の文字列 改行コードを含まない。 046 * @param delim 区切り文字(1文字のみ指定可) 047 * @param inQuote クオート処理を行うかどうか [true:行う/false:行わない] 048 */ 049 public CSVTokenizer( final String str, final char delim, final boolean inQuote ) { 050 currentPosition = 0; 051 this.str = str; 052 maxPosition = str.length(); 053 delimChar = delim; 054 inQuoteFlag = inQuote; 055 } 056 057 /** 058 * CSV 形式の 文字列を解析する CSVTokenizer のインスタンスを作成する。 059 * 060 * @param str CSV形式の文字列 改行コードを含まない。 061 * @param delim 区切り文字(1文字のみ指定可) 062 */ 063 public CSVTokenizer( final String str, final char delim ) { 064 this(str, delim, true); 065 } 066 067 /** 068 * CSV 形式の 文字列を解析する CSVTokenizer のインスタンスを作成する。 069 * 070 * @param str CSV形式の文字列 改行コードを含まない。 071 */ 072 public CSVTokenizer( final String str ) { 073 this(str, ',', true); 074 } 075 076 /** 077 * 次のカンマがある位置を返す。 078 * カンマが残っていない場合は skipDelimiters() == maxPosition となる。 079 * また最後の項目が空の場合も skipDelimiters() == maxPosition となる。 080 * 081 * @param startPos 検索を開始する位置 082 * 083 * @return 次のカンマがある位置。カンマがない場合は、文字列の 084 * 長さの値となる。 085 */ 086 private int skipDelimiters( final int startPos ) { 087 boolean inquote = false; 088 int position = startPos; 089 while (position < maxPosition) { 090 char ch = str.charAt(position); 091 if(!inquote && ch == delimChar) { 092 break; 093 } else if('"' == ch && inQuoteFlag) { 094 inquote = !inquote; // "" クオート処理を行う 095 } 096 position ++; 097 } 098 return position; 099 } 100 101 /** 102 * トークナイザの文字列で利用できるトークンがまだあるかどうかを判定します。 103 * このメソッドが true を返す場合、それ以降の引数のない nextToken への 104 * 呼び出しは適切にトークンを返します。 105 * 106 * @return 文字列内の現在の位置の後ろに 1 つ以上の 107 * トークンがある場合だけ true、そうでない場合は false 108 */ 109 public boolean hasMoreTokens() { 110 int newPosition = skipDelimiters(currentPosition); 111 return ( newPosition <= maxPosition ); // "<" だと末尾の項目を正しく処理できない 112 } 113 114 /** 115 * 文字列トークナイザから次のトークンを返します。 116 * 117 * @og.rev 5.2.0.0 (2010/09/01) トークンの前後が '"'である場合、"で囲われた文字列中の""は"に変換します。 118 * 119 * @return 文字列トークナイザからの次のトークン 120 * @throws NoSuchElementException トークナイザの文字列に 121 * トークンが残っていない場合 122 */ 123 public String nextToken() { 124 // ">=" では末尾の項目を正しく処理できない。 125 // 末尾の項目が空(カンマで1行が終わる)場合、例外が発生して 126 // しまうので。 127 if(currentPosition > maxPosition) { 128 throw new NoSuchElementException(toString()+"#nextToken"); 129 } 130 131 // 3.5.4.7 (2004/02/06) 132 int from = currentPosition; 133 int to = skipDelimiters(currentPosition); 134 currentPosition = to + 1; 135 // 5.2.0.0 (2010/09/01) トークンの前後が '"'である場合、"で囲われた文字列中の""は"に変換します。 136 String rtn = null; 137 // 3.5.5.8 (2004/05/20) トークンの前後が '"' なら、削除します。 138 if( inQuoteFlag && from < maxPosition && from < to && 139 str.charAt(from) == '"' && str.charAt(to-1) == '"' ) { 140 from++; 141 to--; 142 rtn = str.substring( from,to ).replace( "\"\"", "\"" ); 143 } 144 else { 145 rtn = str.substring( from,to ); 146 } 147// return str.substring( from,to ); 148 return rtn; 149 } 150 151 /** 152 * 例外を生成せずにトークナイザの <code>nextToken</code> メソッドを呼び出せる 153 * 回数を計算します。現在の位置は進みません。 154 * 155 * @return 現在の区切り文字を適用したときに文字列に残っているトークンの数 156 * @see CSVTokenizer#nextToken() 157 */ 158 public int countTokens() { 159 int count = 1; 160 int currpos = 0; 161 while ((currpos = skipDelimiters(currpos)) < maxPosition) { 162 currpos++; 163 count++; 164 } 165 return count; 166 } 167 168 /** 169 * インスタンスの文字列表現を返す。 170 * 171 * @return インスタンスの文字列表現。 172 */ 173 @Override 174 public String toString() { 175 return "CSVTokenizer(" + str + ")"; 176 } 177}