For Gauche 0.9.5


Next: , Previous: , Up: ライブラリモジュール - ユーティリティ   [Contents][Index]

12.49 text.csv - CSVテーブル

Module: text.csv

RFC4180に定義されたフォーマットを含む、 CSV (カンマ区切りの値) の表をパーズ/生成するための手続きを提供します。 区切り文字やクオート文字をカスタマイズすることで、CSVに似たフォーマットを 広くカバーすることができます。

CSV の表は、改行で区切られた record の連続で構成されます。 それぞれのレコードは、区切り文字(デフォルトではカンマ)で区切られた 複数の field を含みます。フィールドは、クォートされている (二重引用符で囲まれている)場合は、カンマや改行を含むことができます。 クォートされたフィールドに二重引用符を含めるには、2つの連続する 二重引用符を使います。通常、フィールドの前後の空白は無視されます。

CSV様のファイルの使われ方は多様なので、このモジュールでは柔軟に組み合わせることが出来る 多層的なAPIを提供します。

低レベルAPI

一番下の層のAPIは、テキストとリストのリストとを相互変換するものです。

Function: make-csv-reader separator :optional (quote-char #\")

入力ポートを省略可能引数として取る手続きを返します。 手続きが呼ばれると、ポート(省略された場合は現在の入力ポート)からレコードを1つ読み込み、 フィールドのリストを返します。入力ポートが EOF に達すると、EOF を返します。

Function: make-csv-writer separator :optional newline (quote-char #\") special-char-set

出力ポートとフィールドのリストの2つの引数を取る手続きを返します。 手続きが呼ばれると、separator で区切られたフィールドを 正しくエスケープして出力ポートに出力します。各フィールドの値は文字列でなければなりません。 separatorは文字または文字列です。

レコードの区切り文字列をnewline で指定することもできます。例えば、ファイルが Windows の プログラムでも読めるように、"\r\n" を渡すことができます。

フィールドの出力は、その中に特殊文字が含まれていた場合にクオートされます。 separator, quote-char, newlineに含まれる文字は 自動的に特殊文字と認識されます。さらに special-char-setに文字セットを渡すと、それも特殊文字として扱われます。 省略時は#[;\s]が使われます。

中レベルAPI

スプレッドシート等で作成されたCSVファイルはしばしば、 不要な行や列を含んでいたり、 行や列の内容から有効なデータの場所を探したりする必要があります。 そのために役に立つユーティリティが用意してあります。

スプレッドシート等によって生成される典型的なCSVには以下の特徴があります。

  1. 最初の方に、「ヘッダ行」があります。一番最初の行とは限りませんが、 データの実体より前に現れ、データの各列の意味を決めます。 しばしば、見た目のためだけに余分な列が挿入されたり、 編集によって列が入れ替わったりすることもあるので、 ヘッダ行を見てどの列に何があるか判断する必要があります。
  2. ヘッダ行の後に続く「レコード行」。ここにデータの実体があります。 見た目のためだけに余分な行が挿入されていることもあります。 また、データの終わりがきちんと示されていないことも多いです (例えば、 CSVの末尾に全ての列が空文字列である行がぞろぞろとくっついている、等。)

中レベルCSVパーザの主目的は、低レベルパーザの出力である文字列のリストのリストを 受け取って、ヘッダ行を探し出し、続くレコード行をヘッダ行の内容に沿って タプルに変換することにあります。ここで、タプルは単なる文字列のリストですが、 指定されたヘッダ仕様に沿った順序で並べてあるものとします。

Function: csv-rows->tuples rows header-specs :key required-slots allow-gap?

入力行(文字列のリスト)のリストを、タプルのリストに変換します。 タプルはスロットの値のリストです。

まずheader-specにマッチするヘッダ行が探されます。ヘッダ行が見つかったら、 それにしたがって後続の行をレコード行として解釈し、各行をタプルへと変換します。 ヘッダ行が見つからなければ、#fが返されます。

header-specsはヘッダ仕様のリストです。各要素は文字列、正規表現、 あるいは文字列を取る述語手続きのいずれかです。文字列の場合、その文字列と正確に 一致する内容を持つ列が選ばれます。正規表現なら内容がそれと一致する列、 述語手続きなら、内容がその述語を満たすような列がそれぞれ選ばれます。

header-specsに現れる順番が、出力タプルの列の順番を決めます。

required-slotsは、入力行が有効なレコード行かそうでないかを決めます。 required-slotsの構造は以下のとおりです。

   <required-slots> : (<spec> ...)
   <spec> : <header-spec> | (<header-spec> <predicate>)

<header-spec>はどの列の要素をチェックすべきかを指定します。 header-slotに現れる要素のいずれかとequal?でなければなりません。 <header-spec>だけが<spec>として与えられていた場合は、 その列が空であってはいけない、ということを意味します。 <header-spec><predicate>が与えられた場合は、 レコード行の該当列の値(文字列)が述語手続き<predicate>に与えられ、 それが偽を返したらその行は有効でないということになります。

allow-gap?#tであった場合、無効な行を飛ばしながら、 入力データは最後まで処理されます。allow-gap?#fであった場合(デフォルト)は、 無効な行が出てきた時点で処理を打ちきります。

例を見てみましょう。次のデータがdata.csvというファイルに書かれているとします。 スプレッドシートからエクスポートされるデータによくあるように、不要な行や列が混ざっています。

,,,,,,,,
"Exported data",,,,,,,,
,,,,,,,,
,,Year,Country,,Population,GDP,,Note
,,1958,"Land of Lisp",,39994,"551,435,453",,
,,1957,"United States of Formula Translators",,115333,"4,343,225,434",,Estimated
,,1959,"People's Republic of COBOL",,82524,"3,357,551,143",,
,,1970,"Kingdom of Pascal",,3785,,,"GDP missing"
,,,,,,,,
,,1962,"APL Republic",,1545,"342,335,151",,

ここから、Country, Year, GDP, Populationの値からなるタプルのリストを 次のとおり取り出せます。

(use text.csv)
(use gauche.generator)

(call-with-input-file "data.csv" 
  (^p (csv-rows->tuples 
       (generator->list (cute (make-csv-reader #\,) p))
       '("Country" "Year" "GDP" "Population"))))
 ⇒
  (("Land of Lisp" "1958" "551,435,453" "39994")
   ("United States of Formula Translators" "1957" "4,343,225,434" "115333")
   ("People's Republic of COBOL" "1959" "3,357,551,143" "82524")
   ("Kingdom of Pascal" "1970" "" "3785"))

無関係な行は無視されており、また結果の列はheader-specsに渡した順序に 整列されていることに注目してください。

“Kingdom of Pascal”の列の後にギャップ(空の列)があるため、 csv-rows->tuplesはそこで処理を打ち切っています。 ギャップの後の“APL Republic”まで含めたければ、csv-rows->tuples:allow-gap? #tを渡してください。

次の例では、:required-slots引数を与えて、 Year、Country、GDPのデータの一つ以上が欠けている行を除外しています。 したがって“Kingdom of Pascal”は結果から除かれます。一方、 :allow-gap?引数のために“APL Republic”が含まれます。 (この例ではYearが4桁ぴったりの数値であるかどうかもチェックしています。)

(call-with-input-file "data.csv" 
  (^p (csv-rows->tuples 
       (generator->list (cute (make-csv-reader #\,) p))
       '("Country" "Year" "GDP" "Population")
        :required-slots '(("Year" #/^\d{4}$/) "Country" "GDP")
        :allow-gap? #t)))
 ⇒
 (("Land of Lisp" "1958" "551,435,453" "39994")
  ("United States of Formula Translators" "1957" "4,343,225,434" "115333")
  ("People's Republic of COBOL" "1959" "3,357,551,143" "82524")
  ("APL Republic" "1962" "342,335,151" "1545"))

以下の二つの手続きはcsv-rows->tuplesの材料です。

Function: make-csv-header-parser header-specs

行(文字列のリスト)を受け取り、それがheader-specsで指定される条件に 一致するかどうかを調べます。 (header-specsについては上のcsv-rows->tuplesを見てください。) もし引数が条件を満たしたなら、置換ベクタを返します。 置換ベクタはタプルの位置を入力行の列番号へとマップするもので。 一致しなかった場合は#fを返します。

置換ベクタは整数のベクタです。K番目の要素がIであることは、 タプルのK番目の要素が入力のI番目の列から取られることを示します。

例を見てみましょう。入力に、以下の形の行がヘッダ行として含まれていることが わかっているとします。

(define *input-row* '("" "" "Year" "Country" "" "Population" "GDP" "Notes"))

こういう形の行を見つけたいわけですが、取り出すデータとしては、 CountryYearGDPPopulationだけが この順で必要であるとします。この場合、ヘッダパーザを 次のとおり構成できます。

(define header-parser
  (make-csv-header-parser '("Country" "Year" "GDP" "Population")))

このヘッダパーザを想定される入力行に適用すれば、置換ベクタが返ってきます:

(header-parser *input-row*)
 ⇒ #(3 2 6 5)

返り値の意味は、タプルの第0要素(Country)は入力の第3列にあり、 タプルの第1要素(Year)は入力の第2列にある、といった具合です。 置換ベクタはレコード行をパーズしてタプルを生成するのに使えます。

Function: make-csv-record-parser header-slots permuter :optional required-slots

入力の1行をタプルへと変換する手続きを作って返します。

permutermake-csv-header-parserが返す置換ベクタです。

header-slots及びrequired-slots引数については 上のcvs-rows->tuplesを見てください。


Next: , Previous: , Up: ライブラリモジュール - ユーティリティ   [Contents][Index]