CSVファイル関連の拡張
脳内コンパイル。すなわち、未コンパイルのため、コンパイルエラーが出るかも。
抽象クラスでの実装を試みた。
処理概要としては、以下のことを行う。
指定された型のArrayListでCSVファイルを読み込みながら作成。
指定された型のArrayListでCSVファイルを書き込み。
もう少し拡張を行うとしたら、ヘッダー行の対応。(以下のソースは未対応)
エラーがあったので修正しておきます。
参考にしたのは以下のページ。すばらしい。
DataTableや配列等をCSV形式のファイルとして保存する - .NET Tips (VB.NET,C#...)
CSV形式のファイルをDataTableや配列等として取得する - .NET Tips (VB.NET,C#...)
using System.Text.RegularExpressions; using System.Collections; using System.IO; using System.Text; class CSVFile<T> { private string _csvFile; private bool _isTrimEachRecord = false; private string _writeEncoding = "Shift_JIS"; /// <summary> /// 読み込み時クォーテーション除去後のトリミングを行うかどうか /// </summary> protected bool IsTrimingEachRecord { get { return _isTrimEachRecord; } set { _isTrimEachRecord = value; } } /// <summary> /// 書き込み時のエンコード種別 /// </summary> protected string WriteEncoding { get { return _writeEncoding; } set { _writeEncoding = value; } } /// <summary> /// 指定された型からArrayList型へ変換。 /// 継承先(具体クラス)でArrayList型の領域は確保してください。 /// </summary> /// <param name="item">指定された型</param> /// <return>ArrayList型(string)</return> virtual protected ArrayList ConvertStructure(T item) { return null; } /// <summary> /// ArrayList型から指定された型へ変換。 /// 継承先(具体クラス)で指定された型の領域は確保してください。 /// </summary> /// <param name="item">ArrayList型(string)</param> /// <return>指定された型</return> virtual protected T ConvertStructure(ArrayList item) { return default(T); } /// <summary> /// CSVファイルパスを保持 /// </summary> /// <param name="csvFile">CSVファイルパス</param> virtual public CSVFile(string csvFile) { _csvFile = csvFile; } /// <summary> /// CSVをArrayListに変換 /// </summary> /// <returns>変換結果のArrayList</returns> virtual public ArrayList ReadCSV() { char[] TrimChars = { '\r', '\n' }; ArrayList csvRecords = new ArrayList(); // 読み込みストリームを開く StreamReader _sr = new StreamReader(_csvFile,Encoding.GetEncoding(WriteEncoding),true); //1行のCSVから各フィールドを取得するための正規表現 Regex regCsv = new Regex("\\s*(\"(?:[^\"]|\"\")*\"|[^,]*)\\s*,",RegexOptions.None); ArrayList csvFields = new ArrayList(); string line; //一行ずつ取り出す while ((line = _sr.ReadLine()) != null) { // ArrayListを初期化 csvFields.Clear(); //改行記号が"で囲まれているか調べる while ((CountString(ref line, "\"") % 2) == 1) { string nextLine = _sr.ReadLine(); // 読み込みすぎた場合は例外を発生させる if (nextLine == null) { throw new System.IO.EndOfStreamException(); } line += nextLine; } //行の最後の改行記号を削除し「,」をつける line = line.TrimEnd(TrimChars) + ","; //1つの行からフィールドを取り出す Match m = regCsv.Match(line); while (m.Success) { //前後の空白を削除 string field = m.Groups[1].Value.Trim(); //"で囲まれている時 if (field.StartsWith("\"") && field.EndsWith("\"")) { //前後の"を取る field = field.Substring(1, field.Length - 2); //「""」を「"」にする field = field.Replace("\"\"", "\""); } //Trimを行う設定かどうかを判定する if (IsTrimingEachRecord) { field = field.Trim(); } csvFields.Add(field); m = m.NextMatch(); } // 指定された型へ変換する T _item = ConvertStructure(csvFields); csvRecords.Add(_item); } _sr.Close(); csvRecords.TrimToSize(); return csvRecords; } virtual public void WriteCSV(ArrayList csvFields) { char[] maches = { '"' , ',' , '\r' , '\n' }; //開く StreamWriter _sw = new StreamWriter(_csvPath, false,Encoding.GetEncoding(WriteEncoding)); //レコードを書き込む foreach (T _item in csvFields) { bool isFirstLoop = true; //フィールドの取得 foreach(string _field in ConvertStructure(_item)) { //カンマを書き込む if (isFirstLoop) { isFirstLoop = false; } else { _sw.Write(','); } //"で囲む必要があるか調べる if ( (_field.IndexOfAny(maches) > -1) || _field.StartsWith(" ") || _field.StartsWith("\t") || _field.EndsWith(" ") || _field.EndsWith("\t") ) { _sw.Write("\""); if (_field.IndexOf('"') > -1) { //"を""とする _sw.Write(_field.Replace("\"", "\"\"")); } else { //フィールドを書き込む _sw.Write(_field); } _sw.Write("\""); } else { //フィールドを書き込む _sw.Write(_field); } } //改行する _sw.Write("\r\n"); } //閉じる _sw.Close(); } /// <summary> /// 指定された文字列内にある文字列が幾つあるか数える /// </summary> /// <param name="strInput">strFindが幾つあるか数える文字列の参照</param> /// <param name="strFind">数える文字列</param> /// <returns>strInput内にstrFindが幾つあったか</returns> private int CountString(ref string strInput, string strFind) { int foundCount = 0; int sPos = strInput.IndexOf(strFind); while (sPos > -1) { foundCount++; sPos = strInput.IndexOf(strFind, sPos + 1); } return foundCount; } }
具体化して使う場合は、以下のように実装。
class TestStructure { public int Id; public string Name; } class TestStructureCSV : CSVFile<TestStructure> { public TestStructureCSV(string csvfile) :base(csvfile) { } /// <summary> /// 指定された型からArrayList型へ変換。 /// </summary> /// <param name="item">指定された型</param> /// <return>ArrayList型(string)</return> override protected ArrayList ConvertStructure(TestStructure item) { ArrayList _al = new ArrayList(); _al.Add(item.Id.ToString()); _al.Add(item.Name); return _al; } /// <summary> /// ArrayList型から指定された型へ変換。 /// </summary> /// <param name="item">ArrayList型(string)</param> /// <return>指定された型</return> override protected TestStructure ConvertStructure(ArrayList item) { TestStructure _al = new TestStructure(); _al.Id = int.Parse(item[0]); _al.Name = item[1]; return _al; } }
具体化したクラスを使う場合は、以下のようににする。(読み込みの場合のみ...)
int _id = 1; TestStructureCSV _tscsv = new TestStructureCSV("c:\\test.csv"); foreach (TestStructure _item in _tscsv.ReadCSV()) { if(_item.Id == _id) { Text1.Text = _item.Name; break; } }