日記帳だ! with Tux on Libserver

二度目の大改造!! 日記帳…か?を継承し、より柔軟でパワフルなBlogに変身しました。

RSSに対応しています。リンク・コメント・トラックバックは自由にしていただいてほぼ問題ありません。
RSS購読方法、僕のリンク・コメント・トラックバックについての考えを読むことをおすすめします。

間違いに恐れながらCSVを解釈する

TextFieldParserがバグったので、CSV解析部を自作することに…
すごい長い行の端が欠けるという意味不明なことが起きたので…
なんというか間抜けな話だが、CSVの仕様をよく勉強してきたので負ける気がしない。
非常にベタな書き方ですが、まぁ性能はかなりいいですね。
さて、どんな具合かというとこんな感じ。
public static string[][] csvParser(string csvdata) {
System.Collections.Generic.List<string> record=new List<string>();
System.Collections.Generic.List<string[]> data=new List<string[]>();
char[] csvdataarr=csvdata.ToCharArray();
StringBuilder field=new StringBuilder();
bool qqbegin=false, qqend=false;
foreach (char cur in csvdataarr) {
if (cur=='"') {
if (qqbegin) {
if (qqend) { field.Append(cur); qqend=false; }
else { qqend=true; }
} else {
qqbegin=true; qqend=false;
}
} else if (cur==',') {
if (qqbegin&&!qqend) { field.Append(cur); }
else { record.Add(field.ToString()); field.Length=0; qqend=false; qqbegin=false; }
} else if (cur=='\r') {
if (qqbegin&&!qqend) { field.Append(cur); }
else { /*フィールド末のCRは無視*/ }
} else if (cur=='\n') {
if (qqbegin&&!qqend) { field.Append(cur); }
else { record.Add(field.ToString()); field.Length=0; qqend=false; qqbegin=false;
data.Add(record.ToArray()); record.Clear(); }
} else {
field.Append(cur);
}
}
if (field.Length>0||record.Count>0) record.Add(field.ToString());
if (record.Count>0) data.Add(record.ToArray());
return data.ToArray();
}
静的メソッドですね。csvParserクラスを作るかと思ったが、これで十分扱いやすいので静的メソッド。
大きさのわからない配列はSystem.Collections.Generic.Listクラスを使うのが便利ですね。
これで要素を集めて、最後にToArrayメソッドを発動させればいいと。
System.StringBuilderクラスはこれのchar版だと思ってくれればいい。いや、正しくはないけど。
実はstringクラスはインスタンスで作成されてから実体が変化することはないと。
str+="a";
str+=b.ToString();
str+=c;
これはどういう動作かというと、
"a"をまず作成する、そしてstrと足し算する。しかしその結果は新しく作られた実体に入る。そしてstrに代入。
元のstrの実体は捨てられます。まぁそんなことを3回も繰り返すとえらいことです。
その解決のためにStringBuilderクラスがあるんです。
インスタンスを作って、Appendメソッド、AppendLineメソッドでどんどん投げ入れて行きます。
文字列を投げ入れていくのだから、stringの足し算とは違い実体が何度も捨てられたりはしません。
最後にToStringメソッドでstringに変換して代入。これが賢いやり方です。
作成時に長さがわからなくてもいい、内容を変更できる文字列のクラスだと思えばOKです。
非常に気持ち悪いインデントですが、まぁ省スペースのためです。
この処理は正しいCSVファイルしか処理できません。まぁ正しくないファイルなんかあっても困るけどね。
"が発見されたら、まずqqbeginをtrueにする。qqbeginがtrueのうちはカンマやCR(\r)やLF(\n)であってもfieldにAppendする。
qqbeginがtrueのときに"が発見されたらqqendをtrueにする。この次に"が発見されたらqqendをfalseに戻し"をAppendする。
qqbeginがtrueでもqqendがtrueのときはqqbeginがfalseのときと同様に,やLFのときそれなりの処置をする。
さて、カンマがやってきたらフィールド終わりです。fieldをstringにして、recordにAddする。そしてfieldの長さを0に、すなわち空にする。
そしてLFがやってきたら、recordをstring[]にして、dataにAddする。そしてrecordを空にする。
CRはLFの前にあるだろうということで無視。CR+LFでもLFだけでもOKです。
まぁこんな風にフィールドの中身を読んでるときはfieldに集めて、読み終わったら集めたのをrecordに持って行って、
レコードの最後に来たら、fieldが集められたrecordをdataに持って行くと。
まぁそんな感じになっています。まぁPerlで正規表現でやってたのもこれと似た方法ですけどね。
最後の処理ですが、最終行が空行ならばレコードは増やさないけど、それ以外はきちんと処理するということです。
最終行がaだけあれば、カンマもLFも来ないとaがfieldの中に残ったままになるから、レコードに追加する。
最終行がa,となっていたら、カンマの次は何もないけど、これは長さ0のフィールドがあると解釈できる。
最後のLFでfield、recordが空にされてから追加されていない、これは最終行は空行であるということ。
こんなときはその最終行はただの空行だからdataに追加しない。
とまぁそういう処理もできています。
なんで.NET FrameworkはCSV解釈機能を持たないのだろうか。
そういう疑問を持つ人は多いようです。
Microsoft.VisualBasic.FileIO.TextFieldParserクラスがあるけど、まぁいちいちめんどくさい。
というか、長い行の解釈に失敗してるし。これはかなりまずいと思う。
こういうものの自作というのは、あんまり好ましいことではないですから、できるならばライブラリを使った方がいい。
けどそういうものがない。とすれば自作するしかない。とまぁ、なかなか困ったことです。
今回のcsvParserメソッドは正しいCSVは正しく解釈できるけど、間違ったCSVはインチキな解釈をする、というのがあります。
それでいいとも思うのだけどね。
何が正しくないのかといわれるとそれはなかなか難しいですからね。
まぁこのメソッドは十分実用的であるということにしておきましょうか。
Author : Hidemaro
Date : 2008/09/04(Thu) 22:09
コンピュータ・インターネット | Comment | trackback (0)

Tools