テキスト整形


このセクションでは、Web上からとったテキストを整形する方法を説明します。
以下の処理を目標にします。
・ゴミ取り...日本語システムで文字化けするような文字を書き換える(注:作者やサーバによってどのような文字が出るかはまちまちです)
・文ごとに改行する(注:100%完璧にはできません)


データの読み込み
ここでは、targetフォルダ内にあるwowcat.txtをサンプルとして使用します。まず、このテキストをリストに読み込みます。

--
#!/usr/local/bin/perl

$directory="target"; #目的のファイルがあるフォルダの名称
$out_dir="data"; #出力ファイルをこのディレクトリへ書き出す--後述
$file="wowcat\.txt"; #ファイルの名称。「\.」としてありますが\はなくてもほぼOK
$separator="\\"; #パスをつなぐ記号で、機種固有値(UNIX... / Mac... :)
$in=$directory.$separator.$file; #相対パスでファイルを指定

open (FILE, $in) || die "$in\n$!\n"; #ファイルオープン
while (<FILE>) { #ファイル内のデータ分繰り返し
	push (@array, $_); #配列@arrayに格納
}
close FILE;
#ファイルのクローズ
--

途中で改行された文をつなぐ
次に、テストデータは空白行で始まる箇所とそうでない箇所があり、文も大部分が途中で切れてしまっています。これを段落の切れ目だけが改行され、あとはつながっている文として整形することを考えます。
Web上では段落を表すタグは<P>と書かれます。これは見た目には段落の切れ目に一行の空白行を入れます。この空白行を段落の切れ目として判断することにし、あとの改行は削除して前後をスペースでつなげます。

--
for($i=1; $i<=$#array; $i++) { #配列の要素を削除したいので、for文を使用
  if ($array[$i] eq "\n") {next;} #空白行は何もせずループを抜ける
  if ($array[$i-1] eq "\n") {$array[$i] =~ s/^ +//; next;} #要素の1つ前が空白行の場合、先頭のスペース処理のみでループを抜ける
  #ここからスペースで連結させる処理
  $array[$i] =~ s/^ +//; #行頭の不用なスペース削除
  $array[$i-1] =~ s/ *\n$/ /; #現在処理中の要素の1つ前の要素の行末の改行をスペースに置換
  $array[$i-1] = $array[$i-1].$array[$i]; #現在の要素を1つ前の要素につなげる
  splice(@array, $i, 1); #現在の要素を配列から削除
  $i--; #要素1減文をカウンターに反映
}

#以下は確認のためのforeach文
foreach $yoso (@array) {
  print $yoso;
}
--

1文ずつに分割する
次に、文ごとに改行する方法について考えます。
文は「.」「?」「!」の後に空白文字(スペースか改行)や引用符、カッコがある場合に区切りであると考えることができます。ただ、次のような場合は厄介です。
Mr. Downey is very optimistic and upbeat.
U.S. officials say they lack even a recent photograph.

上記で「Mr.」や「U.S.」または「U.」と「S.」で改行するのはまずいので、まず考えられるこれらの文字列をデータとして記録します。ここではスカラー変数$dontに格納します。
--
$dont="\[A-Z\]|M(r|s|rs)|Dr|Calif|V[Aa]|[MS][Tt]|Jan|Feb|Mar|Apr|Aug|Sep(t|)|Oct|Nov|Dec|Assoc|Co|Gov|Se(n|c)|Ont|i\\\.e|e\\\.g|v(s|)|Pa|Fla|Re(p|v)|Gen|Univ|Jr|[fF]t|[Ss]gt|[Pp]res|[Pp]rof";
--
上記のデータは、以下の語を表します。
A. B. C. ... Z.
Mr. Ms. Mrs.
Dr.
Calif.
VA. Va.
MT. Mt. ST. St.
Jan. Feb. Mar. Apr. Aug. Sep. Sept. Oct. Nov. Dec.
Assoc.
Co.
Gov.
Sen. Sec.
Ont.
i.e.
e.g.
v. vs.
Pa.
Fla.
Rep. Rev.
Gen.
Univ.
Jr.
ft. Ft.
Sgt. sgt.
Pres. pres.
Prof. prof.

それでは、実際にセンテンスごとに改行する作業を考えます。
--
$dont="\[A-Z\]|M(r|s|rs)|Dr|Calif|V[Aa]|[MS][Tt]|Jan|Feb|Mar|Apr|Aug|Sep(t|)|Oct|Nov|Dec|Assoc|Co|Gov|Se(n|c)|Ont|i\\\.e|e\\\.g|v(s|)|Pa|Fla|Re(p|v)|Gen|Univ|Jr|[fF]t|[Ss]gt|[Pp]res|[Pp]rof";
foreach $yoso (@array) {
  $yoso =~ s/\? /\?\n/g;		#「?」+スペースを改行
  $yoso =~ s/! /!\n/g;			#「!」+スペースを改行
  $yoso =~ s/(\.|!|\?)\" /$1\"\n/g;	#「."」「!"」「?"」+スペースを改行
  $yoso =~ s/\.\) /\.\)\n/g;		#「?)」+スペースを改行
  $yoso =~ s/\. /\.\n/g;		#「.」+スペースを改行
  $yoso =~ s/\b($dont)\.\n/$1\. /g;	#$dontに合致する場合のみ復帰させる
  print $yoso;
}
--

$dont変数中、i\\\.eなどの記述がありますが、これは「\\」と書いて文字である「\」を、「\.」と書いて文字である「.」を表すためです。同様に「\[」は文字である「[」を、「\]」は文字である「]」を表すためです。引用符「""」で囲った文字列中には、「\n」のようなメタキャラクタが記述できますが、これを「//」で囲った正規表現で用いる場合は、上記のような記述の仕方になります。perlでは他に「''」という、シングルクォートで囲った文字列の表記法がありますが、この場合はメタキャラクタは働きません。

ごみ取り
次に「ごみ取り」の処理を考えます。この処理はデータやユーザーが必要としているテキストの種類によって左右されますので、各自で必要なデータを追加・修正して下さい。
例:
$yoso =~ s/<[^<>]*>//g; #タグを削除
$yoso =~ s/\s+\n$/\n/; #改行前のスペースの削除
$yoso =~ s/ {2,}/ /g; #2個以上のスペースの連続を1個のスペースに置換
$yoso =~ s/^ +//; #行頭のスペース削除
$yoso =~ s/ム/-/g; #文字化けする文字の置換
$yoso =~ s/-/-/g; #文字化けする文字の置換
$yoso =~ s//(c)/g; #文字化けする文字の置換
$yoso =~ s/’/'/g; #2バイト文字の置換
$yoso =~ s/“|”/"/g; #2バイト文字の置換
$yoso =~ s/・/*/g; #2バイト文字の置換
$yoso =~ s/([^ ])--([^ ])/$1 -- $2/g; #word--wordという箇所にスペース挿入
$yoso =~ s/ --([^ ])/ -- $1/g; # --wordという箇所にスペース挿入
$yoso =~ s/ -([^ -])/ - $1/g; # -wordという箇所にスペース挿入
$yoso =~ s/ \? / -- /g; # ? というパターンを -- に置換

このような作業は、センテンス分割処理と同時にできますので、そのループ内にこれらの文を埋め込みます。

ファイルへの書き出し
ファイルを書き込みでオープンします。ファイルへ書き込む場合もprintを使います。

$in =~ s/$directory/$out_dir/; #入力ファイルのディレクトリ部分を「data」に変更
open (FILE, "> $in") || die "Can't create $in\n$!\n"; #ファイルを書き込みでオープン
foreach $yoso (@array) {
  print FILE $yoso; #print の後にファイルハンドル名を書けば、ファイルへ出力される
}
close FILE; #ファイルクローズ

先ほどの改行処理のループ内に「ゴミ取り」と「書き出し」を追加すると以下のようになります。

--
$dont="\[A-Z\]|M(r|s|rs)|Dr|Calif|V[Aa]|[MS][Tt]|Jan|Feb|Mar|Apr|Aug|Sep(t|)|Oct|Nov|Dec|Assoc|Co|Gov|Se(n|c)|Ont|i\\\.e|e\\\.g|v(s|)|Pa|Fla|Re(p|v)|Gen|Univ|Jr|[fF]t|[Ss]gt|[Pp]res|[Pp]rof";

$in =~ s/$directory/$out_dir/;
open (FILE, "> $in") || die "Can't create $in\n$!\n";

foreach $yoso (@array) {
  #ごみ取り部分
  $yoso =~ s/<[^<>]*>//g;
  $yoso =~ s/\s+\n$/\n/;
  $yoso =~ s/ {2,}/ /g;
  $yoso =~ s/^ +//;
  $yoso =~ s/ム/-/g;
  $yoso =~ s/-/-/g;
  $yoso =~ s//(c)/g;
  $yoso =~ s/’/'/g;
  $yoso =~ s/“|”/"/g;
  $yoso =~ s/・/*/g;
  $yoso =~ s/([^ ])--([^ ])/$1 -- $2/g;
  $yoso =~ s/ --([^ ])/ -- $1/g;
  $yoso =~ s/ -([^ -])/ - $1/g;
  $yoso =~ s/ \? / -- /g;
  
  #センテンス分割部分
  $yoso =~ s/\? /\?\n/g;
  $yoso =~ s/! /!\n/g;
  $yoso =~ s/(\.|!|\?)\" /$1\"\n/g;
  $yoso =~ s/\.\) /\.\)\n/g;
  $yoso =~ s/\. /\.\n/g;
  $yoso =~ s/\b($dont)\.\n/$1\. /g;
  print FILE $yoso;
}
close FILE;
exit (0);
--

スクリプト全体…edit.pl