読者です 読者をやめる 読者になる 読者になる

★ The Tsuchinoko News 2 (つちのこ通信2) ★

重要な話から、どうでもいいことまで。ほとんど役に立たないことを書き連ねています。

【PC】C# CSVお便利クラス

IT雑感

筆者は、かれこれ20年以上「IT屋」である(笑)
この業界、どんどん進化するような宣伝過多な部分もあるが基本的には何も変わっていないというのが筆者の所感。ただ、コンピューターが好事家か大学にしかなかったものが、一般家庭にも普及し、十分にビジネスとなることでインターネットが普及し、という進化はあるが技術的にどうなのかというと、さほど変わっていない。
こと業務用システムの世界では、XMLJSONがポピュラーになったような気がする現在において、「固定長」「CSV」しかも Shift-JIS が、今もごくあたりまえに使われている。
たしかに XMLJSONのような高等な表現は不要で、単にずらずら~っと横並びのシリアルデータの羅列で、昔の汎用機の紙テープの読み取り・書き取りの世界観で十分といえば十分だ。単純な構造なので取扱も容易。
とはいえ、これを実際にプログラムで書いてみると、けっこう様々な事情があって、面倒くさい(笑)
とくに似たような処理がわんさか登場するので、都度都度コーディングしていると、ものすごく長い長いプログラムになり、読みにくいことこの上ない。(カーニハン&リッチー風に言えば、エレガントでない、というのかも知れない)
そこで標題のCSVお便利クラスを作ってみた。
ひとことで CSV といっても、方言というか、なんというか、いろいろあるので、必ずしも全てを処理できるわけではないが、筆者の20年以上の実績で使う前提だと、これで全てを処理可能だ。
とりあえずC#のソース

    public class Csv {

        //
        // CSVファイル関連:便利クラス
        //

        public String Siegae { get; set; }
        public String Delimitter { get; set; }
        public String CrLf { get; set; }
        public Encoding Encoding { get; set; }
        public Boolean OverWrite { get; set; }
        public SUFFIX Suffix { get; set; }
        public MODE mode { get; set; }
        public String Path { get; set; }

        private List<String> SaveData = null;
        public List<List<String>> LoadData = null;

        public enum MODE {
            STANDARD = 0,
            EXCELCSV = 1,
        }

        public enum SUFFIX {
            STANDARD = 0,
            DOUBLE_DIGITS = 1,
            TRIPLE_DIGITS = 2,
            DOUBLE_DIGITS_WITH_PARENTHESES = 3,
            TRIPLE_DIGITS_WITH_PARENTHESES = 4,
        }

        //
        // Constructor
        //
        public Csv()
            : this( MODE.EXCELCSV ) {
        }

        public Csv( MODE _mode ) {

            this.Siegae = "\"";
            this.Delimitter = ",";
            this.CrLf = "\r\n";
            this.Encoding = Encoding.UTF8;
            this.mode = _mode;
            this.OverWrite = false;
            this.Suffix = SUFFIX.STANDARD;
            Clear();

        }

        public void Clear() {

            this.SaveData = new List<String>();
            this.LoadData = new List<List<String>>();
        }

        public void Add( params Object[] value ) {

            String ret = "";

            String delimitter = "";
            String eq = "";
            foreach( Object obj in value ) {

                if( obj != null ) {

                    if( obj.GetType() == typeof( Boolean ) ) {

                        if( this.mode == MODE.EXCELCSV ) {

                            eq = "";
                            Boolean eqbool = (Boolean)obj;
                            if( eqbool )
                                eq = "=";

                        }

                    } else {

                        String adstr = "";
                        if( obj.GetType() == typeof( String ) ) {
                            adstr = (String)obj;
                        } else {
                            adstr = obj.ToString();
                        }
                        ret += delimitter + eq + this.Siegae + adstr + this.Siegae;
                        delimitter = this.Delimitter;
                        eq = "";

                    }

                }

            }

            ret += CrLf;
            this.SaveData.Add( ret );
        }

        public String GetSaveData() {

            String ret = "";
            foreach( String d in this.SaveData ) {
                ret += d;

            }
            return ret;

        }

        public Boolean Save( String path ) {
            return Save( path, false, this.OverWrite, this.Encoding );
        }
        public Boolean Save( String path, Encoding enc ) {
            return Save( path, false, this.OverWrite, enc );
        }
        public Boolean Save( String path, Boolean append ) {
            return Save( path, append, this.OverWrite, this.Encoding );
        }
        public Boolean Save( String path, Boolean append, Boolean overwrite, Encoding enc ) {
            Boolean ret = false;

            String extension = System.IO.Path.GetExtension( path );
            String filename = System.IO.Path.GetFileNameWithoutExtension( path );
            String folder = System.IO.Path.GetDirectoryName( path );

            if( !overwrite ) {

                if( File.Exists( path ) ) {
                    int n = 1;
                    path = folder + "\\" + filename + suffixstr( this.Suffix, n ) + extension;
                    while( File.Exists( path ) ) {
                        n++;
                        path = folder + "\\" + filename + suffixstr( this.Suffix, n ) + extension;
                    }
                }

            }

            this.Path = path;
            try {
                using( StreamWriter sw = new StreamWriter( path, append, enc ) ) {
                    sw.Write( GetSaveData() );
                    sw.Flush();
                    sw.Close();
                }
                ret = true;
            } catch( Exception ) {
            }
            return ret;
        }

        private String suffixstr( SUFFIX sx, int n ) {
            String ret = "";
            switch( sx ) {
                case SUFFIX.DOUBLE_DIGITS:
                    ret = String.Format( "{0:D2}", n );
                    break;
                case SUFFIX.DOUBLE_DIGITS_WITH_PARENTHESES:
                    ret = String.Format( "({0:D2})", n );
                    break;
                case SUFFIX.TRIPLE_DIGITS:
                    ret = String.Format( "{0:D3}", n );
                    break;
                case SUFFIX.TRIPLE_DIGITS_WITH_PARENTHESES:
                    ret = String.Format( "({0:D3})", n );
                    break;
                default:
                    ret = String.Format( "({0})", n );
                    break;
            }
            return ret;
        }

        public Boolean Load( String path ) {
            return Load( path, this.Encoding );
        }
        public Boolean Load( String path, Encoding enc ) {

            Boolean ret = false;
            this.Path = path;
            if( File.Exists( path ) ) {
                try {
                    using( StreamReader sr = new StreamReader( path, enc ) ) {

                        LoadAnalyze( sr.ReadToEnd() );
                        sr.Close();
                        ret = true;
                    }
                } catch( Exception ) {
                }
            }
            return ret;

        }

        public List<List<String>> LoadAnalyze( String strall ) {

            Clear();
            if( strall.Length > 0 ) {

                String[] strlines = strall.Split( '\n' );
                foreach( String strline in strlines ) {

                    if( strline.Length > 0 ) {

                        List<String> line = new List<String>();
                        String s = strline.Replace( "\r", "" ).Replace( "\n", "" ).Replace( "\x1A", "" ).Replace( "\t", "," );
                        String[] columns = s.Split( ',' );
                        for( int i = 0; i < columns.Length; i++ ) {

                            String col = columns[i];
                            if( col.Length > 0 ) {
                                if( this.mode == MODE.EXCELCSV && col.Substring( 0, 1 ).Equals( "=" ) ) {
                                    col = col.Substring( 1, col.Length - 1 );
                                }

                                if( col.Substring( 0, 1 ).Equals( "\"" ) ) {
                                    col = col.Substring( 1, col.Length - 1 );
                                    if( !col.Substring( col.Length - 1 ).Equals( "\"" ) ) {
                                        i++;
                                        while( i < columns.Length ) {

                                            String colsub = columns[i];
                                            col += "," + colsub;
                                            if( colsub.Length > 0 && colsub.Substring( colsub.Length - 1 ).Equals( "\"" ) ) {
                                                break;
                                            }
                                            i++;

                                        }
                                    }
                                }

                                if( col.Substring( col.Length - 1 ).Equals( "\"" ) ) {
                                    col = col.Substring( 0, col.Length - 1 );
                                }
                            }

                            line.Add( col );
                        }
                        this.LoadData.Add( line );

                    }

                }

            }
            return this.LoadData;
        }

    }

(※そのまま貼り付けて使おうとすると 怒られることもあるので適宜 Importしてください)

●使い方その1( CSVファイルの作成(保存) )

            Csv csv = new Csv();
            for( int i = 0; i < 10; i++ ) {
                csv.Add( "あ", "い", "う", "え","お" );
            }
            csv.Save( "C:\\Users\\NJC\\Desktop\\test.csv" );

Csvクラスのインスタンスを作成して .Addでデータを書いて .Saveで保存するだけ。
f:id:tsuchinoko118:20140815081920p:plain
というファイルを作ってくれる。

""(ダブルクォーテーション)で囲っているのがデフォルトだが、これが不要な場合は

            Csv csv = new Csv();
            csv.Siegae = "";
            for( int i = 0; i < 10; i++ ) {
                csv.Add( "あ", "い", "う", "え","お" );
            }
            csv.Save( "C:\\Users\\NJC\\Desktop\\test.csv" );

と Siegaeプロパティを空白にすればOK。

また作成したファイルを EXCELなどで開いて使うことが前提の場合、長い数値だと
わけのわからない短縮形に勝手に変わってしまうことがある。
例えば電話番号 0761234567 だと ( 076-123-4567 では問題は起こらない )

761234567 と 頭の 0 が消えてしまったり
7.63E+08 と意味不明の表現になってしまったり

見えるだけなら問題ないのかも知れないが、このファイルを保存すると
データが変わってしまうので、大変よろしくない。

こうした場合は(EXCELのみの対応だが) ダブルクオーテーションで囲って、先頭に = をつける(式にする)と回避できる。
意味合いとしては 0761234567 は人間が見ると電話番号という文字だが、EXCEL は 761234567 という数字だと見なす。
これを =" " で これは、こういうものですよと指定するわけだ。

            Csv csv = new Csv();
            for( int i = 0; i < 10; i++ ) {
                csv.Add( "あ", "い", "う", "え","お",true,"0762875097" );
            }
            csv.Save( "C:\\Users\\NJC\\Desktop\\test.csv" );

引数の前に true をつけると、これは文字ですよ型で CSVデータが作成される。
その他、もろもろのプロパティは、解説しないので(笑)、ソースを見て解析してもらいたい。
なお、本クラスは自由にコピペしていただいて構わないが、サポートはないので質問があっても、筆者は解説しない。


●使い方その2( CSVファイルの読取(取得) )

            Csv csv = new Csv();
            csv.Load( "C:\\Users\\NJC\\Desktop\\Resized\\test.csv" );
            foreach( var line in csv.LoadData ) {

                String column = "";
                foreach( var s in line ) {
                    column += "[" + s + "] ";
                }
                MessageBox.Show( column );
            }

Csvクラスをインスタンスで作成して、 .Load メソッドにパスを指定すればよいだけ。
.LoadDataプロパティに

List< List<String> >

で格納されているので
foreach の foreach で 列記することができる。
ダブルクオーテーションで囲ってあってもなくても、また 前述の EXCEL対応 = があってもなくても
読み取り可能だ。
筆者はこれで、かなりコーディングがラクチンになった(笑)
なお、本クラスは自由にコピペしていただいて構わないが、サポートはないので質問があっても、筆者は解説しない。

実際に筆者が使っているのは、もうすこし拡張機能が豊富で、固定長フォーマットにも対応させたりしているが、この端折ったクラスでも結構な実用性はあるだろう。

なお、しつこいようだが本クラスは自由にコピペしていただいて構わないが、サポートはないので質問があっても、筆者は解説しない。