このページは、私が、C++ programming の学習にあたって、参考となった事柄、source code などを備忘録として記録したものです。
This page is a record of things that I found useful as a reference, including source code, as I learned C++ programming, serving as a memo for myself.
comma-separated values(CSV) は、データ交換用のデファクトスタンダードとして、古くから多くの表計算ソフトやデータベースソフトで使われています。 しかし、CSV 解析用のclass は、C++ の標準ライブラリーには、用意されていません。 ここでは、 Brian W. Kernighan and Rob Pike の ' The Practice of Programming '(「プログラミング作法」)の第4章にサンプルコードとして掲載されているものを一部改変して紹介します。
このプログラムで扱える CSV フォーマットは、 IETF(Internet Engineering Task Force) による RFC(Request for Comments) 4180 (RFC についてはここを参照) でInformational として仕様が成文化されたものに準拠しています。
元のコードにはなかった、反復子(iterator)としての扱いや、 クウォート解析を行った上でクウォート自体を解析フィールドに残す処理 (CSV ファイルを読み込んだ上で、フィールドを改変し、元のファイルを更新する処理等では有用です。)等を追加しています。
なお、この class を使用した sample code を添付しました。これは、標準入力から 読み込んだ CSV 形式の入力行を解析し、各 field に分割して出力するだけのプログラムです。テスト用として添付します。
Comma-separated values (CSV) have long been used in many spreadsheet and database software as a de facto standard for data exchange. However, there is no class for CSV analysis in the C++ standard library. Here, I introduce a modified version of the sample code from Chapter 4 of "The Practice of Programming" by Brian W. Kernighan and Rob Pike as an example.
The CSV format that this program can handle conforms to the specification documented in RFC (Request for Comments) 4180, which was published as Informational by the IETF (Internet Engineering Task Force).
I have added support for treating CSV as an iterator and for preserving quotation marks as parsed fields (useful for modifying fields and updating the original file after reading a CSV file, etc.), which were not included in the original code.
Attached is a sample code that uses this class, which simply parses input lines in CSV format from standard input and outputs them as divided fields. It is provided as a test program.
Copyright Notice
このコードは、Brian W. Kernighan and Rob Pike の'The Practice of Programming'(「プログラミング作法」)の第4章のサンプルコードを一部改変して使用しています。
このコードの元となったコード(C++ CSV program from Chapter 4 'The Practice of Programming')の電子ファイルは、Brian W. Kernighan のホームページ( http://www.cs.princeton.edu/~bwk/ )から入手可能です。そして次ぎの条件の下に、如何なる目的での使用も可能です。
Copyright (C) 1999 Lucent Technologies
Excerpted from 'The Practice of Programming'(C++ CSV program from Chapter 4) by Brian W. Kernighan and Rob Pike
You may use this code for any purpose, as long as you leave the copyright notice and book citation attached.
このコード Csvclass.hpp は、Dachunzhu が 'The Practice of Programming'のサンプルコードを元に、2002年頃、当時勤務していた職場で「**情報システム」のCSV形式の退避データ(元のデータは、ACCESSのデータベースファイル)を直接編集・加工するために作成したものです。上記条件に従う限り、改変部分も含めて、自由に使用してください。
Csvclass.hpp
This code is a C++ class for parsing comma-separated CSV files. The Csv class divides the loaded lines into fields and provides functions to access each field.
This class includes the following member variables and member functions:
Member variables:
fin: input file pointer
line: input line
field: field string
nfield: number of fields
fieldsep: separator character
keepquote: flag to keep quotes
Member functions:
Csv(): constructor
getline(): retrieve lines and expand if necessary
endofline(): check and discard newlines
split(): split lines into fields
advplain(): process non-quoted fields
advquoted(): process quoted fields
advquoted_keepquote(): process quoted fields and keep quotes
This class uses std::vector to store fields, and fields can also be accessed through iterators. Each field can also be accessed using the overloaded [] operator.
The field separator specified in the constructor is comma by default, but it can be changed if necessary. The keepquote flag can also be set to control whether quotes are kept if fields are enclosed in quotes.
00001
00027 #ifndef CSVCLASS_INC_
00028 #define CSVCLASS_INC_
00029
00030 #include<iostream>
00031 #include<string>
00032 #include<vector>
00033
00034 namespace dachunzhu
00035 {
00036
00037 class Csv
00038 { // カンマで区切られた値を読んで解析する
00039 public:
00040 Csv (bool keepquote = false,
00041 std::istream & fin = std::cin,
00042 std::string sep = ",")
00043 :keepquote (keepquote), fin (fin), fieldsep (sep)
00044 {}
00045
00046 int getline (std::string &);
00047 std::string getfield (unsigned n) const;
00048 std::vector < std::string > gettfields ();
00049 std::string operator[] (unsigned n) const;
00050 typedef std::vector < std::string >::const_iterator iterator;
00051 int getnfield () const
00052 {
00053 return nfield;
00054 }
00055 iterator begin () const
00056 {
00057 return field.begin();
00058 }
00059 iterator end () const
00060 {
00061 return field.begin() + nfield;
00062 }
00063
00064 private:
00065 std::istream & fin; // 入力ファイルポインタ
00066 std::string line; // 入力行
00067 std::vector < std::string > field; // フィールド文字列
00068 unsigned nfield; // フィールド数
00069 std::string fieldsep; // セパレータ文字
00070 bool keepquote; // quote を keep ?
00071
00072 unsigned split ();
00073 int endofline (char);
00074 int advplain (const std::string & line, std::string & fld, int);
00075 int advquoted (const std::string & line, std::string & fld, int);
00076 int advquoted_keepquote (const std::string & line, std::string & fld, int);
00077 };
00078
00079 // getline: 1行取得し、必要に応じて伸張
00080 int Csv::getline (std::string & str)
00081 {
00082 char c;
00083 for (line = ""; fin.get (c) && !endofline (c);)
00084 line += c;
00085 split ();
00086 str = line;
00087 return !fin.eof ();
00088 }
00089
00090 // endofline: \r, \n, \r\n, EOF をチェックして捨てる
00091 int Csv::endofline (char c)
00092 {
00093 int eol;
00094
00095 eol = (c == '\r' || c == '\n');
00096 if (c == '\r')
00097 {
00098 fin.get (c);
00099 if (!fin.eof () && c != '\n')
00100 fin.putback (c); // 読みすぎ
00101 }
00102 return eol;
00103 }
00104
00105 // split: 行をフィールド単位に分割
00106 unsigned Csv::split ()
00107 {
00108 std::string fld;
00109 unsigned i, j;
00110
00111 nfield = 0;
00112 if (line.length () == 0)
00113 return 0;
00114 i = 0;
00115
00116 do
00117 {
00118 if (i < line.length () && line[i] == '"')
00119 if (keepquote == false) // 分割トークンにクォートを keep するか
00120 j = advquoted (line, fld, ++i); // クォートをスキップ
00121 else
00122 j = advquoted_keepquote (line, fld, ++i); // クォートを含める
00123 else
00124 j = advplain (line, fld, i);
00125 if (nfield >= field.size ())
00126 field.push_back (fld);
00127 else
00128 field[nfield] = fld;
00129 nfield++;
00130 i = j + 1;
00131 }
00132 while (j < line.length ());
00133
00134 return nfield;
00135 }
00136
00137 // advquoted: クォートで囲まれたフィールド:次のセパレータのインデックスを返す
00138 int Csv::advquoted (const std::string & s, std::string & fld, int i)
00139 {
00140 unsigned j;
00141
00142 fld = "";
00143 for (j = i; j < s.length (); j++)
00144 {
00145 if (s[j] == '"' && s[++j] != '"')
00146 {
00147 unsigned k = s.find_first_of (fieldsep, j);
00148 if (k > s.length ())
00149 k = s.length ();
00150 for (k -= j; k-- > 0;)
00151 fld += s[j++];
00152 break;
00153 }
00154 fld += s[j];
00155 }
00156 return j;
00157 }
00158
00159 // advquoted_keepquote: クォートで囲まれたフィールド:次のセパレータのインデックスを返す
00160 // 但し、クォート自体を分割するフィールドに残す
00161 int Csv::advquoted_keepquote (const std::string & s, std::string & fld, int i)
00162 {
00163 unsigned j;
00164
00165 fld = s[i - 1];
00166 for (j = i; j < s.length (); j++)
00167 {
00168 if (s[j] == '"' && s[++j] != '"')
00169 {
00170 unsigned k = s.find_first_of (fieldsep, j);
00171 if (k > s.length ())
00172 k = s.length ();
00173 fld += s[j-1];
00174 for (k -= j; k-- > 0;)
00175 fld += s[j++];
00176 break;
00177 }
00178 if (s[j] == '"')
00179 fld += s[j-1];
00180 fld += s[j];
00181 }
00182 return j;
00183 }
00184
00185 // advplain: クォートでくくられていないフィールド:次のセパレータのインデックスを返す
00186 int Csv::advplain (const std::string & s, std::string & fld, int i)
00187 {
00188 unsigned j;
00189
00190 j = s.find_first_of (fieldsep, i);
00191 if (j > s.length ())
00192 j = s.length ();
00193 fld = std::string (s, i, j - i);
00194 return j;
00195 }
00196
00197 // getfield: n番目のフィールドを返す
00198 std::string Csv::getfield (unsigned n) const
00199 {
00200 if (n >= nfield)
00201 return "";
00202 else
00203 return field[n];
00204 }
00205
00206 // gettfields: フィールドに分割処理された入力行全体を返す
00207 std::vector < std::string > Csv::gettfields ()
00208 {
00209 // field メンバーは getline() の前の呼び出しでセットされた
00210 // ものを”器”としてを使っている(field 数は nfield で管理)のでそのまま返すと
00211 // nfield 以上の field を持つ場合がある。
00212 std::vector < std::string > v;
00213 for (unsigned i = 0; i < nfield; i++)
00214 v.push_back (field[i]);
00215 field = v;
00216
00217 return field;
00218 }
00219
00220 // csv[i]としてn番目のフィールドをアクセス
00221 std::string Csv::operator[](unsigned n) const
00222 {
00223 if (n >= nfield)
00224 return "";
00225 else
00226 return field[n];
00227 }
00228
00229 } // end of namespace dachunzhu
00230
00231 #endif // CSVCLASS_INC_
sample.cpp for Csvclass
00001 #include <iostream>
00002 #include <algorithm>
00003 #include <string>
00004 #include <vector>
00005
00006 #include "Csvclass.hpp"
00007
00008 using namespace std;
00009
00010 // Csvtest main: test Csv class
00011 int main(void)
00012 {
00013 string line;
00014
00015 bool keepquote = false;
00016 istream & fin = cin;
00017 string sep = ",";
00018
00019 dachunzhu::Csv csv(keepquote, fin, sep);
00020
00021 while (csv.getline(line) != 0)
00022 {
00023 cout << "line = `" << line <<"'\n";
00024 int i = 0;
00025 for (dachunzhu::Csv::iterator beg = csv.begin(); beg != csv.end(); beg++,i++)
00026 cout << "field[" << i << "] = `"
00027 << *beg << "'\n";
00028 }
00029 return 0;
00030 }
ここでは、Boost.tokenizer(escaped_list_separator)による CSV解析における Shift-jis 文字セット環境下での不具合修正を行うための
The escaped_list_separator_sjis class for Boost.tokenizer
のコーディング例を示します。
実は、CSV(comma-separated values) は、Boost C++ libraries の、Boost.tokenizer class により解析可能です。それは、この Boost.tokenizerに escaped_list_separator class を 関数オブジェクト(TokenizerFunction)として渡すことによって実現できます。
Boost.tokenizer はトークン解析を行うための汎用クラスであり、トークン解析規則を定義した任意のTokenizerFunction を渡すことにより、当該規則に基づくトークン解析が可能になります。
escaped_list_separator class は、boost TokenizerFunction の実装モデルのひとつで、 この class が解析の対象とする文字列 list は、一般的に CSV(comma-separated values) として知られる文字列list のスパーセットになっています。
ただし、この class で扱える CSV は、汎用的ではあるものの IETF(Internet Engineering Task Force)による RFC(Request for Comments) 4180 で informational として仕様が成文化された、いわゆるデファクト・スタンダードな CSV とは異なります。
excel 等で、データ交換用として使われる CSV ファイルは、この RFC 4180 に準拠したものになっており、Boost.tokenizer(escaped_list_separator) class では、直接的には扱えません。これについては、先に示したコーディング例 ( Brian W. Kernighan and Rob Pike による "The Practice of Programming"(「プログラミング作法」)の第4章のサンプルコードに基づくもの)等を参照して対応してください。
escaped_list_separator class で定義される CSV の仕様は次のとおりです。
文字列は、
The escaped_list_separator class. Which is a model of TokenizerFunction.
An escaped list is a super-set of what is commonly known as a comma separated value (csv) list.
It is separated into fields by a comma or other character. If the delimiting character is inside quotes, then it is counted as a regular character.
To allow for embedded quotes in a field, there can be escape sequences using the \ much like C.
The role of the comma, the quotation mark, and the escape
character (backslash \), can be assigned to other characters.
ここで、
「delimiting character」、
「quote」、
「escape character」
は、デフォルトでは、それぞれ
「comma(,)」、
「quotation mark (")」、
「backslash(\)」
とされ、constructor で明示すれば、他の文字に割り当てが可能。
また、escape sequence として、
<escape><quote>、
<escape>n、
<escape><escape>
の3つがサポートされ、それぞれ
<quote>、
newline、
<escape>
を表す。
00001 #include <iostream>
00002 #include <boost/tokenizer.hpp>
00003 #include <string>
00004
00005 int main()
00006 {
00007 using namespace std;
00008 using namespace boost;
00009
00010 typedef tokenizer<escaped_list_separator<char> > Tokenizer;
00011 string line;
00012
00013 while (getline(cin, line))
00014 {
00015 Tokenizer tok(line);
00016 cout << "line = `" << line <<"'\n";
00017 int i = 0;
00018 for
00019 (
00020 Tokenizer::iterator beg=tok.begin();
00021 beg!=tok.end();
00022 ++beg,++i
00023 )
00024 cout << "field[" << i << "] = `" << *beg << "'" << endl;
00025 cout << endl;
00026 }
00027
00028 return 0;
00029 }
Boost.tokenizer(escaped_list_separator class)による CSV 解析は、汎用性があり便利ですが、一部の環境(microsoft windows 等)では問題が生じる場合があります。それは、Shift-jis 文字セット環境下においてです。Shift-jis 文字セットでは、2バイト文字の一部に、その第2バイト目にエスケープ記号("\" (0x5C))と同じコードがくる文字(例えば、「ソ」、「申」、「十」等)があるからです。
その様な場合、escaped_list_separator による解析では、当該文字の次の文字(次の1バイト文字、あるいは次の2バイト文字の第1バイト目)とのセットをエスケープシケースとして扱ってしまい、正しく処理できません(文字化けが生じる、あるいは、解析が実行時エラーとなりアボートする。)。そこで、この The escaped_list_separator_sjis class では、 The escaped_list_separator class コードの一部を改変して分割対象の文字列中に Shift-jis 文字があっても正しく解析できるようにしています。
00001
00048 #ifndef BOOST_TOKEN_FUNCTIONS_ESCLISTSEP_SJIS_HPP_
00049 #define BOOST_TOKEN_FUNCTIONS_ESCLISTSEP_SJIS_HPP_
00050
00051 #include <vector>
00052 #include <stdexcept>
00053 #include <string>
00054 #include <cctype>
00055 #include <algorithm> // for find_if
00056
00057 #ifndef BOOST_TOKEN_FUNCTIONS_JRB120303_HPP_
00058 //
00059 // the following must not be macros if we are to prefix them
00060 // with std:: (they shouldn't be macros anyway...)
00061 //
00062 #ifdef ispunct
00063 # undef ispunct
00064 #endif
00065 #ifdef isspace
00066 # undef isspace
00067 #endif
00068 //
00069 // fix namespace problems:
00070 //
00071 #ifdef BOOST_NO_STDC_NAMESPACE
00072 namespace std
00073 {
00074 using ::ispunct;
00075 using ::isspace;
00076 }
00077 #endif
00078 #endif
00079
00080 namespace boost
00081 {
00082
00083 //===========================================================================
00084 // The escaped_list_separator class. Which is a model of TokenizerFunction
00085 // An escaped list is a super-set of what is commonly known as a comma
00086 // separated value (csv) list.It is separated into fields by a comma or
00087 // other character. If the delimiting character is inside quotes, then it is
00088 // counted as a regular character.To allow for embedded quotes in a field,
00089 // there can be escape sequences using the \ much like C.
00090 // The role of the comma, the quotation mark, and the escape
00091 // character (backslash \), can be assigned to other characters.
00092
00093 #ifndef BOOST_TOKEN_FUNCTIONS_JRB120303_HPP_
00094 struct escaped_list_error : public std::runtime_error
00095 {
00096 escaped_list_error(const std::string& what_arg):std::runtime_error(what_arg) { }
00097 };
00098 #endif
00099
00100 class escaped_list_separator_sjis
00101 {
00102
00103 private:
00104
00105 struct char_eq
00106 {
00107 char e_;
00108 char_eq(char e):e_(e) { }
00109 bool operator()(char c)
00110 {
00111 return std::char_traits<char>::eq(e_,c);
00112 }
00113 };
00114 std::string escape_;
00115 std::string c_;
00116 std::string quote_;
00117 bool last_;
00118
00119 bool is_escape(char e)
00120 {
00121 char_eq f(e);
00122 return std::find_if(escape_.begin(),escape_.end(),f)!=escape_.end();
00123 }
00124 bool is_c(char e)
00125 {
00126 char_eq f(e);
00127 return std::find_if(c_.begin(),c_.end(),f)!=c_.end();
00128 }
00129 bool is_quote(char e)
00130 {
00131 char_eq f(e);
00132 return std::find_if(quote_.begin(),quote_.end(),f)!=quote_.end();
00133 }
00134
00135 template <typename iterator, typename Token>
00136 void do_escape(iterator& next,iterator end,Token& tok)
00137 {
00138 if (++next == end)
00139 throw escaped_list_error(std::string("cannot end with escape"));
00140 if (std::char_traits<char>::eq(*next,'n'))
00141 {
00142 tok+='\n';
00143 return;
00144 }
00145 else if (is_quote(*next))
00146 {
00147 tok+=*next;
00148 return;
00149 }
00150 else if (is_c(*next))
00151 {
00152 tok+=*next;
00153 return;
00154 }
00155 else if (is_escape(*next))
00156 {
00157 tok+=*next;
00158 return;
00159 }
00160 else
00161 throw escaped_list_error(std::string("unknown escape sequence"));
00162 }
00163
00164 bool is_sjis1st(char c)
00165 {
00166 unsigned char j = static_cast<unsigned char>(c);
00167 return ((j >= 0x81 && j <= 0x9f) || (j >= 0xe0 && j <= 0xfc));
00168 }
00169 bool is_sjis2nd(char c)
00170 {
00171 unsigned char j = static_cast<unsigned char>(c);
00172 return ((j >= 0x40 && j <= 0x7e) || (j >= 0x80 && j <= 0xfc));
00173 }
00174
00175 template <typename iterator, typename Token>
00176 void do_sjis(iterator& next, iterator end, Token& tok)
00177 {
00178 tok += *next;
00179 if (++next == end)
00180 throw escaped_list_error(std::string("cannot end with Shift-jis code"));
00181 if (is_sjis2nd(*next))
00182 {
00183 tok += *next;
00184 }
00185 else
00186 {
00187 throw escaped_list_error(std::string("unknown Shift-jis code"));
00188 }
00189 }
00190
00191 public:
00192
00193 explicit escaped_list_separator_sjis(char e = '\\',
00194 char c = ',',char q = '\"')
00195 : escape_(1,e), c_(1,c), quote_(1,q), last_(false)
00196 { }
00197
00198 escaped_list_separator_sjis(std::string e, std::string c, std::string q)
00199 : escape_(e), c_(c), quote_(q), last_(false)
00200 { }
00201
00202 void reset()
00203 {
00204 last_=false;
00205 }
00206
00207 template <typename InputIterator, typename Token>
00208 bool operator()(InputIterator& next,InputIterator end,Token& tok)
00209 {
00210 bool bInQuote = false;
00211 tok = Token();
00212
00213 if (next == end)
00214 {
00215 if (last_)
00216 {
00217 last_ = false;
00218 return true;
00219 }
00220 else
00221 return false;
00222 }
00223 last_ = false;
00224 for (;next != end;++next)
00225 {
00226 if (is_escape(*next))
00227 {
00228 do_escape(next,end,tok);
00229 }
00230 else if (is_c(*next))
00231 {
00232 if (!bInQuote)
00233 {
00234 // If we are not in quote, then we are done
00235 ++next;
00236 // The last character was a c, that means there is
00237 // 1 more blank field
00238 last_ = true;
00239 return true;
00240 }
00241 else tok+=*next;
00242 }
00243 else if (is_quote(*next))
00244 {
00245 bInQuote=!bInQuote;
00246 }
00247 else if (is_sjis1st(*next))
00248 {
00249 do_sjis(next,end,tok);
00250 }
00251 else
00252 {
00253 tok += *next;
00254 }
00255 }
00256 return true;
00257 }
00258
00259 }; // end 0f class escaped_list_separator_sjis
00260
00261 } // end of namespace boost
00262
00263
00264 #endif // BOOST_TOKEN_FUNCTIONS_ESCLISTSEP_SJIS_HPP_
00001 #include <iostream>
00002 #include <boost/tokenizer.hpp>
00003 #include <string>
00004 #include "escaped_list_separator_sjis.hpp"
00005
00006 int main()
00007 {
00008 using namespace std;
00009 using namespace boost;
00010
00011 string line;
00012
00013 while (getline(cin, line))
00014 {
00015 tokenizer<escaped_list_separator_sjis > tok(line);
00016 cout << "line = `" << line <<"'\n";
00017 int i = 0;
00018 for
00019 (
00020 tokenizer<escaped_list_separator_sjis >::iterator beg=tok.begin();
00021 beg!=tok.end();
00022 ++beg,++i
00023 )
00024 cout << "field[" << i << "] = `" << *beg << "'" << endl;
00025 cout << endl;
00026 }
00027
00028 return 0;
00029 }
Boost.tokeniser については、
"http://www.boost.org/libs/tokenizer/" の documentation
を参照してください。
The escaped_list_separator class は、
"http://www.boost.org/boost/token_functions.hpp"
Copyright John R. Bandela 2001
Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at "http://www.boost.org/LICENSE_1_0.txt"
中で定義されています。
いわゆる書式付き出力です。cout(ostream class)のメンバー関数である setw() と setfill() を使ってフィールドの幅と充填文字を定義しています。<iomanip>
ヘッダーを取り込む必要があります。
使い方は、 [dachunzhu@dachunzhu-pc CsvParserClass]$ cat source.cpp | ./prog
>dest.txt
などのようにして、source code ファイルを標準出力に表示し、これをパイプでプログラムに渡し、その出力をファイル等へリダイレクトする等して取り込みます。下は、source code
自体のこのプログラムによる出力結果になります。
00001 #include <iostream>
00002 #include <string>
00003 #include <iomanip>
00004
00005 int main()
00006 {
00007 using namespace std;
00008
00009 string line;
00010 const string wSpaces = " ";
00011 int i=1;
00012
00013 while (getline(cin, line))
00014 {
00015 line.append(wSpaces);
00016 cout << setw(5) << setfill('0') << i << " " << line << endl;
00017 i++;
00018 }
00019 }