作者: dune
日時: 2002/9/01(04:05)
極悪です。

閑舎さんの txtsearch に触発されて、僕も pdf をテキストに変換
した結果を保存して、そこから文字列を検索するスクリプトを書い
てみました。インデックスを作るわけではないので高速とは言えな
いけど、ないよりはましでしょう。

本当は溜まって整理のつかないデバイスのデータシート(全部で 4
GB ある)を検索したくて作りました。家の PC だと 2.5GB ある  
pdf からテキストへの変換に 63 分(変換結果のテキストファイル
は 179MB)。そこから二語を検索するのに 40 秒かかりました。ヒ
ット数が多くなるともっと時間がかかると思います。ちなみにこの
スクリプトを書くのに 9 時間かかりました(わっはっは)。

さて、スクリプト(pdfsearch.pl)は環境変数 PDF に設定したデ
ィレクトリ以下の pdf から正規表現で複数の文字列を and 検索で
きます。

pdfsearch.pl を初めて起動すると、環境変数 PDF で指定したディ
レクトリに pdf の内容をテキストに変換して全部くっつけたファ
イル 00legend.txt を作ります。その後、pdfsearch.pl utf8 など
とすると、utf8 という文字列を含んだ pdf の内容(をテキストに
変換したもの)を表示します。検索する語は複数指定できます。

pdf からテキストへの変換は http://www.foolabs.com/xpdf/ にあ
る pdftotext というプログラムを使っています。解説を見ると   
EUC とか Shift-JIS にも対応できるようなのですが、やり方がわ
からなくて今回は化け化けのままほっといてます。

テキストへの変換は初回だけで2回目からは行いませんが、オプシ
ョン -f を指定すれば強制的に再変換します。また pdf2text の変
換結果をいじってパラグラフ単位にしてます(空行をパラグラフの
境界としてます)。

検索後を複数指定した場合、ファイルの中身を何回も走査するうえ、
処理が全部終わるまで結果が出てきません。そのときはオプション
-l を指定すると処理単位がファイル全体からパラグラフ単位にな
って結果が速く出てきます。



perl のデータで試した場合の例です。データ少ないので参考にな
らないかな。

{{{
D:%set PDF=D:\DATA\perl

D:%timer "perl pdfsearch.pl"
ok   : D:\DATA\perl/Kansai.pm/Seminar/20000319_1st/fukuhara/fukuhara.pdf
ok   : D:\DATA\perl/Kansai.pm/Seminar/20000319_1st/kiyoka/kiyoka.pdf
ok   : D:\DATA\perl/Kansai.pm/Seminar/20000319_1st/yamatomo/yamamoto.pdf
ok   : D:\DATA\perl/Kansai.pm/Seminar/20000520_2nd/funaki/funaki.pdf
ok   : D:\DATA\perl/Kansai.pm/Seminar/20000520_2nd/ivanov/perltalk.pdf
ok   : D:\DATA\perl/Kansai.pm/Seminar/20000520_2nd/moriwaka/moriwaka.pdf
ok   : D:\DATA\perl/Kansai.pm/Seminar/20001202_3rd/hozumi/hozumi.pdf
ok   : D:\DATA\perl/Kansai.pm/Seminar/20001202_3rd/hozumi/hozumi_appendix.pdf
ok   : D:\DATA\perl/Kansai.pm/Seminar/20001202_3rd/kiyoka/kiyoka_jcode.pdf
ok   : D:\DATA\perl/Kansai.pm/ご案内.pdf
ok   : D:\DATA\perl/mkweb/makeweb.pdf
pattern not specifed
21.364000 sec
D:%timer "perl pdfsearch.pl utf8"
"D:\DATA\perl/Kansai.pm/Seminar/20000520_2nd/ivanov/perltalk.pdf"       ュ
        ops: キ In regexp: ュ ・p{}, ・P{} => * * * match ュ * * * * キ
        lib/5.6.0/unicode *** キ use byte; キ use utf8; * * utf8 キ use
        charnames `:full'; * **name use charnames `:full'; print
        "・N{KATAKANA LETTER A}"; # キ use charnames `:short'; * *
        script:char use charnames `:short'; print "・N{katakana:a}";
0.082000 sec
D:%
}}}



スクリプトは下記です。いつものことで、発言をアップするとバグ
が見つかるのですが、ちょくちょく修正はしないし、これ以上の機
能アップ(インデックス作ったりとか)もできないと思いますが、
ここはチガウとか、こうしたらイイよ、というのがありました教え
てください。

--^pdfsearch.pl
use strict;
use File::Recurse;
use Getopt::Std;
use Text::Wrap;

my $PDF2TEXT    = q(C:/usr/xpdf/pdftotext.exe -raw -q);

my %opt;
getopts('fl',\%opt);
my $rootdir     = $ENV{PDF} or die qq(env var "PDF" not defined\n);
my @pattern     = map(qr/$_/,sort{length $b <=> length $a}@ARGV);

$rootdir        =~ s/[\/\\]/\\/;
$rootdir        =~ s/\\$//;
my $temp_file   = $rootdir.q(\\temp_file.tmp);
my $legend_file = $rootdir.q(\\00legend.txt);

END{
    unlink $temp_file;
    close LEGEND;
}

### テキストファイル作成 ###
if($opt{f} or not -f $legend_file){
    if(not -d $rootdir){
        die qq(dir "$rootdir" not found\n);
    }
    
    open(LEGEND,">$legend_file") or die qq($! "$legend_file"\n);
    recurse{
        do{{
            if(not s/\.pdf$//i){
                print qq(skip : $_\n)   if 0;
                last;
            }
            my $basename    = $_;
            my $error_level = `$PDF2TEXT "$basename.pdf" $temp_file`;
            if($error_level){
                print qq(err  : $basename.pdf\n);
                next;
            }
            print qq(ok   : $basename.pdf\n);
            
            open(TEMP,$temp_file) or die qq($! "$temp_file"\n);
            print LEGEND $basename,".pdf\n";
            my($last_line,$last_length);
            while(<TEMP>){
                s/\s+/\x20/g;
                s/^\s//;
                s/\s$//;
                my $length  = length;
                if($length){
                    if($last_length){
                        print LEGEND "\x20",$_;
                    }else{
                        print LEGEND $_;
                    }
                }else{
                    if($last_length){
                        print LEGEND "\n";
                    }else{
                        ;;;
                    }
                }
                ($last_line,$last_length)   = ($_,$length);
            }
            close TEMP;
            print LEGEND "\n";
        }}while(0);
    }$rootdir;
    close LEGEND;
}

### 検索 ###
if(not @pattern){
    die qq(pattern not specifed\n);
}
if(defined $opt{l}){
    open(LEGEND,$legend_file) or die qq($! "$temp_file");
    my $file;
    LOOP:while(<LEGEND>){
        if($_ eq "\n"){
            undef $file;
        }elsif(not $file){
            chomp;
            $file   = $_;
        }else{
            foreach my $pattern (@pattern){
                next LOOP unless m/$pattern/;
            }
            print wrap(qq/"$file"\t/,"\t",$_);
        }
    }
    close LEGEND;
}else{
    my(%found,%skip);
    foreach my $pattern (@pattern){
        my %islocalfound;
        open(LEGEND,$legend_file) or die qq($! "$temp_file");
        my $file;
        LOOP:while(<LEGEND>){
            if($_ eq "\n"){
                undef $file;
            }elsif(not $file){
                chomp;
                $file   = $_;
            }elsif($skip{$file}){
                ;;;
            }elsif(m/$pattern/){
                ++$islocalfound{$file};
                push(@{$found{$file}},$_);
            }
        }
        foreach my $file (keys %islocalfound){
            next if $islocalfound{$file};
            $skip{$file}    = 1;
        }
        close LEGEND;
    }
    foreach my $file (sort keys %found){
        next if $skip{$file};
        foreach(@{$found{$file}}){
            print wrap(qq/"$file"\t/,"\t",$_);
        }
    }
}
--$
-- 
FZH01112@..., http://homepage1.nifty.com/dune/