Text::CSV で Error 2032 が出たときの対処方法

久々に Perl を使って csv ファイルの処理を行いました。
カンマ区切りの csv ファイルや、タブ区切りの tsv ファイルを扱うにあたって、単純に split 関数を使って文字列を区切る処理では、各フィールド内にカンマやタブが混入したデータの場合に正常に切り出せません。

散々既出だと思うので、あえて今更説明するまでもないと思いつつも、簡単な実例を交えて説明してみます。具体的には下記のようなプログラムでは、期待するフィールドに切り出すことができません。

use Data::Dumper;
my $line = qq{"aaa,a","bbb,b","ccc,c"};
print Dumper split /,/, $line;

期待する結果は、"aaa,a"、"bbb,b"、"ccc,c" の3カラムに切り出したいところですが、下記のように意図しない区切りで切り出されてしまいます。単純にカンマを delimiter として分割しているので動作としては正しいのですが、欲しい結果とは異なります。

$VAR1 = '"aaa';
$VAR2 = 'a"';
$VAR3 = '"bbb';
$VAR4 = 'b"';
$VAR5 = '"ccc';
$VAR6 = 'c"';

そんなときに威力を発揮するのが Text::CSV や Text::CSV_XS なわけです。

- スポンサーリンク -

Text::CSV を使って意図するフィールド分割を行うコードは下記の通りになります。

use Data::Dumper;
use Text::CSV;
my $tc = Text::CSV->new;
my $line = qq{"aaa,a","bbb,b","ccc,c"};
$tc->parse($line);
print Dumper $tc->fields;

結果は期待通りに、"aaa,a"、"bbb,b"、"ccc,c" の3フィールドに分割されました。

$VAR1 = 'aaa,a';
$VAR2 = 'bbb,b';
$VAR3 = 'ccc,c';

そんなわけで、csv や tsv ファイルを Excel へ流し込んだり、集計したりする際には、Text::CSV をおとなしく使っていれば良いのですが、先日書いたコードを実行したところ、Error 2032 なるエラーでファイルが解析できない問題が発生しました。Perl 界隈においては、そんなときまずソース読めが基本ですね。

読んでみました。CSV_PP.pm に下記のようなエラー定義がありました。

my $ERRORS = {
        # PP and XS
        1000 => "INI - constructor failed",
        1001 => "sep_char is equal to quote_char or escape_char",
        1002 => "INI - allow_whitespace with escape_char or quote_char SP or TAB",
        1003 => "INI - \r or \n in main attr not allowed",

        2010 => "ECR - QUO char inside quotes followed by CR not part of EOL",
        2011 => "ECR - Characters after end of quoted field",

        2021 => "EIQ - NL char inside quotes, binary off",
        2022 => "EIQ - CR char inside quotes, binary off",
        2025 => "EIQ - Loose unescaped escape",
        2026 => "EIQ - Binary character inside quoted field, binary off",
        2027 => "EIQ - Quoted field not terminated",

        2030 => "EIF - NL char inside unquoted verbatim, binary off",
        2031 => "EIF - CR char is first char of field, not part of EOL",
        2032 => "EIF - CR char inside unquoted, not part of EOL",
        2034 => "EIF - Loose unescaped quote",
        2037 => "EIF - Binary character in unquoted field, binary off",

        2110 => "ECB - Binary character in Combine, binary off",

        2200 => "EIO - print to IO failed. See errno",

        # PP Only Error
        4002 => "EIQ - Unescaped ESC in quoted field",
        4003 => "EIF - ESC CR",
        4004 => "EUF - ",

        # Hash-Ref errors
        3001 => "EHR - Unsupported syntax for column_names ()",
        3002 => "EHR - getline_hr () called before column_names ()",
        3003 => "EHR - bind_columns () and column_names () fields count mismatch",
        3004 => "EHR - bind_columns () only accepts refs to scalars",
        3006 => "EHR - bind_columns () did not pass enough refs for parsed fields",
        3007 => "EHR - bind_columns needs refs to writable scalars",
        3008 => "EHR - unexpected error in bound fields",

        0    => "",
};

Error 2032 は終端前に制御コードが入っているのがエラーの原因と読み取れました。従って対処したコードとして、下記のように parse() を呼び出す前に、\x01-\x1f までの範囲の制御コードを削除するコードとしました。イメージとしてこんな感じです。

open my $fh, '<', $file or die;
my $tc = Text::CSV->new;
while(my $line=<$fh>) {
  $line =~ s/[\x01-\x1f]+$//gsm;
  $tc->parse($line);
  my @fields  = $tc->fields;
}
close $fh;

以上、備忘録をかねてメモとして残しておきました。やっぱりエラーが出たときはソース読むのが手っ取り早いですね。

- スポンサーリンク -