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     */
016    package org.opengion.fukurou.util;
017    
018    import java.util.NoSuchElementException;
019    
020    /**
021     * CSVTokenizer は、CSVファイルの??タを?次?する StringTokenizer と非常に
022     * 良く似たクラスです?
023     *
024     * StringTokenizer では、デリミタが?続する?合も?つの?ミタとするため?
025     * ??タが存在しな??合?表現が?来ませんでした?例えば、AA,BB,,DD など)
026     * また??ミタをデータ中に含??合?処?出来ません? AA,BB,"cc,dd",EE など)
027     * こ?、CSVTokenizer クラスでは、データが存在しな??合もト?クンとして返します?
028     * また??ルコー??ション("")で囲まれた?の?ミタは無視します?
029     * ただし??ミタとしては、常に?種類? cher ??しか?できません?
030     *
031     * @version  4.0
032     * @author   Kazuhiko Hasegawa
033     * @since    JDK5.0,
034     */
035    public 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  区????文字?み?可)
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  区????文字?み?可)
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    }