CSV処理
まあ特別なことはこれといってしていない。
ヘッダーの有無とか読み飛ばしとか必要かな・・・?
/** * CSVの解析ロジック */ public interface Tokenizer { /** * 1つの文字列を複数の文字列に分割する * @param text * @return */ String[] toToken(String text); /** * 複数の文字列を1つの文字列に結合する * @param tokens * @return */ String toLine(String[] tokens); /** * 分割する際に次の行と合わせて1つの文字列とする必要があるか否かを返す * @param text * @return */ boolean hasNextLine(String text); }
/** * 単純に1つの文字を区切り文字として結合・分割する解析ロジック */ public class SimpleTokenizer implements Tokenizer { // 区切り文字 // デフォルトは, private char delimiter = ','; /** * 区切り文字を,として解析を行う */ public SimpleTokenizer() {} /** * 指定した区切り文字で解析を行う * @param delimiter */ public SimpleTokenizer(char delimiter) { this.delimiter = delimiter; } /** * 配列内の文字列を区切り文字で結合して1つの文字列とする */ @Override public String toLine(String[] tokens) { return StringUtils.join(tokens, delimiter); } /** * 区切り文字を引数として{@link String#split(String)}を呼び出す */ @Override public String[] toToken(String text) { return text.split("\\" + String.valueOf(delimiter)); } /** * 常にfalseを返す */ @Override public boolean hasNextLine(String text) { return false; } }
import java.util.ArrayList; import java.util.Arrays; /** * CSVの行を扱うクラス */ public class CsvLine extends ArrayList<String> { // CSVの解析ロジック private Tokenizer tokenizer; /** * デフォルトの解析ロジックを使用する。 */ public CsvLine() { this(new SimpleTokenizer()); } /** * デフォルトの解析ロジックを使用してtextを配列に分割して保持する。 * @param text */ public CsvLine(String text) { this(text, new SimpleTokenizer()); } /** * 指定した解析ロジックを使用する * @param tokenizer */ public CsvLine(Tokenizer tokenizer) { if (tokenizer == null) { throw new IllegalArgumentException("tokenizer must not null"); } this.tokenizer = tokenizer; } /** * 指定した解析ロジックを使用してtextを配列に分割して保持する。 * @param text * @param tokenizer */ public CsvLine(String text, Tokenizer tokenizer) { if (text == null) { throw new IllegalArgumentException("text must not null"); } if (tokenizer == null) { throw new IllegalArgumentException("tokenizer must not null"); } this.tokenizer = tokenizer; if (text.length() == 0) { return; } addAll(Arrays.asList(tokenizer.toToken(text))); } /** * デフォルトの解析ロジックを使用して配列を格納する。 * @param array */ public CsvLine(String[] array) { this(array, new SimpleTokenizer()); } /** * 指定した解析ロジックを使用して配列を格納する。 * @param array * @param tokenizer */ public CsvLine(String[] array, Tokenizer tokenizer) { if (tokenizer == null) { throw new IllegalArgumentException("tokenizer must not null"); } addAll(Arrays.asList(array)); this.tokenizer = tokenizer; } public void SetTokenizer(Tokenizer tokenizer) { if (tokenizer == null) { throw new NullPointerException(); } this.tokenizer = tokenizer; } public Tokenizer getTokenizer() { return tokenizer; } @Override public String toString() { return tokenizer.toLine(toArray(new String[size()])); } }
import java.io.BufferedReader; import java.io.IOException; import java.io.Reader; import java.util.NoSuchElementException; /** * CSVを1行ずつ読むクラス */ public class CsvReader { public static final String LINE_SEPARATOR = System.getProperty("line.separator"); // ベースとなるReader private BufferedReader reader; // 使用する解析ロジック private Tokenizer tokenizer; // 現在読込み中の行 private String currentLine; // hasNext()を複数回続けて呼ばれるとデータを読み飛ばしてしまうので、2回目以降は前回の結果を返すだけにする private boolean hasNextCalled; private boolean hasNextResult; /** * デフォルトの解析ロジックを使用してCSVを読み込む * @param reader */ public CsvReader(Reader reader) { this(reader, new SimpleTokenizer()); } /** * 指定した解析ロジックを使用してCSVを読み込む * @param reader * @param tokenizer */ public CsvReader(Reader reader, Tokenizer tokenizer) { if (reader == null) { throw new IllegalArgumentException("reader must not null"); } if (tokenizer == null) { throw new IllegalArgumentException("tokenizer must not null"); } this.reader = new BufferedReader(reader); this.tokenizer = tokenizer; } /** * 次の行が存在するか否かを返す * @return * @throws IOException */ public boolean hasNext() throws IOException { if (hasNextCalled) { return hasNextResult; } hasNextResult = false; hasNextCalled = false; currentLine = null; String text = ""; // Readerから1行読み込む while ((text = reader.readLine()) != null) { if (currentLine == null) { currentLine = ""; } if (currentLine.length() > 0) { currentLine += LINE_SEPARATOR; } currentLine += text; // 解析ロジックが次の行に続くと判断すれば、次の行もつなげて1つの行とする if (!tokenizer.hasNextLine(currentLine)) { hasNextResult = true; break; } } hasNextCalled = true; return hasNextResult; } /** * 次の行を返す。 * @return */ public CsvLine next() { if (currentLine == null) { throw new NoSuchElementException(); } hasNextCalled = false; return new CsvLine(currentLine, tokenizer); } }
import java.io.IOException; import java.io.StringWriter; import java.io.Writer; /** * CSVを書き込むクラス */ public class CsvWriter { // 書き込み先のWriter private Writer writer; // 使用する解析ロジック private Tokenizer tokenizer; /** * デフォルトの解析ロジックを使用してメモリ内のWriterに書き込む。<br/> * 結果は{@link #toString()}で取得する。 */ public CsvWriter() { this(new StringWriter()); } /** * デフォルトの解析ロジックを使用して指定したWriterに書き込む。 * @param writer */ public CsvWriter(Writer writer) { this(writer, new SimpleTokenizer()); } /** * 指定した解析ロジックを使用して指定したWriterに書き込む * @param writer * @param tokenizer */ public CsvWriter(Writer writer, Tokenizer tokenizer) { if (writer == null) { throw new NullPointerException("writer must not null"); } if (tokenizer == null) { throw new NullPointerException("tokenizer must not null"); } this.writer = writer; this.tokenizer = tokenizer; } /** * CSVを1行書き込む。<br/> * 解析ロジックはlineが保持しているものを使用する。 * @param line * @throws IOException */ public void write(CsvLine line) throws IOException { writer.write(line.toString()); writer.write(CsvReader.LINE_SEPARATOR); } /** * CSVを1行書き込む * @param array * @throws IOException */ public void write(String[] array) throws IOException { writer.write(tokenizer.toLine(array)); writer.write(CsvReader.LINE_SEPARATOR); } public String toString() { return writer.toString(); } }
import java.io.IOException; import java.io.Reader; import java.io.StringWriter; import java.io.Writer; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; /** * CSVファイルを読み書きするクラス */ public class CsvFile extends ArrayList<CsvLine> { // CSVの解析ロジック private Tokenizer tokenizer; // adjustの有効/無効フラグ private boolean isAdjust = true; /** * デフォルトの解析ロジックを使用する */ public CsvFile() { this(new SimpleTokenizer()); } /** * 指定した解析ロジックを使用する * @param tokenizer */ public CsvFile(Tokenizer tokenizer) { if (tokenizer == null) { throw new IllegalArgumentException("tokenizer must not null"); } this.tokenizer = tokenizer; } /** * デフォルトの解析ロジックを使用してCSVデータを読み込む * @param source * @throws IOException */ public CsvFile(Reader source) throws IOException { this(source, new SimpleTokenizer()); } /** * 指定した解析ロジックを使用してCSVデータを読み込む * @param source * @param tokenizer * @throws IOException */ public CsvFile(Reader source, Tokenizer tokenizer) throws IOException { if (tokenizer == null) { throw new IllegalArgumentException("tokenizer must not null"); } this.tokenizer = tokenizer; for (CsvReader r = new CsvReader(source, tokenizer); r.hasNext();) { add(r.next()); } } /** * CSVの内容をライタに書き込む。 * 解析ロジックは自分のメンバを使用する。 * @param writer * @throws IOException */ public void write(Writer writer) throws IOException { // 出力前に項目数を合わせる adjustTokens(); for (CsvLine line : this) { writer.write(tokenizer.toLine(line.toArray(new String[line.size()]))); writer.write(CsvReader.LINE_SEPARATOR); } } /** * 項目数が異なるアイテムがあった場合に、最大項目数に合わせて空白を追加する */ private void adjustTokens() { // 複数の項目を追加する場合など、フラグの値に応じて処理を行ったり行わなかったり。 if (!isAdjust) { return; } int maxTokens = 0; for (CsvLine line : this) { maxTokens = Math.max(maxTokens, line.size()); } for (CsvLine line : this) { while (line.size() < maxTokens) { line.add(""); } } } @Override public boolean add(CsvLine e) { boolean ret = super.add(e); adjustTokens(); return ret; } public boolean add(String e) { return add(new CsvLine(e, tokenizer)); } @Override public void add(int index, CsvLine element) { super.add(index, element); adjustTokens(); } public void add(int index, String element) { add(index, new CsvLine(element, tokenizer)); } @Override public boolean addAll(Collection<? extends CsvLine> c) { boolean ret = false; isAdjust = false; for (CsvLine line : c) { if (add(line)) { ret = true; } } isAdjust = true; adjustTokens(); return ret; } @Override public boolean addAll(int index, Collection<? extends CsvLine> c) { isAdjust = false; for (CsvLine line : c) { add(index++, line); } isAdjust = true; adjustTokens(); return true; } @Override public CsvLine set(int index, CsvLine element) { CsvLine ret = super.set(index, element); adjustTokens(); return ret; } public CsvLine set(int index, String element) { return set(index, new CsvLine(element, tokenizer)); }; @Override public String toString() { StringWriter writer = new StringWriter(); try { write(writer); } catch (IOException e) { } return writer.toString(); } public void sort() { sort(new ArrayComparator<String>(new NumberStringComparator())); } public void sort(final ArrayComparator<String> comparator) { Collections.sort(this, new Comparator<CsvLine>() { @Override public int compare(CsvLine o1, CsvLine o2) { return comparator.compare(o1.toArray(new String[o1.size()]), o2.toArray(new String[o2.size()])); } }); } }
import java.util.ArrayList; import java.util.List; /** * 引用符、および引用符・区切り文字を含む文字列の解析に対応したロジック */ public class QuotableTokenizer implements Tokenizer { // 区切り文字 // デフォルトは, private char delimiter = ','; // 引用符 // デフォルトは" private char quote = '"'; // 結合の際、常に引用符をつけるか否かのフラグ private boolean allQuote = true; /** * {@link #QuotableTokenizer(char, char, boolean) QuotableTokenizer(',', '"', true)}と同じ */ public QuotableTokenizer() {} /** * 区切り文字、引用符などを指定する * @param delimiter 区切り文字として使用する文字 * @param quote 引用符として使用する文字 * @param allQuote {@link #toLine(String[])}の際、常に引用符をつけるか否かのフラグ。falseにすると文字列の中に引用符・区切り文字・改行を含む場合のみ引用符がつく */ public QuotableTokenizer(char delimiter, char quote, boolean allQuote) { this.delimiter = delimiter; this.quote = quote; this.allQuote = allQuote; } @Override public String toLine(String[] tokens) { if (tokens == null) { return ""; } StringBuffer ret = new StringBuffer(); for (int i = 0; i < tokens.length; i++) { if (i > 0) { ret.append(delimiter); } ret.append(appendQuote(tokens[i], delimiter, quote, allQuote)); } return ret.toString(); } @Override public String[] toToken(String text) { return split(text, delimiter, quote); } @Override public boolean hasNextLine(String text) { return (StringUtils.countChar(text, quote) % 2) != 0; } public static String appendQuote(String text, char delimiter, char quote, boolean all) { if (all || StringUtils.indexAny(text, new char[] {delimiter, quote, '\r', '\n'}) >= 0) { text = text.replaceAll("\\" + quote, "$0$0"); text = quote + text + quote; } return text; } public static String[] split(String text, char delimiter, char quote) { List<String> ret = new ArrayList<String>(); String item = ""; int quoteCount = 0; for (int i = 0; i < text.length(); i++) { char c = text.charAt(i); if (c == quote) { quoteCount++; item += c; } else if (c == delimiter) { if ((quoteCount % 2) == 0) { ret.add(trimQuote(item, quote)); item = ""; } else { item += c; } } else { item += c; } } if (item.length() > 0) { ret.add(trimQuote(item, quote)); } return ret.toArray(new String[0]); } public static String trimQuote(String text, char quote) { if (text == null) { return null; } if (text.length() >= 2 && text.startsWith(String.valueOf(quote)) && text.endsWith(String.valueOf(quote))) { text = text.substring(1, text.length() - 1); text = text.replaceAll("\\" + quote + "\\" + quote, "\\" + quote); } return text; } }